plugins.ActivityFileValidationPlugin Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fit Show documentation
Show all versions of fit Show documentation
The Official Garmin FIT SDK
/////////////////////////////////////////////////////////////////////////////////////////////
// Copyright 2023 Garmin International, Inc.
// Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
// may not use this file except in compliance with the Flexible and Interoperable Data
// Transfer (FIT) Protocol License.
/////////////////////////////////////////////////////////////////////////////////////////////
// ****WARNING**** This file is auto-generated! Do NOT edit this file.
// Profile Version = 21.120Release
// Tag = production/release/21.120.00-0-g2d77811
/////////////////////////////////////////////////////////////////////////////////////////////
package com.garmin.fit.plugins;
import com.garmin.fit.ActivityMesg;
import com.garmin.fit.DateTime;
import com.garmin.fit.DeviceIndex;
import com.garmin.fit.DeviceInfoMesg;
import com.garmin.fit.File;
import com.garmin.fit.FileIdMesg;
import com.garmin.fit.Fit;
import com.garmin.fit.FitListener;
import com.garmin.fit.FitMessages;
import com.garmin.fit.LapMesg;
import com.garmin.fit.Manufacturer;
import com.garmin.fit.Mesg;
import com.garmin.fit.MesgBroadcastPlugin;
import com.garmin.fit.RecordMesg;
import com.garmin.fit.SessionMesg;
import com.garmin.fit.plugins.ActivityFileValidationResult.Level;
import com.garmin.fit.plugins.ActivityFileValidationResult.Status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class ActivityFileValidationPlugin implements MesgBroadcastPlugin {
private static final int MIN_TIMEZONE_OFFSET_IN_SECONDS = -43200; // -12 hours
private static final int MAX_TIMEZONE_OFFSET_IN_SECONDS = 50400; // +14 hours
private final FitListener fitListener;
private final ArrayList results;
private int mesgCount;
public ActivityFileValidationPlugin() {
fitListener = new FitListener();
results = new ArrayList<>();
mesgCount = 0;
}
@Override
public void onIncomingMesg(Mesg mesg) {
fitListener.onMesg(mesg);
mesgCount++;
}
@Override
public void onBroadcast(List mesgs) {
FitMessages fitMessages = fitListener.getFitMessages();
results.clear();
// FileId Message Checks
results.add(checkFileIdMesgExists(fitMessages.getFileIdMesgs()));
if (fitMessages.getFileIdMesgs().size() > 0) {
results.add(checkFileIdMesgIsFirst(mesgs));
results.add(checkFileIdMesgType(fitMessages.getFileIdMesgs().get(0)));
results.add(checkFileIdMesgManufacturerId(fitMessages.getFileIdMesgs().get(0)));
results.add(checkFileIdMesgTimeCreated(fitMessages.getFileIdMesgs().get(0)));
}
// Activity Message Checks
results.add(checkActivityMesgExists(fitMessages.getActivityMesgs()));
if (fitMessages.getActivityMesgs().size() > 0) {
results.add(checkActivityMesgTimestamp(fitMessages.getActivityMesgs().get(0)));
results.add(checkActivityMesgLocalTimeStamp(fitMessages.getActivityMesgs().get(0)));
results.add(checkActivityMesgTotalTimerTime(fitMessages.getActivityMesgs().get(0), fitMessages.getSessionMesgs()));
results.add(checkActivityMesgSessionCount(fitMessages.getActivityMesgs().get(0), fitMessages.getSessionMesgs().size()));
}
// Session Message Checks
results.add(checkSessionMesgsExists(fitMessages.getSessionMesgs().size()));
results.add(checkSessionMesgTimestamp(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgStartTime(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgTotalTimerTimeAndTotalElapsedTime(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgFirstLapIndexAndNumLapsAreSequentialAndAbut(fitMessages.getSessionMesgs(), fitMessages.getLapMesgs()));
results.add(checkSessionMesgTotalTimerTime(fitMessages.getSessionMesgs(), fitMessages.getLapMesgs()));
results.add(checkSessionMesgTotalElapsedTime(fitMessages.getSessionMesgs(), fitMessages.getLapMesgs()));
results.add(checkSessionMesgSport(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgSubSport(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgsAreSequentialAndAbut(fitMessages.getSessionMesgs()));
results.add(checkSessionMesgValidMesgIndexes(fitMessages.getSessionMesgs()));
// Lap Message Checks
results.add(checkLapMesgsExists(fitMessages.getLapMesgs().size()));
results.add(checkLapMesgValidMesgIndexes(fitMessages.getLapMesgs()));
results.add(checkLapMesgValidStartTimeAndTimeStamp(fitMessages.getSessionMesgs(), fitMessages.getLapMesgs()));
results.add(checkLapMesgsAreSequentialAndAbut(fitMessages.getLapMesgs()));
//Record Message Checks
results.add(checkRecordMesgTimestampsAgainstSessionMesgTimes(fitMessages.getSessionMesgs(), fitMessages.getRecordMesgs()));
results.add(checkRecordMesgsAreChronological(fitMessages.getRecordMesgs()));
//Device Info Message Checks
results.add(checkDeviceInfoMesgValidTimestamp(fitMessages.getDeviceInfoMesgs()));
results.add(checkDeviceInfoMesgValidDeviceIndex(fitMessages.getDeviceInfoMesgs()));
results.add(checkDeviceInfoMesgValidManufacturerIdExists(fitMessages.getDeviceInfoMesgs()));
}
/**
* Repeat message validation by passing an empty message list to onBroadcast().
* Some checks may be skipped or fail since the message list is empty. In normal
* circumstances, this method does not need to called directly.
*/
public void repeatValidation() {
onBroadcast(new ArrayList());
}
public int getMesgCount() {
return mesgCount;
}
public List getResults() {
return Collections.unmodifiableList(results);
}
ActivityFileValidationResult checkFileIdMesgExists(List fileIdMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("FileId Message Exists", Level.REQUIRED);
if (fileIdMesgs.size() > 0) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
}
return result;
}
ActivityFileValidationResult checkFileIdMesgIsFirst(List mesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("FileId Message Is First", Level.REQUIRED);
for (Mesg mesg : mesgs) {
if (mesg.getName().equals("pad")) {
continue;
} else if (mesg.getName().equals("file_id")) {
result.setStatus(Status.PASSED);
break;
} else {
result.setStatus(Status.FAILED);
break;
}
}
return result;
}
ActivityFileValidationResult checkFileIdMesgType(FileIdMesg fileIdMesg) {
ActivityFileValidationResult result = new ActivityFileValidationResult("FileId Message Type Is Activity", Level.REQUIRED);
if (fileIdMesg.getType() == File.ACTIVITY) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
result.setDescription("Expected Type: Activity, actual Type: " + fileIdMesg.getType() + ".");
}
return result;
}
ActivityFileValidationResult checkFileIdMesgManufacturerId(FileIdMesg fileIdMesg) {
ActivityFileValidationResult result = new ActivityFileValidationResult("FileId Message Manufacturer Id Exists", Level.REQUIRED);
if (fileIdMesg.getManufacturer() == null) {
result.setStatus(Status.FAILED);
result.setDescription("Manufacturer Id is null.");
return result;
}
if (Manufacturer.getStringFromValue(fileIdMesg.getManufacturer()).equals("")) {
result.setStatus(Status.WARNING);
result.setDescription("Unknown Manufacturer Id " + fileIdMesg.getManufacturer() + ".");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkFileIdMesgTimeCreated(FileIdMesg fileIdMesg) {
ActivityFileValidationResult result = new ActivityFileValidationResult("FileId Message Time Created Exists", Level.REQUIRED);
if (fileIdMesg.getTimeCreated() == null) {
result.setStatus(Status.FAILED);
result.setDescription("Time Created is null.");
} else {
result.setStatus(Status.PASSED);
}
return result;
}
ActivityFileValidationResult checkActivityMesgExists(List activityMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Activity Message Exists", Level.REQUIRED);
if (activityMesgs.size() > 0) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
}
return result;
}
ActivityFileValidationResult checkActivityMesgTimestamp(ActivityMesg activityMesg) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Activity Message Timestamp Exists", Level.REQUIRED);
if (activityMesg.getTimestamp() == null) {
result.setStatus(Status.FAILED);
result.setDescription("Timestamp is null.");
} else {
result.setStatus(Status.PASSED);
}
return result;
}
ActivityFileValidationResult checkActivityMesgLocalTimeStamp(ActivityMesg activityMesg) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Activity Message Local Timestamp is Valid", Level.REQUIRED);
if (activityMesg.getLocalTimestamp() == null) {
result.setStatus(Status.FAILED);
result.setDescription("Local Timestamp is null.");
return result;
}
Long localTimestamp = activityMesg.getLocalTimestamp();
Long timestamp = activityMesg.getTimestamp().getTimestamp();
long timezoneOffset = localTimestamp - timestamp;
if ((timezoneOffset >= MIN_TIMEZONE_OFFSET_IN_SECONDS) && (timezoneOffset <= MAX_TIMEZONE_OFFSET_IN_SECONDS)) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
result.setDescription("Local Timestamp is not within the tolerated range of [-12,14] hours.");
}
return result;
}
ActivityFileValidationResult checkActivityMesgTotalTimerTime(ActivityMesg activityMesg, List sessionMesgs) {
return checkValidFieldSums(activityMesg, sessionMesgs, "Activity", "Session", "total_timer_time");
}
ActivityFileValidationResult checkActivityMesgSessionCount(ActivityMesg activityMesg, int numSessions) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Activity Message Session Count Is Equal To Actual Session Count", Level.OPTIONAL);
if (activityMesg.getNumSessions() == null) {
result.setStatus(Status.WARNING);
result.setDescription("Num Sessions is null.");
return result;
}
if (activityMesg.getNumSessions() != numSessions) {
result.setStatus(Status.WARNING);
result.setDescription("Num Sessions does not match the actual number of Sessions messages found in the file.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkSessionMesgsExists(int sessionMesgsSize) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Exists", Level.REQUIRED);
if (sessionMesgsSize > 0) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
}
return result;
}
ActivityFileValidationResult checkSessionMesgTimestamp(List sessionMesgs) {
return checkMesgValidTimestamp(sessionMesgs, "Session");
}
ActivityFileValidationResult checkSessionMesgStartTime(List sessionMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Start Time Is Valid", Level.REQUIRED);
if (sessionMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullTimestamp = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTimestamp() == null);
boolean hasNullStartTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getStartTime() == null);
if (hasNullTimestamp || hasNullStartTime) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null Start Time and/or a null Timestamp.");
return result;
}
for (SessionMesg sessionMesg : sessionMesgs) {
if (sessionMesg.getTimestamp().before(sessionMesg.getStartTime())) {
result.setStatus(Status.FAILED);
result.setDescription("A Session message Start Time is greater than its Timestamp.");
return result;
}
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkSessionMesgTotalTimerTimeAndTotalElapsedTime(List sessionMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Total Timer Time and Total Elapsed Time are Valid", Level.REQUIRED);
if (sessionMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullTimerTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTotalTimerTime() == null);
boolean hasNullTotalElapsedTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTotalElapsedTime() == null);
if (hasNullTimerTime || hasNullTotalElapsedTime) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null Start Time and/or a null Timestamp.");
return result;
}
for (SessionMesg sessionMesg : sessionMesgs) {
if (sessionMesg.getTotalElapsedTime() < sessionMesg.getTotalTimerTime()) {
result.setStatus(Status.FAILED);
result.setDescription("Session message Total Timer Time " + sessionMesg.getTotalTimerTime() + " > " + " Total Elapsed Time" + sessionMesg.getTotalElapsedTime() + ".");
return result;
}
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkSessionMesgFirstLapIndexAndNumLapsAreSequentialAndAbut(List sessionMesgs, List lapMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message First Lap Index and Num Laps are Valid", Level.REQUIRED);
if (sessionMesgs.size() == 0 || lapMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
result.setDescription("No Session messages exist and/or no Lap messages exist.");
return result;
}
boolean hasNullFirstLapIndex = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getFirstLapIndex() == null);
boolean hasNullNumLaps = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getNumLaps() == null);
if (hasNullFirstLapIndex || hasNullNumLaps) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null First Lap Index value and/or null Num Lap value.");
return result;
}
int expectedFirstLapIndex = 0;
for (SessionMesg sessionMesg : sessionMesgs) {
if (sessionMesg.getFirstLapIndex() != expectedFirstLapIndex) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contains an invlaid First Lap Index value.");
return result;
}
expectedFirstLapIndex += sessionMesg.getNumLaps();
}
if (expectedFirstLapIndex != lapMesgs.size()) {
result.setStatus(Status.FAILED);
result.setDescription("Sum of Session messages Num Laps values is not equal to total number of Lap messages found in the file.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkSessionMesgTotalTimerTime(List sessionMesgs, List lapMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Total Timer Time Is Equal To Sum of Lap Messages Total Timer Time", Level.REQUIRED);
if (sessionMesgs.size() == 0 || lapMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
result.setDescription("No Session messages exist and/or no Lap messages exist.");
return result;
}
boolean hasNullSessionTotalTimerTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTotalTimerTime() == null);
if (hasNullSessionTotalTimerTime) {
result.setStatus(Status.FAILED);
return result;
}
boolean hasNullFirstLapIndex = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getFirstLapIndex() == null);
boolean hasNullNumLaps = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getNumLaps() == null);
if (hasNullFirstLapIndex || hasNullNumLaps) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null First Lap Index value and/or null Num Lap value.");
return result;
}
for (SessionMesg session : sessionMesgs) {
List laps = lapMesgs.stream()
.skip(session.getFirstLapIndex())
.limit(session.getNumLaps())
.collect(Collectors.toList());
result = checkValidFieldSums(session, laps, "Session", "Lap", "total_timer_time");
if (result.getStatus() == Status.FAILED) {
return result;
}
}
return result;
}
ActivityFileValidationResult checkSessionMesgTotalElapsedTime(List sessionMesgs, List lapMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Total Elapsed Time Is Equal To Sum of Lap Messages Total Elapsed Time", Level.REQUIRED);
if (sessionMesgs.size() == 0 || lapMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
result.setDescription("No Session messages exist and/or no Lap messages exist.");
return result;
}
boolean hasNullSessionTotalElapsedTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTotalElapsedTime() == null);
if (hasNullSessionTotalElapsedTime) {
result.setStatus(Status.FAILED);
return result;
}
boolean hasNullFirstLapIndex = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getFirstLapIndex() == null);
boolean hasNullNumLaps = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getNumLaps() == null);
if (hasNullFirstLapIndex || hasNullNumLaps) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null First Lap Index value and/or null Num Lap value.");
return result;
}
for (SessionMesg session : sessionMesgs) {
List laps = lapMesgs.stream()
.skip(session.getFirstLapIndex())
.limit(session.getNumLaps())
.collect(Collectors.toList());
result = checkValidFieldSums(session, laps, "Session", "Lap", "total_elapsed_time");
if (result.getStatus() == Status.FAILED) {
return result;
}
}
return result;
}
ActivityFileValidationResult checkSessionMesgSport(List sessionMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Sport Exists", Level.REQUIRED);
Status status = checkFieldValuesAreValid(sessionMesgs, "sport");
if (status == Status.FAILED) {
result.setDescription("One or more Session messages contain a null Sport value.");
}
result.setStatus(status);
return result;
}
ActivityFileValidationResult checkSessionMesgSubSport(List sessionMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Session Message Sub Sport Exists", Level.OPTIONAL);
Status status = checkSubSportValuesAreValid(sessionMesgs);
if (status == Status.WARNING) {
result.setDescription("One or more Session messages contain a null Sub Sport value. Set Sub Sport value to 'generic' if unknown.");
}
result.setStatus(status);
return result;
}
ActivityFileValidationResult checkSessionMesgsAreSequentialAndAbut(List sessionMesgs) {
return checkMesgsAreSequentialAndAbut(sessionMesgs, "Session");
}
ActivityFileValidationResult checkSessionMesgValidMesgIndexes(List sessionMesgs) {
return checkValidMesgIndexes(sessionMesgs, "Session");
}
ActivityFileValidationResult checkLapMesgValidMesgIndexes(List lapMesgs) {
return checkValidMesgIndexes(lapMesgs, "Lap");
}
ActivityFileValidationResult checkLapMesgsExists(int lapMesgsSize) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Lap Message Exists", Level.REQUIRED);
if (lapMesgsSize > 0) {
result.setStatus(Status.PASSED);
} else {
result.setStatus(Status.FAILED);
}
return result;
}
ActivityFileValidationResult checkLapMesgValidStartTimeAndTimeStamp(List sessionMesgs, List lapMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Lap Message Start Time and Timestamp are Valid", Level.REQUIRED);
if (sessionMesgs.size() == 0 || lapMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullStartTime = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getStartTime() == null);
boolean hasNullTimestamp = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getTimestamp() == null);
if (hasNullStartTime || hasNullTimestamp) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null Start Time and/or null Timestamp.");
return result;
}
boolean hasNullFirstLapIndex = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getFirstLapIndex() == null);
boolean hasNullNumLaps = sessionMesgs.stream()
.anyMatch(sessionMesg -> sessionMesg.getNumLaps() == null);
if (hasNullFirstLapIndex || hasNullNumLaps) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session messages contain a null First Lap Index value and/or a null Num Lap value.");
return result;
}
boolean hasNullLapMesgStartTime = lapMesgs.stream()
.anyMatch(lapMesg -> lapMesg.getStartTime() == null);
boolean hasNullLapMesTimeStamp = lapMesgs.stream()
.anyMatch(lapMesg -> lapMesg.getTimestamp() == null);
if (hasNullLapMesgStartTime || hasNullLapMesTimeStamp) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Lap messages contain a null Start Time and/or a null Timestamp.");
return result;
}
for (SessionMesg session : sessionMesgs) {
List laps = lapMesgs.stream()
.skip(session.getFirstLapIndex())
.limit(session.getNumLaps())
.collect(Collectors.toList());
for (LapMesg lap : laps) {
boolean isStartTimeBefore = lap.getStartTime().before(session.getStartTime());
boolean isTimestampAfter = lap.getTimestamp().after(session.getTimestamp());
if (isStartTimeBefore || isTimestampAfter) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Lap messages contain an incorrect Start Time and/or an incorrect Timestamp relative to the Session message.");
return result;
}
}
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkLapMesgsAreSequentialAndAbut(List lapMesgs) {
return checkMesgsAreSequentialAndAbut(lapMesgs, "Lap");
}
ActivityFileValidationResult checkRecordMesgTimestampsAgainstSessionMesgTimes(List sessionMesgs, List recordMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Record Message Timestamps Fall Within Session Message Times", Level.REQUIRED);
if (sessionMesgs.size() == 0 || recordMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
DateTime firstSessionMesgStartTime = sessionMesgs.get(0).getStartTime();
DateTime lastSessionMesgTimeStamp = sessionMesgs.get(sessionMesgs.size() - 1).getTimestamp();
if ((firstSessionMesgStartTime == null) || (lastSessionMesgTimeStamp == null)) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Session Messages contain a null Start Time and/or a null Timestamp.");
return result;
}
DateTime firstRecordMesgTimestamp = recordMesgs.get(0).getTimestamp();
DateTime lastRecordMesgTimestamp = recordMesgs.get(recordMesgs.size() - 1).getTimestamp();
if ((firstRecordMesgTimestamp == null) || (lastRecordMesgTimestamp == null)) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Record messages contain a null Timestamp.");
return result;
}
if (firstRecordMesgTimestamp.getTimestamp() - firstSessionMesgStartTime.getTimestamp() < -1) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Record message Timestamps is earlier than Session Start Time.");
return result;
}
if (lastRecordMesgTimestamp.getTimestamp() - lastSessionMesgTimeStamp.getTimestamp() > 1) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Record message Timestamps is later than Session Timestamp.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkRecordMesgsAreChronological(List recordMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Record Messages Are in Chronological Ascending Order", Level.REQUIRED);
if (recordMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullTimestamp = recordMesgs.stream()
.anyMatch(recordMesg -> recordMesg.getTimestamp() == null);
if (hasNullTimestamp) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Record messages contain a null Timestamp.");
return result;
}
DateTime previousTimestamp = recordMesgs.get(0).getTimestamp();
for (RecordMesg recordMesg : recordMesgs) {
if (previousTimestamp.after(recordMesg.getTimestamp())) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Record messages are not sequential.");
return result;
}
previousTimestamp = recordMesg.getTimestamp();
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkDeviceInfoMesgValidTimestamp(List deviceInfoMesgs) {
return checkMesgValidTimestamp(deviceInfoMesgs, "Device Info");
}
ActivityFileValidationResult checkDeviceInfoMesgValidDeviceIndex(List deviceInfoMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Device Info Message Device Index is Valid", Level.REQUIRED);
if (deviceInfoMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullDeviceIndex = deviceInfoMesgs.stream()
.anyMatch(deviceInfoMesg -> deviceInfoMesg.getDeviceIndex() == null);
if (hasNullDeviceIndex) {
result.setStatus(Status.FAILED);
result.setDescription("One or more Device Info messages contain a null Device Index value.");
return result;
}
boolean hasCreator = deviceInfoMesgs.stream()
.anyMatch(deviceInfoMesg -> deviceInfoMesg.getDeviceIndex().equals(DeviceIndex.CREATOR));
if (!hasCreator) {
result.setStatus(Status.FAILED);
result.setDescription("At least one Device Info message Device Index field needs to be set to Creator.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkDeviceInfoMesgValidManufacturerIdExists(List deviceInfoMesgs) {
ActivityFileValidationResult result = new ActivityFileValidationResult("Device Info Message Manufacturer Id is Valid", Level.OPTIONAL);
if (deviceInfoMesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullManufacturer = deviceInfoMesgs.stream()
.anyMatch(deviceInfoMesg -> deviceInfoMesg.getManufacturer() == null);
if (hasNullManufacturer) {
result.setStatus(Status.WARNING);
result.setDescription("One or more Device Info messages contain a null Manufacturer Id.");
return result;
}
boolean hasInvalidManufacturer = deviceInfoMesgs.stream()
.anyMatch(deviceInfoMesg -> Manufacturer.getStringFromValue(deviceInfoMesg.getManufacturer()).equals(""));
if (hasInvalidManufacturer) {
result.setStatus(Status.WARNING);
result.setDescription("One or more Device Info messages contain an unknown Manufacturer Id.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
@SafeVarargs
@SuppressWarnings("varargs")
final boolean isEmpty(List extends Mesg>... lists) {
return Arrays.stream(lists).anyMatch(l -> l.size() == 0);
}
ActivityFileValidationResult checkValidMesgIndexes(List extends Mesg> mesgs, String mesgName) {
ActivityFileValidationResult result = new ActivityFileValidationResult(mesgName + " Message Valid Message Index", Level.REQUIRED);
if (mesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
for (int i = 0; i < mesgs.size(); i++) {
Integer messageIndex = mesgs.get(i).getFieldIntegerValue(254, 0, Fit.SUBFIELD_INDEX_MAIN_FIELD);
if (messageIndex == null) {
result.setStatus(Status.FAILED);
result.setDescription("One or more " + mesgName + " messages contain a null Message Index value.");
return result;
}
if (messageIndex != i) {
result.setStatus(Status.FAILED);
result.setDescription("Message Indexes are not sequential.");
return result;
}
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkMesgValidTimestamp(List extends Mesg> mesgs, String name) {
ActivityFileValidationResult result = new ActivityFileValidationResult(name + " Message Timestamps are Valid", Level.REQUIRED);
if (mesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
boolean hasNullTimestamp = mesgs.stream()
.anyMatch(mesg -> mesg.getFieldLongValue("timestamp") == null);
if (hasNullTimestamp) {
result.setStatus(Status.FAILED);
result.setDescription("One or more " + name + " messages contain a null Timestamp.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkValidFieldSums(Mesg mesg, List extends Mesg> mesgs, String mesgName, String mesgListName, String fieldName) {
ActivityFileValidationResult result = new ActivityFileValidationResult(mesgName + " Message " + fieldName + " is Equal to the Sum of " + mesgListName + " Messages " + fieldName + " Values", Level.REQUIRED);
if (mesgs.size() == 0) {
result.setStatus(Status.SKIPPED);
return result;
}
if ((mesg.getFieldFloatValue(fieldName) == null)) {
result.setStatus(Status.FAILED);
result.setDescription(mesgName + " Message contains a null " + fieldName);
return result;
}
boolean hasNullValue = mesgs.stream()
.anyMatch(m -> m.getFieldFloatValue(fieldName) == null);
if (hasNullValue) {
result.setStatus(Status.FAILED);
result.setDescription(mesgListName + "Message contains a null " + fieldName + " value.");
return result;
}
double sumOfFieldValues = mesgs.stream()
.mapToDouble(mesgList -> mesgList.getFieldFloatValue(fieldName))
.sum();
if (Math.abs(mesg.getFieldFloatValue(fieldName) - sumOfFieldValues) > 1) {
result.setStatus(Status.FAILED);
result.setDescription(mesgName + " message " + fieldName + " does not equal the sum of " + mesgListName + " messages " + fieldName + " values.");
return result;
}
result.setStatus(Status.PASSED);
return result;
}
ActivityFileValidationResult checkMesgsAreSequentialAndAbut(List extends Mesg> mesgs, String mesgName) {
ActivityFileValidationResult result = new ActivityFileValidationResult(mesgName + " Message Are Sequential and Abut", Level.REQUIRED);
if (mesgs.size() <= 1) {
result.setStatus(Status.SKIPPED);
result.setDescription("Check requires two or more " + mesgName + " messages, found " + mesgs.size() + ".");
return result;
}
boolean hasNullStartTime = mesgs.stream()
.anyMatch(mesg -> mesg.getFieldLongValue("start_time") == null);
boolean hasNullTotalElapsedTime = mesgs.stream()
.anyMatch(mesg -> mesg.getFieldLongValue("total_elapsed_time") == null);
if (hasNullStartTime || hasNullTotalElapsedTime) {
result.setStatus(Status.FAILED);
result.setDescription("One or more " + mesgName + " messages contain a null Start Time and/or a null Total Elapsed Time.");
return result;
}
long previousEndTime = calculateEndTime(mesgs.get(0));
for (Mesg mesg : mesgs.subList(1, mesgs.size())) {
long startTime = mesg.getFieldLongValue("start_time");
if (Math.abs(startTime - previousEndTime) > 2) {
result.setStatus(Status.FAILED);
result.setDescription(mesgName + " messages are not sequential, or do not abut.");
return result;
}
previousEndTime = calculateEndTime(mesg);
}
result.setStatus(Status.PASSED);
return result;
}
boolean anyMatchNull(List extends Mesg> mesgs, String fieldName) {
return mesgs.stream().anyMatch(mesg -> mesg.getFieldValue(fieldName) == null);
}
boolean allMatchNull(List extends Mesg> mesgs, String fieldName) {
return mesgs.stream().allMatch(mesg -> mesg.getFieldValue(fieldName) == null);
}
Status checkFieldValuesAreValid(List extends Mesg> mesgs, String fieldName) {
if (mesgs.size() == 0) {
return Status.SKIPPED;
}
return anyMatchNull(mesgs, fieldName) ? Status.FAILED : Status.PASSED;
}
Status checkSportValuesAreValid(List extends Mesg> mesgs) {
Status status = checkFieldValuesAreValid(mesgs, "sport");
return status == Status.FAILED ? Status.WARNING : status;
}
Status checkSubSportValuesAreValid(List extends Mesg> mesgs) {
Status status = checkFieldValuesAreValid(mesgs, "sub_sport");
return status == Status.FAILED ? Status.WARNING : status;
}
Long calculateEndTime(Mesg mesg){
long startTime = mesg.getFieldLongValue("start_time");
long totalElapsedTime = mesg.getFieldLongValue("total_elapsed_time");
return startTime + totalElapsedTime;
}
}