private Angle parse()

in endorsed/src/org.apache.sis.util/main/org/apache/sis/measure/AngleFormat.java [1469:1795]


    private Angle parse(final String source, final ParsePosition pos, final boolean spaceAsSeparator) {
        double degrees;
        double minutes   = NaN;
        double seconds   = NaN;
        final int length = source.length();
        @SuppressWarnings("LocalVariableHidesMemberVariable")
        final NumberFormat numberFormat = numberFormat();
        //  ┌─────────────────────────────────────────────────────────────────────────┐
        //  │ BLOCK A: Assign values to `degrees`, `minutes` and `seconds` variables. │
        //  │          This block does not take the hemisphere field in account, and  │
        //  │          values will need adjustment if decimal separator is missing.   │
        //  │          The { } block is for restricting the scope of local variables. │
        //  └─────────────────────────────────────────────────────────────────────────┘
        {
            /*
             * Extract the prefix, if any. If we find a degrees, minutes or seconds suffix
             * before to have meet any number, we will consider that as a parsing failure.
             */
            final int indexStart = pos.getIndex();
            int index = skipSuffix(source, pos, PREFIX_FIELD);
            if (index >= DEGREES_FIELD && index <= SECONDS_FIELD) {
                pos.setErrorIndex(indexStart);
                pos.setIndex(indexStart);
                return null;
            }
            index = skipSpaces(source, pos.getIndex(), length);
            pos.setIndex(index);
            /*
             * Parse the degrees field. If there is no separator between degrees, minutes
             * and seconds, then the parsed number may actually include many fields (e.g.
             * "DDDMMmmm"). The separation will be done later.
             */
            Number fieldObject = numberFormat.parse(source, pos);
            if (fieldObject == null) {
                pos.setIndex(indexStart);
                if (pos.getErrorIndex() < indexStart) {
                    pos.setErrorIndex(index);
                }
                return null;
            }
            degrees = fieldObject.doubleValue();
            int indexEndField = pos.getIndex();
            boolean missingDegrees = true;
BigBoss:    switch (skipSuffix(source, pos, DEGREES_FIELD)) {
                /* ------------------------------------------
                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
                 * ------------------------------------------
                 * The degrees value is followed by the prefix for angles.
                 * Stop parsing, since the remaining characters are for another angle.
                 */
                case PREFIX_FIELD: {
                    pos.setIndex(indexEndField);
                    break BigBoss;
                }
                /* ------------------------------------------
                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
                 * ------------------------------------------
                 * Found the seconds suffix instead of the degrees suffix. Move 'degrees'
                 * value to 'seconds' and stop parsing, since seconds are the last field.
                 */
                case SECONDS_FIELD: {
                    seconds = degrees;
                    degrees = NaN;
                    break BigBoss;
                }
                /* ------------------------------------------
                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
                 * ------------------------------------------
                 * No recognized suffix after degrees. If "spaces as separator" are allowed and
                 * a minutes field is expected after the degrees field, we will pretent that we
                 * found the minutes suffix. Otherwise stop parsing.
                 */
                default: {
                    if (!spaceAsSeparator || !isFallbackAllowed || minutesFieldWidth == 0) {
                        break BigBoss;
                    }
                    // Fall through for parsing minutes.
                }
                /* ------------------------------------------
                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES
                 * ------------------------------------------
                 * After the degrees field, check if there is a minute field.
                 * We proceed as for degrees (parse a number, skip the suffix).
                 */
                case DEGREES_FIELD: {
                    final int indexStartField = pos.getIndex();
                    index = skipSpaces(source, indexStartField, length);
                    if (!spaceAsSeparator && index != indexStartField) {
                        break BigBoss;
                    }
                    pos.setIndex(index);
                    fieldObject = numberFormat.parse(source, pos);
                    if (fieldObject == null) {
                        pos.setIndex(indexStartField);
                        break BigBoss;
                    }
                    indexEndField = pos.getIndex();
                    minutes = fieldObject.doubleValue();
                    switch (skipSuffix(source, pos, (minutesFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                         * ------------------------------------------
                         * Found the expected suffix, nothing special to do.
                         * Continue the outer switch for parsing seconds.
                         */
                        case MINUTES_FIELD: {
                            break; // Continue outer switch for parsing seconds.
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                         * ------------------------------------------
                         * Found the seconds suffix instead of the minutes suffix. Move 'minutes'
                         * value to 'seconds' and stop parsing, since seconds are the last field.
                         */
                        case SECONDS_FIELD: {
                            seconds = minutes;
                            minutes = NaN;
                            break BigBoss;
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                         * ------------------------------------------
                         * No suffix has been found. This is normal if the pattern doesn't specify
                         * a minutes field, in which case we reject the number that we just parsed.
                         * However if minutes were expected and space separators are allowed, then
                         * check for seconds.
                         */
                        default: {
                            if (spaceAsSeparator && isFallbackAllowed && minutesFieldWidth != 0) {
                                break; // Continue outer switch for parsing seconds.
                            }
                            // Fall through for rejecting the minutes.
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                         * ------------------------------------------
                         * Found the degrees suffix instead of the minutes suffix.
                         * This means that the number we have just read belong to an
                         * other angle. Stop the parsing before that number.
                         */
                        case DEGREES_FIELD: {
                            pos.setIndex(indexStartField);
                            minutes = NaN;
                            break BigBoss;
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED MINUTES
                         * ------------------------------------------
                         * Found the prefix of another angle. Accept the number that
                         * we have just parsed despite the missing minutes suffix, and
                         * stop parsing before the prefix.
                         */
                        case PREFIX_FIELD: {
                            pos.setIndex(indexEndField);
                            break BigBoss;
                        }
                    }
                    missingDegrees = false;
                    // Fall through for parsing the seconds.
                }
                /* -----------------------------------------------------
                 * STRING ANALYSIS FOLLOWING PRESUMED DEGREES OR MINUTES
                 * -----------------------------------------------------
                 * If a minutes field was found without degrees, move the 'degrees'
                 * value to 'minutes'. Then try to parse the next number as seconds.
                 */
                case MINUTES_FIELD: {
                    if (missingDegrees) {
                        minutes = degrees;
                        degrees = NaN;
                    }
                    final int indexStartField = pos.getIndex();
                    index = skipSpaces(source, indexStartField, length);
                    if (!spaceAsSeparator && index != indexStartField) {
                        break BigBoss;
                    }
                    pos.setIndex(index);
                    fieldObject = numberFormat.parse(source, pos);
                    if (fieldObject == null) {
                        pos.setIndex(indexStartField);
                        break;
                    }
                    indexEndField = pos.getIndex();
                    seconds = fieldObject.doubleValue();
                    switch (skipSuffix(source, pos, (secondsFieldWidth != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
                         * ------------------------------------------
                         * Found the expected second suffix. We are done.
                         */
                        case SECONDS_FIELD: {
                            break;
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
                         * ------------------------------------------
                         * No suffix has been found. This is normal if the pattern doesn't specify
                         * a seconds field, in which case we reject the number that we just parsed.
                         * However if seconds were expected and space separators are allowed, then
                         * accept the value.
                         */
                        default: {
                            if (isFallbackAllowed && secondsFieldWidth != 0) {
                                break;
                            }
                            // Fall through for rejecting the seconds.
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
                         * ------------------------------------------
                         * Found the degrees or minutes suffix instead of the seconds suffix.
                         * This means that the number we have just read belong to another angle.
                         * Stop the parsing before that number.
                         */
                        case MINUTES_FIELD:
                        case DEGREES_FIELD: {
                            pos.setIndex(indexStartField);
                            seconds = NaN;
                            break;
                        }
                        /* ------------------------------------------
                         * STRING ANALYSIS FOLLOWING PRESUMED SECONDS
                         * ------------------------------------------
                         * Found the prefix of another angle. Accept the number that
                         * we have just parsed despite the missing seconds suffix, and
                         * stop parsing before the prefix.
                         */
                        case PREFIX_FIELD: {
                            pos.setIndex(indexEndField);
                            break BigBoss;
                        }
                    }
                }
            }
        }
        //  ┌────────────────────────────────────────────────────────────────┐
        //  │ BLOCK B: Handle the case when there is no decimal separator.   │
        //  │          Then combine the fields into a decimal degrees value. │
        //  └────────────────────────────────────────────────────────────────┘
        if (isNegative(minutes)) {
            seconds = -seconds;
        }
        if (isNegative(degrees)) {
            minutes = -minutes;
            seconds = -seconds;
        }
        if (!useDecimalSeparator) {
            final double facteur = pow10(fractionFieldWidth);
            if (secondsFieldWidth != 0) {
                if (minutesSuffix == null && isNaN(seconds)) {
                    if (degreesSuffix == null && isNaN(minutes)) {
                        degrees /= facteur;
                    } else {
                        minutes /= facteur;
                    }
                } else {
                    seconds /= facteur;
                }
            } else if (isNaN(seconds)) {
                if (minutesFieldWidth != 0) {
                    if (degreesSuffix == null && isNaN(minutes)) {
                        degrees /= facteur;
                    } else {
                        minutes /= facteur;
                    }
                } else if (isNaN(minutes)) {
                    degrees /= facteur;
                }
            }
        }
        /*
         * If there is no separation between degrees and minutes fields (e.g. if the pattern
         * is "DDDMMmmm"), then the 'degrees' variable contains degrees, minutes and seconds
         * in sexagesimal units. We need to convert to decimal units.
         */
        if (minutesSuffix == null && secondsFieldWidth != 0 && isNaN(seconds)) {
            double facteur = pow10(secondsFieldWidth);
            if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
                // ┌───────────────┐
                // │   DDDMMSS.s   │
                // └───────────────┘
                seconds  = degrees;
                minutes  = truncate(degrees / facteur);
                seconds -= minutes * facteur;
                facteur  = pow10(minutesFieldWidth);
                degrees  = truncate(minutes / facteur);
                minutes  -= degrees * facteur;
            } else {
                // ┌───────────────┐
                // │   DDD°MMSS.s  │
                // └───────────────┘
                seconds  = minutes;
                minutes  = truncate(minutes / facteur);
                seconds -= minutes*facteur;
            }
        } else if (degreesSuffix == null && minutesFieldWidth != 0 && isNaN(minutes)) {
            // ┌─────────────┐
            // │   DDDMM.m   │
            // └─────────────┘
            final double facteur = pow10(minutesFieldWidth);
            minutes  = degrees;
            degrees  = truncate(degrees / facteur);
            minutes -= degrees * facteur;
        }
        pos.setErrorIndex(-1);
        if ( isNaN(degrees)) degrees  = 0;
        if (!isNaN(minutes)) degrees += minutes /   60;
        if (!isNaN(seconds)) degrees += seconds / 3600;
        //  ┌─────────────────────────────────────────────────────┐
        //  │ BLOCK C: Check for hemisphere suffix (N, S, E or W) │
        //  │          after the angle string representation.     │
        //  └─────────────────────────────────────────────────────┘
        for (int index = pos.getIndex(); index < length;) {
            final int c = source.codePointAt(index);
            index += Character.charCount(c);
            switch (Character.toUpperCase(c)) {
                case NORTH: pos.setIndex(index); return new Latitude ( degrees);
                case SOUTH: pos.setIndex(index); return new Latitude (-degrees);
                case EAST : pos.setIndex(index); return new Longitude( degrees);
                case WEST : pos.setIndex(index); return new Longitude(-degrees);
            }
            if (!Character.isSpaceChar(c)) {                // Method shall be consistent with skipSpaces(…)
                break;
            }
        }
        return new Angle(degrees);
    }