Field.java
001 /*
002  * Java GPX Library (jpx-3.1.0).
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *      http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 package io.jenetics.jpx.format;
017 
018 import static java.lang.Math.abs;
019 import static java.lang.Math.floor;
020 import static java.math.RoundingMode.DOWN;
021 import static java.math.RoundingMode.HALF_EVEN;
022 import static java.util.Objects.requireNonNull;
023 
024 import java.text.DecimalFormat;
025 import java.text.DecimalFormatSymbols;
026 import java.text.NumberFormat;
027 import java.text.ParsePosition;
028 import java.util.Locale;
029 import java.util.Optional;
030 import java.util.concurrent.atomic.AtomicReference;
031 
032 /**
033  * Represents one of the existing location fields: latitude, longitude and
034  * elevation.
035  *
036  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
037  @version 3.0
038  @since 1.4
039  */
040 abstract class Field implements Format {
041 
042     private static final DecimalFormatSymbols SYMBOLS =
043         DecimalFormatSymbols.getInstance(Locale.US);
044 
045     private final String _pattern;
046     private final char _type;
047 
048     private boolean _prefixSign = false;
049     private boolean _absolute = false;
050 
051     private final AtomicReference<NumberFormat> _format = new AtomicReference<>();
052 
053     Field(final String pattern, final char type) {
054         _pattern = requireNonNull(pattern);
055         _type = type;
056         _format.set(new DecimalFormat(toDecimalPattern(pattern, type), SYMBOLS));
057     }
058 
059     private static String toDecimalPattern(final String pattern, final char type) {
060         return pattern.replace(type, '0');
061     }
062 
063     final void setPrefixSign(final boolean b) {
064         _prefixSign = b;
065 
066         final String decimalPattern = toDecimalPattern(_pattern, _type);
067         final String pattern = _prefixSign
068             "+%1$s;-%1$s".formatted(decimalPattern)
069             :  decimalPattern;
070 
071         setFormat(new DecimalFormat(pattern, SYMBOLS));
072     }
073 
074     final boolean isPrefixSign() {
075         return _prefixSign;
076     }
077 
078     final void setAbsolute(final boolean b) {
079         _absolute = b;
080     }
081 
082     final boolean isAbsolute() {
083         return _absolute;
084     }
085 
086     final void setTruncate(final boolean b) {
087         _format.get().setRoundingMode(b ? DOWN : HALF_EVEN);
088     }
089 
090     final void setFormat(final NumberFormat format) {
091         _format.set(requireNonNull(format));
092     }
093 
094     final int getMinimumFractionDigits() {
095         return _format.get().getMinimumFractionDigits();
096     }
097 
098     @Override
099     public String toPattern() {
100         return _pattern;
101     }
102 
103     /**
104      * Formatting the given double value with the field formatter.
105      *
106      @param value the double value to format
107      @return the formatted double value
108      */
109     final String format(final double value) {
110         synchronized (_format) {
111             return _format.get().format(value);
112         }
113     }
114 
115     /**
116      * Parsers the given input string.
117      *
118      @param in the input string to parse
119      @param pos the parse position
120      @return the parsed double value
121      */
122     final double parse(final CharSequence in, final ParsePosition pos) {
123         int i = pos.getIndex();
124         String s = in.toString();
125         boolean strictWidth = < _format.get().getMinimumIntegerDigits();
126         if (strictWidth) {
127             int end = i + toPattern().length();
128             s = in.subSequence(0, end).toString();
129         }
130 
131         final Number n = parse0(s, pos);
132 
133         if (i == pos.getIndex()) {
134             pos.setErrorIndex(i);
135             throw new ParseException("Not found " + toPattern(), in, i);
136         }
137 
138         return n.doubleValue();
139     }
140 
141     private Number parse0(final String value, final ParsePosition pos) {
142         synchronized (_format) {
143             return _format.get().parse(value, pos);
144         }
145     }
146 
147 
148     static double toMinutes(final double degrees) {
149         final double dd = abs(degrees);
150         return (dd - floor(dd)) 60.0;
151     }
152 
153     static double toSeconds(final double degrees) {
154         final double dd = abs(degrees);
155         final double d = floor(dd);
156         final double m = floor((dd - d60.0);
157         return (dd - d - m / 60.03600.0;
158     }
159 
160     static Optional<Field> ofPattern(final String pattern) {
161         // TODO better?
162         for (int i = 0; i < pattern.length(); ++i) {
163             switch (pattern.charAt(i)) {
164                 case 'L'{
165                     final String p = pattern.replace('L''D');
166                     return Optional.of(new LatitudeDegree(p));
167                 }
168                 case 'D'return Optional.of(new LatitudeDegree(pattern));
169                 case 'M'return Optional.of(new LatitudeMinute(pattern));
170                 case 'S'return Optional.of(new LatitudeSecond(pattern));
171                 case 'l'{
172                     final String p = pattern.replace('l','d');
173                     return Optional.of(new LongitudeDegree(p));
174                 }
175                 case 'd'return Optional.of(new LongitudeDegree(pattern));
176                 case 'm'return Optional.of(new LongitudeMinute(pattern));
177                 case 's'return Optional.of(new LongitudeSecond(pattern));
178                 case 'E'return Optional.of(new Elevation(pattern));
179                 case 'H'{
180                     final String p = pattern.replace('H''E');
181                     return Optional.of(new Elevation(p));
182                 }
183             }
184         }
185 
186         return Optional.empty();
187     }
188 
189 }