Ignore:
Timestamp:
2023-11-02T17:28:05+01:00 (14 months ago)
Author:
taylor.smock
Message:

Fix failing FIT test. Also decrease parsing method complexity a bit.

Location:
applications/editors/josm/plugins/FIT/src/main/java/org/openstreetmap/josm/plugins/fit/lib
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/FIT/src/main/java/org/openstreetmap/josm/plugins/fit/lib/FitReader.java

    r36160 r36192  
    1212import java.util.EnumSet;
    1313import java.util.List;
     14import java.util.Set;
    1415import java.util.logging.Level;
    1516import java.util.logging.Logger;
     
    3435 */
    3536public final class FitReader {
     37    private record LastTimestamp(long lastTimestamp, int offsetAddition, byte lastOffset){}
     38    private record ParseFitRecordHeader(LastTimestamp lastTimestamp, FitDeveloperFieldDescriptionMessage[] devFields){}
    3639    private static final FitData[] EMPTY_FIT_DATA_ARRAY = new FitData[0];
    3740
     
    4548     * @param inputStream The stream to read
    4649     * @param options options for reading the file
     50     * @return The parsed FIT data
    4751     * @throws FitException if there was an error reading the fit file <i>or</i> there was an {@link InputStream} read
    4852     *                      exception.
     
    5559        final var fitData = new ArrayList<FitData>();
    5660        try {
    57             final var header = readFitHeader(bufferedInputStream);
    58             final var headerSize = bufferedInputStream.bytesRead();
    59             bufferedInputStream.mark(1);
    60             var localMessageHeaders = new FitDefinitionMessage[1];
    61             var developerData = new FitDeveloperFieldDescriptionMessage[0];
    62             long lastTimeStamp = Long.MIN_VALUE;
    63             var offsetAddition = 0;
    64             byte lastOffset = 0;
    65             while (bufferedInputStream.read() != -1
    66                     && bufferedInputStream.bytesRead() < header.dataSize() - headerSize) {
    67                 bufferedInputStream.reset();
    68                 final var nextRecordHeader = readNextRecordHeader(bufferedInputStream);
    69                 if (nextRecordHeader instanceof FitRecordNormalHeader normalHeader) {
    70                     if (normalHeader.isDefinitionMessage()) {
    71                         final var fitDefinitionMessage = readNextDefinition(normalHeader, bufferedInputStream);
    72                         if (localMessageHeaders.length <= normalHeader.localMessageType()) {
    73                             localMessageHeaders = Arrays.copyOf(localMessageHeaders,
    74                                     normalHeader.localMessageType() + 1);
    75                         }
    76                         localMessageHeaders[normalHeader.localMessageType()] = fitDefinitionMessage;
    77                     } else {
    78                         final Object obj = readNextRecord(localMessageHeaders[normalHeader.localMessageType()],
    79                                 developerData, bufferedInputStream);
    80                         if (obj instanceof IFitTimestamp<?> timestamp) {
    81                             lastTimeStamp = timestamp.timestamp().getEpochSecond() & 0xFF_FFF_FE0L;
    82                             offsetAddition = 0;
    83                             lastOffset = 0;
    84                         }
    85                         if (obj instanceof FitData fd) {
    86                             fitData.add(fd);
    87                         } else if (obj instanceof FitDeveloperFieldDescriptionMessage developerFieldDescriptionMessage) {
    88                             if (developerData.length <= developerFieldDescriptionMessage.developerDataIndex()) {
    89                                 developerData = Arrays.copyOf(developerData,
    90                                         developerFieldDescriptionMessage.developerDataIndex() + 1);
    91                             }
    92                             developerData[developerFieldDescriptionMessage
    93                                     .developerDataIndex()] = developerFieldDescriptionMessage;
    94                         } else {
    95                             handleException(optionsSet, new IllegalStateException("Unknown record type"));
    96                         }
    97                     }
    98                 } else if (nextRecordHeader instanceof FitRecordCompressedTimestampHeader compressedHeader) {
    99                     if (lastTimeStamp == Long.MIN_VALUE) {
    100                         handleException(optionsSet,
    101                                 new FitException("No full timestamp found before compressed timestamp header"));
    102                         break;
    103                     }
    104                     if (compressedHeader.timeOffset() < lastOffset) {
    105                         offsetAddition++;
    106                     }
    107                     final Object obj = readNextRecord(localMessageHeaders[compressedHeader.localMessageType()],
    108                             developerData, bufferedInputStream);
    109                     if (obj instanceof FitData fd) {
    110                         // Note: this could be wrong
    111                         long actualTimestamp = lastTimeStamp + compressedHeader.timeOffset() + (offsetAddition * 0x20L);
    112                         if (fd instanceof IFitTimestamp<?> timestamp) {
    113                             fitData.add((FitData) timestamp.withTimestamp(Instant.ofEpochSecond(actualTimestamp)));
    114                         } else {
    115                             fitData.add(fd);
    116                         }
    117                     } else {
    118                         handleException(optionsSet, new IllegalStateException(
    119                                 "Cannot handle non-data types with compressed timestamp headers"));
    120                     }
    121                     lastOffset = compressedHeader.timeOffset();
    122                 } else {
    123                     handleException(optionsSet,
    124                             new UnsupportedOperationException("Unknown record type: " + nextRecordHeader.getClass()));
    125                 }
    126                 bufferedInputStream.mark(1);
    127             }
     61            parseData(bufferedInputStream, optionsSet, fitData);
    12862        } catch (FitException fitException) {
    12963            handleException(optionsSet, fitException);
     
    13468    }
    13569
    136     private static <T extends Throwable> void handleException(EnumSet<FitReaderOptions> options, T throwable) throws T {
     70    private static void parseData(CountingInputStream bufferedInputStream,
     71                                  Set<FitReaderOptions> optionsSet, List<FitData> fitData) throws IOException {
     72        final var header = readFitHeader(bufferedInputStream);
     73        final var headerSize = bufferedInputStream.bytesRead();
     74        bufferedInputStream.mark(1);
     75        var localMessageHeaders = new FitDefinitionMessage[1];
     76        var developerData = new FitDeveloperFieldDescriptionMessage[0];
     77        var lastTimeStamp = new LastTimestamp(Long.MIN_VALUE, 0, (byte) 0);
     78        while (bufferedInputStream.read() != -1
     79                && bufferedInputStream.bytesRead() < header.dataSize() + headerSize) {
     80            bufferedInputStream.reset();
     81            final var nextRecordHeader = readNextRecordHeader(bufferedInputStream);
     82            if (nextRecordHeader instanceof FitRecordNormalHeader normalHeader) {
     83                if (normalHeader.isDefinitionMessage()) {
     84                    localMessageHeaders = parseFitRecordDefinitionHeader(bufferedInputStream, localMessageHeaders, normalHeader);
     85                } else {
     86                    final var parsed = parseFitRecordHeader(optionsSet, bufferedInputStream,
     87                            localMessageHeaders, developerData, fitData, normalHeader);
     88                    if (parsed.lastTimestamp() != null) {
     89                        lastTimeStamp = parsed.lastTimestamp();
     90                    }
     91                    developerData = parsed.devFields();
     92                }
     93            } else if (nextRecordHeader instanceof FitRecordCompressedTimestampHeader compressedHeader) {
     94                if (lastTimeStamp.lastTimestamp() == Long.MIN_VALUE) {
     95                    handleException(optionsSet,
     96                            new FitException("No full timestamp found before compressed timestamp header"));
     97                    break;
     98                }
     99                if (compressedHeader.timeOffset() < lastTimeStamp.lastOffset()) {
     100                    lastTimeStamp = new LastTimestamp(lastTimeStamp.lastTimestamp(), lastTimeStamp.offsetAddition() + 1,
     101                            lastTimeStamp.lastOffset());
     102                }
     103                parseFitRecordCompressedHeader(optionsSet, bufferedInputStream, localMessageHeaders, developerData,
     104                        fitData, compressedHeader, lastTimeStamp);
     105                lastTimeStamp = new LastTimestamp(lastTimeStamp.lastTimestamp(), lastTimeStamp.offsetAddition(),
     106                        compressedHeader.timeOffset());
     107            } else {
     108                handleException(optionsSet,
     109                        new UnsupportedOperationException("Unknown record type: " + nextRecordHeader.getClass()));
     110            }
     111            bufferedInputStream.mark(1);
     112        }
     113    }
     114
     115    private static FitDefinitionMessage[] parseFitRecordDefinitionHeader(InputStream inputStream,
     116                                                                         FitDefinitionMessage[] localMessageHeaders,
     117                                                                         FitRecordNormalHeader normalHeader) throws IOException {
     118        final var fitDefinitionMessage = readNextDefinition(normalHeader, inputStream);
     119        if (localMessageHeaders.length <= normalHeader.localMessageType()) {
     120            localMessageHeaders = Arrays.copyOf(localMessageHeaders,
     121                    normalHeader.localMessageType() + 1);
     122        }
     123        localMessageHeaders[normalHeader.localMessageType()] = fitDefinitionMessage;
     124        return localMessageHeaders;
     125    }
     126
     127    private static ParseFitRecordHeader parseFitRecordHeader(Set<FitReaderOptions> optionsSet, InputStream bufferedInputStream,
     128                                               FitDefinitionMessage[] localMessageHeaders, FitDeveloperFieldDescriptionMessage[] developerData,
     129                                               List<FitData> fitData, FitRecordNormalHeader normalHeader) throws IOException {
     130        final Object obj = readNextRecord(localMessageHeaders[normalHeader.localMessageType()],
     131                developerData, bufferedInputStream);
     132        LastTimestamp lastTimeStamp = null;
     133        if (obj instanceof IFitTimestamp<?> timestamp) {
     134            lastTimeStamp = new LastTimestamp(timestamp.timestamp().getEpochSecond() & 0xFF_FFF_FE0L, 0, (byte) 0);
     135        }
     136        if (obj instanceof FitData fd) {
     137            fitData.add(fd);
     138        } else if (obj instanceof FitDeveloperFieldDescriptionMessage developerFieldDescriptionMessage) {
     139            if (developerData.length <= developerFieldDescriptionMessage.developerDataIndex()) {
     140                developerData = Arrays.copyOf(developerData,
     141                        developerFieldDescriptionMessage.developerDataIndex() + 1);
     142            }
     143            developerData[developerFieldDescriptionMessage
     144                    .developerDataIndex()] = developerFieldDescriptionMessage;
     145        } else {
     146            handleException(optionsSet, new IllegalStateException("Unknown record type"));
     147        }
     148        return new ParseFitRecordHeader(lastTimeStamp, developerData);
     149    }
     150
     151    private static void parseFitRecordCompressedHeader(Set<FitReaderOptions> optionsSet, InputStream bufferedInputStream,
     152                                                       FitDefinitionMessage[] localMessageHeaders,
     153                                                       FitDeveloperFieldDescriptionMessage[] developerData, List<FitData> fitData,
     154                                                       FitRecordCompressedTimestampHeader compressedHeader, LastTimestamp lastTimeStamp)
     155            throws IOException {
     156        final Object obj = readNextRecord(localMessageHeaders[compressedHeader.localMessageType()],
     157                developerData, bufferedInputStream);
     158        if (obj instanceof FitData fd) {
     159            // Note: this could be wrong
     160            long actualTimestamp = lastTimeStamp.lastTimestamp() + compressedHeader.timeOffset() + (lastTimeStamp.offsetAddition() * 0x20L);
     161            if (fd instanceof IFitTimestamp<?> timestamp) {
     162                fitData.add((FitData) timestamp.withTimestamp(Instant.ofEpochSecond(actualTimestamp)));
     163            } else {
     164                fitData.add(fd);
     165            }
     166        } else {
     167            handleException(optionsSet, new IllegalStateException(
     168                    "Cannot handle non-data types with compressed timestamp headers"));
     169        }
     170    }
     171
     172    private static <T extends Throwable> void handleException(Set<FitReaderOptions> options, T throwable) throws T {
    137173        if (!options.contains(FitReaderOptions.TRY_TO_FINISH)) {
    138174            throw throwable;
  • applications/editors/josm/plugins/FIT/src/main/java/org/openstreetmap/josm/plugins/fit/lib/utils/DevDataUtils.java

    r36158 r36192  
    2525    }
    2626
     27    /**
     28     * Parse the dev fields
     29     * @param littleEndian {@code true} if the bit ordering is little endian
     30     * @param developerFieldList The current list of developer fiels
     31     * @param devFields The array of developer field descriptions
     32     * @param inputStream The stream to read
     33     * @return The dev data record
     34     * @throws IOException If something happened while reading the stream
     35     */
    2736    public static FitDevDataRecord parseDevFields(boolean littleEndian,
    2837            Collection<FitDeveloperField> developerFieldList, FitDeveloperFieldDescriptionMessage[] devFields,
Note: See TracChangeset for help on using the changeset viewer.