diff --git a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties.xlf b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties.xlf index fe93e12c1d2..c1febec818b 100644 --- a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties.xlf +++ b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties.xlf @@ -1,14 +1,14 @@ - +
- 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 - + - +
@@ -1085,6 +1085,9 @@ Medal Series + + Fleets can run in parallel + No color @@ -1577,14 +1580,14 @@ Ranking - - Total points + + Net points - - Competitor''s total points in the race + + Competitor''s net points in the race - - Competitor''s total points in the regatta + + Competitor''s net points in the regatta Show rank chart @@ -1748,8 +1751,8 @@ Points carried over from previous races. - - The total of all points of a competitor in the regatta.\nSorting based on rank, not points. + + The total net points of a competitor in the regatta.\nSorting based on rank, not points. Wind data @@ -1820,8 +1823,8 @@ Penalty/Redress - - Net points + + Total points Time point must be after start of tracking. @@ -2058,10 +2061,10 @@ Oscillating - Amplitude(in Grad) + Amplitude - Frequency(in 1/h) + Frequency Noise @@ -2465,9 +2468,6 @@ GET leaderboardgroups request failed with Server error. Please see server logs for more information. - - Leaderboard Groups: - Override existing data if names and ids match @@ -2739,7 +2739,7 @@ Do you really want to remove competitor {0}? - Filter competitors by name, boat class, sail number or ID + Filter competitors by name, boat class, sail number, search tag or ID Edit Competitor @@ -3333,8 +3333,8 @@ Average number of operations per message - - Show uncorrected net points + + Show uncorrected total points Set start time received @@ -4548,6 +4548,141 @@ {0,number,#,###}-{1,number,#,###} of over {2,number,#,###} + + Initializing... + + + Setting up connection + + + Connecting... + + + Reading data + + + Data-Transfer Complete, Initializing Import Operation + + + Initializing Import + + + Waiting for other data import operations to finish + + + Importing leaderboard groups + + + Importing wind tracks + + + Updating Event-LeaderboardGroup links + + + Strategy Simulator + + + Control settings + + + Wind display + + + Wind setup + + + Sailing setup + + + Choose a wind pattern + + + Oscillations + + + Oscillation with Gusts + + + Gusts + + + Measured + + + Base Bearing + + + Race Course Diff + + + Base Speed + + + Probability + + + Gust size + + + Average Speed + + + Speed Variance + + + Average Direction + + + Speed Left Side + + + Speed Middle + + + Speed Right Side + + + Current Speed + + + Current Bearing + + + % + + + per hr + + + Please select a valid wind pattern. + + + Downwind legs are NOT supported yet. Sorry about that :) + + + Race Leader + + + Opportunist Left + + + Opportunist Right + + + 1-Turner Left + + + 1-Turner Right + + + Current + + + out-of-bounds + + + ambiguous wind +
\ No newline at end of file diff --git a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties.xlf b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties.xlf index 816ca89ac7b..e8f79ce0e93 100644 --- a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties.xlf +++ b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties.xlf @@ -1,14 +1,14 @@ - +
- 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 - + - +
@@ -1443,6 +1443,10 @@ Medal Series メダルシリーズ + + Fleets can run in parallel + フリートを並列に実行可能 + No color 色なし @@ -2099,17 +2103,17 @@ Ranking ランキング - - Total points - 総得点 + + Net points + 総合得点 - - Competitor''s total points in the race - 当該レースの競技者総得点 + + Competitor''s net points in the race + 当該レースの競技者総合得点 - - Competitor''s total points in the regatta - 当該レガッタの競技者総得点 + + Competitor''s net points in the regatta + 当該レガッタの競技者総合得点 Show rank chart @@ -2327,9 +2331,9 @@ Points carried over from previous races. 以前のレースから持ち越された得点です。 - - The total of all points of a competitor in the regatta.\nSorting based on rank, not points. - レガッタにおける競技者のすべての得点の合計です。\nソートは得点ではなくランクに基づきます。 + + The total net points of a competitor in the regatta.\nSorting based on rank, not points. + レガッタにおける競技者の総合得点を合計したものです。\nソートは得点ではなくランクに基づきます。 Wind data @@ -2423,9 +2427,9 @@ Penalty/Redress ペナルティー/救済 - - Net points - 正味得点 + + Total points + 総得点 Time point must be after start of tracking. @@ -2740,12 +2744,12 @@ 振動中 - Amplitude(in Grad) - 振幅 (勾配) + Amplitude + 振幅 - Frequency(in 1/h) - 周期 (1/h) + Frequency + 周期 Noise @@ -2905,7 +2909,7 @@ Show only races of same day - 同一のレースのみ表示 + 同日のレースのみ表示 Show currently running races @@ -3283,10 +3287,6 @@ GET leaderboardgroups request failed with Server error. Please see server logs for more information. リーダーボード取得の要求がサーバエラーで失敗しました。詳細については、サーバログを参照してください。 - - Leaderboard Groups: - リーダーボードグループ: - Override existing data if names and ids match 名称と ID が一致する場合に既存データを上書き @@ -3561,7 +3561,7 @@ Total time traveled in this race, starting to measure when competitor passes start waypoint\n(not when the race starts) - 競技者がスタート変針店を通過したときに計測を開始した、このレースで移動した合計時間\n(レースのスタート時ではなく) + 競技者がスタート変針点を通過したときに計測を開始した、このレースで移動した合計時間\n(レースのスタート時ではなく) Total time traveled downwind in this race @@ -3648,8 +3648,8 @@ 競技者 {0} を削除しますか - Filter competitors by name, boat class, sail number or ID - 競技者をフィルタリングする名称、艇種、セールナンバー、または ID + Filter competitors by name, boat class, sail number, search tag or ID + 競技者をフィルタリングする名称、艇種、セールナンバー、検索タグ、または ID Edit Competitor @@ -4439,9 +4439,9 @@ Average number of operations per message メッセージごとの操作の平均数 - - Show uncorrected net points - 未修正正味得点表示 + + Show uncorrected total points + 未修正総得点表示 Set start time received @@ -6059,6 +6059,186 @@ {0,number,#,###}-{1,number,#,###} of over {2,number,#,###} {0,number,#,###}-{1,number,#,###} of over {2,number,#,###} + + Initializing... + 初期化しています... + + + Setting up connection + 接続の設定 + + + Connecting... + 接続しています... + + + Reading data + データ読込 + + + Data-Transfer Complete, Initializing Import Operation + データ転送が完了、インポート処理を初期化中 + + + Initializing Import + インポート初期化中 + + + Waiting for other data import operations to finish + 他のデータインポート処理が終了するのを待機中 + + + Importing leaderboard groups + リーダーボードグループのインポート + + + Importing wind tracks + 風追跡のインポート + + + Updating Event-LeaderboardGroup links + イベント/リーダーボードグループのリンク更新 + + + Strategy Simulator + 戦略シミュレータ + + + Control settings + 管理設定 + + + Wind display + 風表示 + + + Wind setup + 風設定 + + + Sailing setup + 帆走設定 + + + Choose a wind pattern + 風パターン選択 + + + Oscillations + 振れ + + + Oscillation with Gusts + 振れ (突風あり) + + + Gusts + 突風 + + + Measured + 計測 + + + Base Bearing + 基本方位 + + + Race Course Diff + レースコース差異 + + + Base Speed + 基本風速 + + + Probability + 確率 + + + Gust size + 突風規模 + + + Average Speed + 平均風速 + + + Speed Variance + 風速偏差 + + + Average Direction + 平均方向 + + + Speed Left Side + 風速 (左側) + + + Speed Middle + 風速 (中央) + + + Speed Right Side + 風速 (右側) + + + Current Speed + 現風速 + + + Current Bearing + 現方位 + + + % + % + + + per hr + /hr + + + Please select a valid wind pattern. + 有効な風パターンを選択してください。 + + + Downwind legs are NOT supported yet. Sorry about that :) + ダウンウィンドレグはまだサポートされていません (申し訳ありません)。 + + + Race Leader + レース首位艇 + + + Opportunist Left + オポチュニスト左 + + + Opportunist Right + オポチュニスト右 + + + 1-Turner Left + ワンターナー左 + + + 1-Turner Right + ワンターナー右 + + + Current + 現在 + + + out-of-bounds + 禁止区域 + + + ambiguous wind + 不明瞭な風 +
\ No newline at end of file diff --git a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties.xlf b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties.xlf index f32bf1875e4..d5ff0cea052 100644 --- a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties.xlf +++ b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties.xlf @@ -1,14 +1,14 @@ - +
- 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 - + - +
@@ -1443,6 +1443,10 @@ Medal Series Серия на медали + + Fleets can run in parallel + Флоты могут идти параллельно + No color Без цвета @@ -1645,7 +1649,7 @@ Mark/Gate name - Название отметки/ворот + Название отметки/ворот Error updating race course: {0} @@ -1737,7 +1741,7 @@ Instead of the usual count-back system used for low point schemes to break ties, competitors with equal point sums are compared by comparing the sum of the points they scored across all acts. If this still leaves a tie, the tie is broken by the last act. - Вместо обычной системы обратного отчета, используемой для разрешения равенства в схемах низких баллов, участники с равными суммами баллов сравниваются по сумме баллов, полученных за все акты. Если равенство сохраняется, оно разрешается по последнему акту. + Вместо обычной системы обратного отсчета, используемой для разрешения равенства в схемах низких баллов, участники с равными суммами баллов сравниваются по сумме баллов, полученных за все акты. Если равенство сохраняется, оно разрешается по последнему акту. High Point, winner gets 1 point @@ -2099,17 +2103,17 @@ Ranking Ранг - - Total points - Общий балл + + Net points + Чистый балл - - Competitor''s total points in the race - Общий балл участника в данной гонке + + Competitor''s net points in the race + Чистый балл участника в данной гонке - - Competitor''s total points in the regatta - Общий балл участника в данной регате + + Competitor''s net points in the regatta + Чистый балл участника в данной регате Show rank chart @@ -2327,9 +2331,9 @@ Points carried over from previous races. Баллы, перенесенные из других гонок. - - The total of all points of a competitor in the regatta.\nSorting based on rank, not points. - Общая сумма всех баллов участника в регате.\nСортировка по рангу, не по баллам. + + The total net points of a competitor in the regatta.\nSorting based on rank, not points. + Общая чистая сумма баллов участника в данной регате.\nСортировка по рангу, не по баллам. Wind data @@ -2423,9 +2427,9 @@ Penalty/Redress Взыскание/компенсация - - Net points - Точки сети + + Total points + Общий балл Time point must be after start of tracking. @@ -2740,12 +2744,12 @@ Колебания - Amplitude(in Grad) - Амплитуда (в град.) + Amplitude + Амплитуда - Frequency(in 1/h) - Частота (1/ч) + Frequency + Частота Noise @@ -3283,10 +3287,6 @@ GET leaderboardgroups request failed with Server error. Please see server logs for more information. Сбой GET-запроса групп таблиц лидеров с ошибкой сервера. Дополнительные сведения см. в журналах сервера. - - Leaderboard Groups: - Группы таблиц лидеров: - Override existing data if names and ids match Переопределять существующие данные при совпадении названий и идентификаторов @@ -3648,8 +3648,8 @@ Действительно удалить участника {0}? - Filter competitors by name, boat class, sail number or ID - Фильтрация участников по названию, классу лодки, номеру на парусе или идентификатору + Filter competitors by name, boat class, sail number, search tag or ID + Фильтрация участников по названию, классу лодки, номеру на парусе, тегу поиска или идентификатору Edit Competitor @@ -3921,7 +3921,7 @@ Search tag - Тег поиска + Тег поиска Do you really want to remove the leaderboard: ''{0}''? @@ -4053,7 +4053,7 @@ Course Status - Статус курса + Статус курса Can only be edited before starting tracking @@ -4425,7 +4425,7 @@ Time Comparison:\nRace is the leg duration measured from GPS, Omniscient is the course if one would know everything about the wind, Opportunist are courses if one decides to take a turn measuring the distance to the next mark, 1-Turners are the courses if one knows everything about the wind and just takes one turn - Сравнение времени:\nгонка — это продолжительность отрезка, измеренная по данным GPS, "Всезнайка" — курс при наличии всех сведений о ветре, "Авантюрист" — курсы при принятии решения сделать поворот для измерения дистанции до следующей отметки, "1 поворот" — курсы с одним поворотом при наличии всех сведений о ветре + Сравнение времени:\nгонка — это продолжительность отрезка, измеренная по данным GPS, "Информированный" — курс при наличии всех сведений о ветре, "Ситуативный" — курсы при принятии решения сделать поворот для измерения дистанции до следующей отметки, "1 поворот" — курсы с одним поворотом при наличии всех сведений о ветре Races scored @@ -4439,9 +4439,9 @@ Average number of operations per message Среднее число операций на сообщение - - Show uncorrected net points - Показать неисправленные точки сети + + Show uncorrected total points + Показать неисправленный общий балл Set start time received @@ -5997,67 +5997,247 @@ Manage2Sail Event - Событие Manage2Sail + Событие Manage2Sail There are no events yet. - Событий еще нет. + Событий еще нет. Please select a result import provider... - Выберите поставщика импорта результатов... + Выберите поставщика импорта результатов... Build version - Версия сборки + Версия сборки Server Information - Информация о сервере + Информация о сервере Server Configuration - Конфигурация сервера + Конфигурация сервера Standalone server - Изолированный сервер + Изолированный сервер Send... - Отправить... + Отправить... PassId - PassId + PassId Created at - Создано в + Создано в Logical timepoint - Логическая отметка времени + Логическая отметка времени Info - Инфо + Инфо Wind fixes - Замеры ветра + Замеры ветра 2x Mark - 2-отмет. + 2-отмет. {0,number,#,###}-{1,number,#,###} of {2,number,#,###} - {0,number,#,###}–{1,number,#,###} из {2,number,#,###} + {0,number,#,###}–{1,number,#,###} из {2,number,#,###} {0,number,#,###}-{1,number,#,###} of over {2,number,#,###} - {0,number,#,###}–{1,number,#,###} из {2,number,#,###} + {0,number,#,###}–{1,number,#,###} из {2,number,#,###} + + + Initializing... + Инициализация... + + + Setting up connection + Установка соединения + + + Connecting... + Соединение... + + + Reading data + Чтение данных + + + Data-Transfer Complete, Initializing Import Operation + Передача данных завершена, инициализация операции импорта + + + Initializing Import + Инициализация импорта + + + Waiting for other data import operations to finish + Ожидание завершения других операций импорта данных + + + Importing leaderboard groups + Импорт групп таблиц лидеров + + + Importing wind tracks + Импорт данных отслеживания ветра + + + Updating Event-LeaderboardGroup links + Обновление ссылок на группы таблиц лидеров событий + + + Strategy Simulator + Симулятор стратегии + + + Control settings + Настройки управления + + + Wind display + Отображение ветра + + + Wind setup + Настройка ветра + + + Sailing setup + Настройка плавания + + + Choose a wind pattern + Выберите ветровой режим + + + Oscillations + Колебания + + + Oscillation with Gusts + Колебания с порывами + + + Gusts + Порывы + + + Measured + Измеренная + + + Base Bearing + Базовый ветер + + + Race Course Diff + Отклонение от курса гонки + + + Base Speed + Базовая скорость + + + Probability + Вероятность + + + Gust size + Величина порыва + + + Average Speed + Средняя скорость + + + Speed Variance + Отклонение скорости + + + Average Direction + Среднее направление + + + Speed Left Side + Скорость слева + + + Speed Middle + Скорость в середине + + + Speed Right Side + Скорость справа + + + Current Speed + Текущая скорость + + + Current Bearing + Текущее направление + + + % + % + + + per hr + в ч. + + + Please select a valid wind pattern. + Выберите допустимый режим ветра. + + + Downwind legs are NOT supported yet. Sorry about that :) + Подветренные галсы пока что НЕ поддерживаются. Извините :) + + + Race Leader + Лидер гонки + + + Opportunist Left + Ситуативный левый + + + Opportunist Right + Ситуативный правый + + + 1-Turner Left + 1 поворот левый + + + 1-Turner Right + 1 поворот правый + + + Current + Текущий + + + out-of-bounds + вне ограничений + + + ambiguous wind + неопределенный ветер
diff --git a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties.xlf b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties.xlf index 29668e4636b..25d9b35b890 100644 --- a/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties.xlf +++ b/XLF/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties.xlf @@ -1,14 +1,14 @@ - +
- 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 - + - +
@@ -1443,6 +1443,10 @@ Medal Series 奖牌轮系列 + + Fleets can run in parallel + 船队可以并行航行 + No color 没有颜色 @@ -2099,17 +2103,17 @@ Ranking 排名 - - Total points - 总分 + + Net points + 净分 - - Competitor''s total points in the race - 参赛队在本轮次比赛中的总分 + + Competitor''s net points in the race + 参赛队在本轮次比赛中的净分 - - Competitor''s total points in the regatta - 参赛队在比赛中的总分 + + Competitor''s net points in the regatta + 参赛队在比赛中的净分 Show rank chart @@ -2327,9 +2331,9 @@ Points carried over from previous races. 前几个轮次的比赛中的得分。 - - The total of all points of a competitor in the regatta.\nSorting based on rank, not points. - 参赛队在比赛中的总得分。\n按名次(而不是得分)排序。 + + The total net points of a competitor in the regatta.\nSorting based on rank, not points. + 参赛队在比赛中的总净分。\n按名次(而不是得分)排序。 Wind data @@ -2423,9 +2427,9 @@ Penalty/Redress 惩罚/补偿 - - Net points - 净分 + + Total points + 总分 Time point must be after start of tracking. @@ -2740,12 +2744,12 @@ 振荡 - Amplitude(in Grad) - 振幅 (Grad) + Amplitude + 振幅 - Frequency(in 1/h) - 频率 (1/h) + Frequency + 频率 Noise @@ -3283,10 +3287,6 @@ GET leaderboardgroups request failed with Server error. Please see server logs for more information. 获取积分榜请求失败,服务器出错。请查看服务器日志,了解更多信息。 - - Leaderboard Groups: - 积分榜分组: - Override existing data if names and ids match 如果名称和编号匹配,则覆盖现有数据 @@ -3648,8 +3648,8 @@ 是否确定移除参赛队 {0}? - Filter competitors by name, boat class, sail number or ID - 按名称、船只级别或帆号或编号筛选参赛队 + Filter competitors by name, boat class, sail number, search tag or ID + 按名称、船只级别或帆号、搜索标记或编号筛选参赛队 Edit Competitor @@ -4439,9 +4439,9 @@ Average number of operations per message 各消息操作的平均数 - - Show uncorrected net points - 显示未更正的净分 + + Show uncorrected total points + 显示未更正的总分 Set start time received @@ -6059,6 +6059,186 @@ {0,number,#,###}-{1,number,#,###} of over {2,number,#,###} {0,number,#,###}-{1,number,#,###}/{2,number,#,###} 以上 + + Initializing... + 正在初始化... + + + Setting up connection + 正在设置连接 + + + Connecting... + 正在连接... + + + Reading data + 正在读取数据 + + + Data-Transfer Complete, Initializing Import Operation + 数据传输完成,正在初始化导入操作 + + + Initializing Import + 正在初始化导入 + + + Waiting for other data import operations to finish + 正在等待其他数据导入操作完成 + + + Importing leaderboard groups + 正在导入积分榜分组 + + + Importing wind tracks + 正在导入风力跟踪 + + + Updating Event-LeaderboardGroup links + 正在更新活动积分榜分组链接 + + + Strategy Simulator + 策略模拟器 + + + Control settings + 控制设置 + + + Wind display + 风力显示 + + + Wind setup + 风力设置 + + + Sailing setup + 航行设置 + + + Choose a wind pattern + 选择风力模式 + + + Oscillations + 振荡 + + + Oscillation with Gusts + 振荡和阵风 + + + Gusts + 阵风 + + + Measured + 测量 + + + Base Bearing + 基本航向角 + + + Race Course Diff + 比赛轮次场地差异 + + + Base Speed + 基本速度 + + + Probability + 概率 + + + Gust size + 阵风大小 + + + Average Speed + 平均速度 + + + Speed Variance + 速度偏差 + + + Average Direction + 平均方向 + + + Speed Left Side + 速度 - 左侧 + + + Speed Middle + 速度 - 中间 + + + Speed Right Side + 速度 - 右侧 + + + Current Speed + 当前速度 + + + Current Bearing + 当前航向角 + + + % + % + + + per hr + 每小时 + + + Please select a valid wind pattern. + 请输入有效的风力模式。 + + + Downwind legs are NOT supported yet. Sorry about that :) + 暂不支持顺风航段。抱歉 :) + + + Race Leader + 比赛轮次第一名 + + + Opportunist Left + 投机级 - 左 + + + Opportunist Right + 投机级 - 右 + + + 1-Turner Left + 一圈即成级 - 左 + + + 1-Turner Right + 一圈即成级 - 右 + + + Current + 水流 + + + out-of-bounds + 越界 + + + ambiguous wind + 方向不定的风 +
\ No newline at end of file diff --git a/configuration/csvToGpx.sh b/configuration/csvToGpx.sh new file mode 100644 index 00000000000..cee63c8ae73 --- /dev/null +++ b/configuration/csvToGpx.sh @@ -0,0 +1,30 @@ +#!/bin/sh +CSV=$1 +GPX=$2 +if [ "$GPX" = "" ]; then + GPX="${CSV}.gpx" +fi + +# TODO extract trackname from a CSV column +TRACKNAME=`head -n 2 "$CSV" | tail -n 1 | awk --field-separator , '{ print $1; }'` + +cat << "EOF" >"$GPX" + + + +EOF +echo " $TRACKNAME + " >>$GPX +cat "$CSV" | tail -n +2 | \ + awk --field-separator , '{ + split($2, dateWithColons, ":") + hoursOfDay=int($3/3600) + minutesOfHour=int(($3-hoursOfDay*3600)/60) + secondsOfMinute=$3-hoursOfDay*3600-minutesOfHour*60 + printf " %0.1f%0.2f\n", $6, $7, dateWithColons[3] "-" dateWithColons[2] "-" dateWithColons[1], hoursOfDay, minutesOfHour, secondsOfMinute, $11, $12/3600*1852; + }' >>"$GPX" +cat << "EOF" >>"$GPX" + + + +EOF diff --git a/configuration/releaseAndroidApps.sh b/configuration/releaseAndroidApps.sh new file mode 100644 index 00000000000..df83534a658 --- /dev/null +++ b/configuration/releaseAndroidApps.sh @@ -0,0 +1,128 @@ +#!/bin/sh +ANDROID_RELEASE_BRANCH=android-xmake-release +RELEASE_BRANCH=rel-1.4 +APP_MANIFESTS="mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml" +GIT_REMOTE=origin +UPDATE_ANDROID_MANIFEST_VERSIONS=1 +UPDATE_POM_VERSIONS=1 +PERFORM_GIT_OPERATIONS=1 + +increment_version_code() { + MANIFEST=$1 + echo "Incrementing version code and version name for $MANIFEST" + OLD_VERSION_CODE=`grep 'android:versionCode="[0-9]*"' $MANIFEST | sed -e 's/^.*android:versionCode="\([0-9]*\)".*$/\1/'` + NEW_VERSION_CODE=$(($OLD_VERSION_CODE + 1)) + echo $MANIFEST: OLD_VERSION_CODE is $OLD_VERSION_CODE, NEW_VERSION_CODE is $NEW_VERSION_CODE + OLD_MINOR_VERSION=`grep 'android:versionName="[0-9]*\.[0-9]*"' $MANIFEST | sed -e 's/^.*android:versionName="\([0-9]*\)\.\([0-9]*\)".*$/\2/'` + NEW_MINOR_VERSION=$((OLD_MINOR_VERSION + 1)) + echo $MANIFEST: OLD_MINOR_VERSION is $OLD_MINOR_VERSION, NEW_MINOR_VERSION is $NEW_MINOR_VERSION + sed --in-place -e "s/android:versionCode=\"$OLD_VERSION_CODE\"/android:versionCode=\"$NEW_VERSION_CODE\"/" -e "s/android:versionName=\"\([0-9]*\)\.$OLD_MINOR_VERSION\"/android:versionName=\"\1.$NEW_MINOR_VERSION\"/" "$MANIFEST" +} + +upgrade_pom_and_manifest_versions() { + POM=$1 + NEW_POM_VERSION=$2 + POM_WITH_PARENT_SPEC_REMOVED="${POM}.parentspecremoved" + PARENT_SPEC="${POM}.parentspec" + RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC="${POM}.restored" + POM_BACKUP="${POM}.bak" + cp "$POM" "$POM_BACKUP" + # find the area to remove + snip_area_start_line=`grep -n SNIP_MARKER_START pom.xml | sed -e 's/^\([0-9]*\):.*$/\1/'` + snip_area_end_line=`grep -n SNIP_MARKER_END pom.xml | sed -e 's/^\([0-9]*\):.*$/\1/'` + sed -e "$snip_area_start_line,$snip_area_end_line d" <"$POM" >"$POM_WITH_PARENT_SPEC_REMOVED" + sed -e "1,$((snip_area_start_line - 1)) d" -e "$((snip_area_end_line + 1)),$ d" <"$POM" >"$PARENT_SPEC" + cp "$POM_WITH_PARENT_SPEC_REMOVED" "$POM" + echo mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=$NEW_POM_VERSION + mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=$NEW_POM_VERSION + sed --in-place -e 's/$/\\/' "$PARENT_SPEC" + cat "$POM" | sed -e "$snip_area_start_line i \\`cat "$PARENT_SPEC"`" | sed -e "$snip_area_start_line,$snip_area_end_line s/\\\\$//" >"$RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC" + cp "$RESTORED_POM_WITH_NEW_VERSION_AND_PARENT_SPEC" "$POM" +} + +if [ $# -eq 0 ]; then + echo "buildAndUpdateProduct [-m -p -g -r ] CR-Id: " + echo "" + echo "Request a new Java Correction Workbench (JCWB) Correction Request ID at:" + echo " https://css.wdf.sap.corp/sap(bD1lbiZjPTAwMQ==)/bc/bsp/spn/jcwb/default.htm?newCMForProject=sapsailingcapture#" + echo "See more usage details in the sapsailing.com wiki at:" + echo " https://wiki.sapsailing.com/wiki/building-and-deploying#building-deploying-stopping-and-starting-server-instances_app-build-process-for-ios-and-android_xmake-build-for-android-apps" + echo "-m Disable upgrading the AndroidManifest.xml versionCode and versionName" + echo "-p Disable upgrading the pom.xml and MANIFEST.MF versions" + echo "-g Disable the final git push operation to refs/for/$RELEASE_BRANCH" + echo "-r The git remote; defaults to origin" + echo "" + echo "Example: $0 CR-Id: 012003146900000486712016" + exit 2 +fi + +options='mpgr:' +while getopts $options option +do + case $option in + m) UPDATE_ANDROID_MANIFEST_VERSIONS=0;; + p) UPDATE_POM_VERSIONS=0;; + g) PERFORM_GIT_OPERATIONS=0;; + r) GIT_REMOTE=$OPTARG;; + \?) echo "Invalid option" + exit 4;; + esac +done +shift $((OPTIND-1)) + +# change directory to the git root, assuming this script is in the configuration/ subfolder +GIT_DIR="`dirname \"$0\"`/.." +echo GIT_DIR: $GIT_DIR +JCWB_CHANGE_REQUEST_ID_GIT_TAG=$* +echo "Releasing based on Java Correction Workbench (JCWB) Correction Request ID tag $JCWB_CHANGE_REQUEST_ID_GIT_TAG" +cd "$GIT_DIR" + +git fetch $GIT_REMOTE +git checkout $ANDROID_RELEASE_BRANCH +git merge -m "Merging $GIT_REMOTE/$ANDROID_RELEASE_BRANCH" $GIT_REMOTE/$ANDROID_RELEASE_BRANCH +git fetch $GIT_REMOTE $RELEASE_BRANCH:$RELEASE_BRANCH +git merge -m "Merging $RELEASE_BRANCH into $ANDROID_RELEASE_BRANCH, probably incorporating version setting to -SNAPSHOT" $GIT_REMOTE/$RELEASE_BRANCH +git push $GIT_REMOTE $ANDROID_RELEASE_BRANCH:$ANDROID_RELEASE_BRANCH + +# Patch the AndroidManifest.xml files to upgrade the android:versionCode sequential counter relevant for the PlayStore +# and the android:versionName which is what the user sees and which we expect to follow a major.minor version scheme, +# where this script increments the minor version by one +if [ "$UPDATE_ANDROID_MANIFEST_VERSIONS" = "1" ]; then + for m in $APP_MANIFESTS; do + increment_version_code $m + done +fi + +# Determine the version in the root pom.xml; if a -SNAPSHOT version, simply remove the -SNAPSHOT; +# otherwise, increment micro version by one and use the Tycho version plugin to update; +# For this to work, the specification needs to be removed from the top-level pom which later +# needs to be re-activated before committing everything +OLD_POM_VERSION=`head -n 15 pom.xml | grep '.*' | sed -e 's/^.*\(.*\)<\/version>.*$/\1/'` +echo $OLD_POM_VERSION | grep -q -- "-SNAPSHOT" +if [ "$?" = "0" ]; then + # found a -SNAPSHOT identifier; assuming this was for the next release already; simply remove -SNAPSHOT + NEW_POM_VERSION=`echo $OLD_POM_VERSION | sed -e 's/-SNAPSHOT//'` +else + # no -SNAPSHOT found; increment the micro version by one + NEW_POM_VERSION=`echo $OLD_POM_VERSION | (IFS=. read major minor micro; echo ${major}.${minor}.$(($micro + 1)) )` +fi +echo OLD_POM_VERSION is $OLD_POM_VERSION, NEW_POM_VERSION is $NEW_POM_VERSION +if [ "$UPDATE_POM_VERSIONS" = "1" ]; then + upgrade_pom_and_manifest_versions pom.xml $NEW_POM_VERSION +fi + +# Now commit the version changes and amend the commit using the change request ID tag: +git commit -a -m "Upgraded Android apps from version $OLD_POM_VERSION to $NEW_POM_VERSION" +git commit --amend -m "`git show -s --pretty=format:%s%n%n%b` +$JCWB_CHANGE_REQUEST_ID_GIT_TAG" +if [ "$PERFORM_GIT_OPERATIONS" = "1" ]; then + git push $GIT_REMOTE $ANDROID_RELEASE_BRANCH:refs/for/$RELEASE_BRANCH +fi + +echo "Now go to https://git.wdf.sap.corp/#/dashboard/self and vote on your change, using the \"Reply\" button." +echo "Remove the pMiosVoter because it has nothing good to say on Android. Then \"Submit\" your change to merge it to $RELEASE_BRANCH." +echo "After successful merge, launch a customer stage build here: https://xmake-ldi.wdf.sap.corp:8443/view/SAPSail/job/sapsailingcapture-Release/" +echo "When done, create a BCP update ticket. See https://wiki.wdf.sap.corp/wiki/display/NAAS/Mobile+Patch+Releases (remove the saprole parameter from the URL)" +echo "Copy the description of, e.g., https://support.wdf.sap.corp/sap/support/message/1670304244 to start with and adjust versions and commit IDs." +echo "Make sure you have your Metaman stuff updated, particularly the Release Notes section." +echo "Then wait for feedback on the release build being ready for smoke testing, do the smoke tests and report back in BCP. That's it :-)" diff --git a/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/BarbadosResultSpreadsheet.java b/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/BarbadosResultSpreadsheet.java index baf6b5c0c80..3266f9e40fa 100755 --- a/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/BarbadosResultSpreadsheet.java +++ b/java/com.sap.sailing.barbados.resultimport/src/com/sap/sailing/barbados/resultimport/impl/BarbadosResultSpreadsheet.java @@ -41,9 +41,9 @@ public class BarbadosResultSpreadsheet { throw new IllegalArgumentException("Didn't find "+RACE_SCORE_COLUMN_NAME+" column"); } final int numberOfRaces = i-COLUMN_NUMBER_OF_FIRST_RACE_RANK; - final int columnNumberOfNetScore = i; - final int columnNumberOfTotalScore = columnNumberOfNetScore+1; - final int columnNumberOfFirstRaceScore = columnNumberOfTotalScore+2; + final int columnNumberOfTotalScore = i; + final int columnNumberOfNetScore = columnNumberOfTotalScore+1; + final int columnNumberOfFirstRaceScore = columnNumberOfNetScore+2; int rowIndex = 1; Row row; String iocCountryCode; @@ -53,8 +53,8 @@ public class BarbadosResultSpreadsheet { String helm = row.getCell(3).getStringCellValue(); String crew = row.getCell(4).getStringCellValue(); Iterable names = Arrays.asList(new String[] { helm, crew }); - double scoreAfterDiscarding = row.getCell(columnNumberOfTotalScore).getNumericCellValue(); - double netPointsBeforeDiscarding = row.getCell(columnNumberOfNetScore).getNumericCellValue(); + double scoreAfterDiscarding = row.getCell(columnNumberOfNetScore).getNumericCellValue(); + double totalPointsBeforeDiscarding = row.getCell(columnNumberOfTotalScore).getNumericCellValue(); List rankAndMaxPointsReasonAndPointsAndDiscarded = new ArrayList<>(); for (int raceNumber=0; raceNumber + + +
+ + H4sIAAAAAAAAAIVVTW8aMRC9+1fMpVJyiATptypXAiIIFfkQoEY9mt0BLBYb2SaU/vrO2Muuk0b04p1582zPjO23sx1iKWc8ijjO0cywsKb0fVxahykG3Y6HRQTEjfZBmQLnTj1jRbNPAJwQca+2KHkQE1zNjzuU9AU2xEzpanwj+QP3++0CnfiFykkemJ2wyK/D9ypoa1Slw1FmtugVzprjVtZfMcWVCkHJ+isGdu889hwqmUxgW/StCoNKeS/ZgmiKYYUYZBzFVBUoeaAVtjsMOlgnWzMuEOeKOaqt5IESVyW6hVWu5Mq0Wd3YrdJmisFpfEY3WJMnM5qP+5wjc9xzS86vuPJi9Dgb6t/naMS4Igqtd6fc5pFKJua5CUyDmpdSnaLfV+F/CUOipSkPy7Zt/60U7BJaus9bOnJ2v8ubBxHJKXmUe8aNyWt9URAT8tzibaPti/aQRztP/Tr1TTxpU86CQ7MK67Hpo9rTUwiSYTjhoA2cIqK/DB05UNWWra6c6NU6gNKO3evaXTjEP8jIezlCEyrMoA/yzlJBKuTgRzl06NcZ8knS9tasMuizvKXl4UC5sftFjlQVA19P3FUNdDuEWJdy7Mqf2laUBvgGu5a3e+d0oQzS86ro8T3jrCARGJv0SmoMPIPcAMd34KJDWuHDN+jCgY4yXKb+sZC8bp6P6sKdW4ZGWXphFhQxGmEJlpIiBFRIRtKqpzWJFbt8wEmnDgQlCp/zm4qW1n4ta/W6U2U2vTDUlDXfGMk+7xoRYEgkWXpY3lFX9nSHvUwI36AGa1hzVWxyRvSb6A9Njcqi0W+ij0hSF44D7YrqBa0OQB2JWY/oJfkHN7HeEzfmHSGwDhLYKrflHiS10uWb7W4IwAy4eMfbTuI9v2zzs6Xeb4faaL/Os4swJDwrhw48L4LdfzPvYzggmuYEeqasN3irJLjo1scCV9/rHS/F2NOfqNjQr2nsoTabLNpnHwUxS6iNQNLK9qTd5jU57plofwGIogiCRAcAAA== + + + + + +
+ + + Speed + + + Speed 10s before + + + Distance Traveled + + + Name + + + Leg Type + + + Sail Number + + + Year + + + Leg Number + + + Nationality + + + Acronym + + + Regatta + + + Course Area + + + Boat Class + + + Fleet + + + Race + + + Competitor + + + Boat + + + Team + + + Leaderboards + + + Races + + + Legs + + + GPS-Fixes + + + Mark Passings + + + Race Results + + + Races of Competitors + + + Leaderboard Group + + + Leaderboard + + + Leg + + + Mark Passing + + + Leg of competitor + + + GPS-Fix + + + Wind Strength in Beaufort + + + Calm + + + Light air + + + Light breeze + + + Gentle breeze + + + Moderate breeze + + + Fresh breeze + + + Strong breeze + + + High wind + + + Gale + + + Strong gale + + + Storm + + + Violent storm + + + Hurricane + + + Relative score in race (0 best; 1 worst) + + + Wind speed in Bft + + + Distance to start at start + + + Speed when starting + + + Speed 10s before start + + + Rank at First Mark + + + Number of Maneuvers + + + Number of Tacks + + + Number of Jibes + + + Number of Penalty Circles + + + Rank Gains or Losses + + + Distance to Starboard Side (% of Length) + + + Number of Podium Finishes + + + Number of Wins + + + Rank Gains or Losses (1st Mark -> Finish) + + + Is Tracked + + + Number of Competitor Fixes + + + Number of Mark Fixes + + +
+
\ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java index 717896ff6fe..964b04bdfce 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java @@ -66,7 +66,7 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti Leaderboard leaderboard = getLeaderboard(); final TimePoint now = MillisecondsTimePoint.now(); double competitorCount = Util.size(leaderboard.getCompetitors()); - double points = leaderboard.getNetPoints(competitor, raceColumn, now); + double points = leaderboard.getTotalPoints(competitor, raceColumn, now); double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ? competitorCount - points : points; final double result = relativeLowPoints / competitorCount; @@ -167,7 +167,7 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti public Boolean isPodiumFinish() { Leaderboard leaderboard = getLeaderboard(); final TimePoint now = MillisecondsTimePoint.now(); - double points = leaderboard.getNetPoints(competitor, raceColumn, now); + double points = leaderboard.getTotalPoints(competitor, raceColumn, now); if (leaderboard.getScoringScheme().isHigherBetter()) { double competitorCount = Util.size(leaderboard.getCompetitors()); return points >= (competitorCount - 2.05); @@ -180,7 +180,7 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti public Boolean isWin() { Leaderboard leaderboard = getLeaderboard(); final TimePoint now = MillisecondsTimePoint.now(); - double points = leaderboard.getNetPoints(competitor, raceColumn, now); + double points = leaderboard.getTotalPoints(competitor, raceColumn, now); if (leaderboard.getScoringScheme().isHigherBetter()) { double competitorCount = Util.size(leaderboard.getCompetitors()); return points >= (competitorCount - 0.05); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java index 4b38e380d31..3c5b4e48398 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractSpeedImpl.java @@ -16,7 +16,12 @@ public abstract class AbstractSpeedImpl implements Speed { @Override public Distance travel(TimePoint t1, TimePoint t2) { - return new NauticalMileDistance((t2.asMillis() - t1.asMillis()) / 1000. / 3600. * getKnots()); + return travel(t1.until(t2)); + } + + @Override + public Distance travel(Duration duration) { + return new NauticalMileDistance(duration.asHours() * getKnots()); } @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index 68cd340fb19..3c8d6f06877 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -65,6 +65,7 @@ public enum BoatClassMasterdata { LASER_INT ("Laser Int.", true, 4.19, 1.39, BoatHullType.MONOHULL, false, "Laser", "LSR"), LASER_SB3 ("Laser SB3", true, 6.15, 2.15, BoatHullType.MONOHULL, false, "LSB3", "SB20"), LAGO_26 ("Lago 26", true, 7.95, 2.50, BoatHullType.MONOHULL, true, "Lago26"), + M32 ("M32", false, 9.70, 5.50, BoatHullType.CATAMARAN, true, "M/32", "M32 Catamaran", "M/32 Catamaran"), MELGES_20 ("Melges 20", true, 6.10, 2.13, BoatHullType.MONOHULL, true, "Melges-20", "M20"), MELGES_24 ("Melges 24", true, 7.32, 2.50, BoatHullType.MONOHULL, true, "Melges-24", "M24"), MINI_TRANSAT ("Mini Transat 6.50", true, 6.50, 3.00, BoatHullType.MONOHULL, true, "Mini Transat"), diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java index 32f66797e7e..47908ed650b 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java @@ -40,7 +40,7 @@ public enum CourseDesignerMode { public static CourseDesignerMode[] validValues() { List validValues = new ArrayList(); for (CourseDesignerMode type : values()) { - if (type != UNKNOWN) { + if (type != UNKNOWN && type != ADMIN_CONSOLE) { validValues.add(type); } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java index db67c67a765..e0eeb351dfd 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java @@ -40,7 +40,7 @@ public enum DetailType implements Serializable { TIME_ON_TIME_FACTOR(4, DESCENDING), TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE(0, ASCENDING), TOTAL_DISTANCE_TRAVELED(0, ASCENDING), TOTAL_AVERAGE_SPEED_OVER_GROUND(2, DESCENDING), TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_CURRENT_RIDE_HEIGHT_IN_METERS(2, DESCENDING), - RACE_TOTAL_POINTS(2, ASCENDING), REGATTA_TOTAL_POINTS(2, ASCENDING), REGATTA_TOTAL_POINTS_SUM(2, ASCENDING), + RACE_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS_SUM(2, ASCENDING), RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(1, ASCENDING), RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START(1, ASCENDING), RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING), RACE_CALCULATED_TIME_TRAVELED(0, ASCENDING), RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD(0, ASCENDING), diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java index 50ab82ccfd3..74ead26ede8 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Speed.java @@ -60,6 +60,11 @@ public interface Speed extends Comparable, Serializable { public double getStatuteMilesPerHour() { return 0; } + + @Override + public Distance travel(Duration duration) { + return Distance.NULL; + } }; @Statistic(messageKey="", resultDecimals=2) @@ -85,4 +90,6 @@ public interface Speed extends Comparable, Serializable { * line" on a sphere) with this speed. */ Duration getDuration(Distance distance); + + Distance travel(Duration duration); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java index ff197288789..66093cb7e55 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AbstractLeaderboardDTO.java @@ -106,23 +106,23 @@ public abstract class AbstractLeaderboardDTO implements Serializable { public boolean scoredInMedalRace(CompetitorDTO competitor) { LeaderboardRowDTO row = rows.get(competitor); for (RaceColumnDTO race : races) { - if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).totalPoints > 0) { + if (race.isMedalRace() && row.fieldsByRaceColumnName.get(race.getRaceColumnName()).netPoints > 0) { return true; } } return false; } - public Double getNetPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) { - Double netPoints = null; - LeaderboardRowDTO row = rows.get(competitor); + public Double getTotalPoints(CompetitorDTO competitor, String nameOfLastRaceSoFar) { + Double totalPoints = null; + final LeaderboardRowDTO row = rows.get(competitor); if (row != null) { LeaderboardEntryDTO field = row.fieldsByRaceColumnName.get(nameOfLastRaceSoFar); - if (field != null && field.netPoints != null) { - netPoints = field.netPoints; + if (field != null && field.totalPoints != null) { + totalPoints = field.totalPoints; } } - return netPoints; + return totalPoints; } public boolean raceIsTracked(String raceColumnName) { diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java index 7df1e2403a2..1eab1c2ace2 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java @@ -30,15 +30,15 @@ public class LeaderboardEntryDTO implements Serializable { */ public MaxPointsReason reasonForMaxPoints; - public Double netPoints; - public Double netPointsUncorrected; + public Double totalPoints; + public Double totalPointsUncorrected; /** - * Tells if the net points have been overridden by a score correction. Can be used to render differently in editing environment. + * Tells if the total points have been overridden by a score correction. Can be used to render differently in editing environment. */ - public boolean netPointsCorrected; + public boolean totalPointsCorrected; - public Double totalPoints; + public Double netPoints; public boolean discarded; @@ -130,7 +130,7 @@ public class LeaderboardEntryDTO implements Serializable { public LeaderboardEntryDTO() { } public boolean hasScoreCorrection() { - return netPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE); + return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE); } @Override @@ -162,9 +162,9 @@ public class LeaderboardEntryDTO implements Serializable { .hashCode()); result = prime * result + ((fleet == null) ? 0 : fleet.hashCode()); result = prime * result + ((legDetails == null) ? 0 : legDetails.hashCode()); - result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); - result = prime * result + ((netPointsUncorrected == null) ? 0 : netPointsUncorrected.hashCode()); - result = prime * result + (netPointsCorrected ? 1231 : 1237); + result = prime * result + ((totalPoints == null) ? 0 : totalPoints.hashCode()); + result = prime * result + ((totalPointsUncorrected == null) ? 0 : totalPointsUncorrected.hashCode()); + result = prime * result + (totalPointsCorrected ? 1231 : 1237); result = prime * result + ((race == null) ? 0 : race.hashCode()); result = prime * result + ((reasonForMaxPoints == null) ? 0 : reasonForMaxPoints.hashCode()); result = prime @@ -176,7 +176,7 @@ public class LeaderboardEntryDTO implements Serializable { result = prime * result + ((startTack == null) ? 0 : startTack.hashCode()); result = prime * result + ((averageSamplingInterval == null) ? 0 : averageSamplingInterval.hashCode()); result = prime * result + ((timeSinceLastPositionFixInSeconds == null) ? 0 : timeSinceLastPositionFixInSeconds.hashCode()); - result = prime * result + ((totalPoints == null) ? 0 : totalPoints.hashCode()); + result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); result = prime * result + ((windwardDistanceToCompetitorFarthestAheadInMeters == null) ? 0 : windwardDistanceToCompetitorFarthestAheadInMeters @@ -250,17 +250,17 @@ public class LeaderboardEntryDTO implements Serializable { return false; } else if (!legDetails.equals(other.legDetails)) return false; - if (netPoints == null) { - if (other.netPoints != null) + if (totalPoints == null) { + if (other.totalPoints != null) return false; - } else if (!netPoints.equals(other.netPoints)) + } else if (!totalPoints.equals(other.totalPoints)) return false; - if (netPointsUncorrected == null) { - if (other.netPointsUncorrected != null) + if (totalPointsUncorrected == null) { + if (other.totalPointsUncorrected != null) return false; - } else if (!netPointsUncorrected.equals(other.netPointsUncorrected)) + } else if (!totalPointsUncorrected.equals(other.totalPointsUncorrected)) return false; - if (netPointsCorrected != other.netPointsCorrected) + if (totalPointsCorrected != other.totalPointsCorrected) return false; if (race == null) { if (other.race != null) @@ -292,10 +292,10 @@ public class LeaderboardEntryDTO implements Serializable { return false; } else if (!timeSinceLastPositionFixInSeconds.equals(other.timeSinceLastPositionFixInSeconds)) return false; - if (totalPoints == null) { - if (other.totalPoints != null) + if (netPoints == null) { + if (other.netPoints != null) return false; - } else if (!totalPoints.equals(other.totalPoints)) + } else if (!netPoints.equals(other.netPoints)) return false; if (windwardDistanceToCompetitorFarthestAheadInMeters == null) { if (other.windwardDistanceToCompetitorFarthestAheadInMeters != null) diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java index b7e0ddbdcc4..1f5036c8083 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java @@ -15,7 +15,7 @@ public class LeaderboardRowDTO implements Serializable { public CompetitorDTO competitor; public Map fieldsByRaceColumnName; public Double carriedPoints; - public Double totalPoints; + public Double netPoints; public Double totalTimeSailedInSeconds; public Double totalTimeSailedDownwindInSeconds; public Double maximumSpeedOverGroundInKnots; @@ -35,7 +35,7 @@ public class LeaderboardRowDTO implements Serializable { + ((maximumSpeedOverGroundInKnots == null) ? 0 : maximumSpeedOverGroundInKnots.hashCode()); result = prime * result + ((totalDistanceTraveledInMeters == null) ? 0 : totalDistanceTraveledInMeters.hashCode()); - result = prime * result + ((totalPoints == null) ? 0 : totalPoints.hashCode()); + result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); result = prime * result + ((totalTimeSailedDownwindInSeconds == null) ? 0 : totalTimeSailedDownwindInSeconds.hashCode()); result = prime * result + ((totalTimeSailedInSeconds == null) ? 0 : totalTimeSailedInSeconds.hashCode()); @@ -84,10 +84,10 @@ public class LeaderboardRowDTO implements Serializable { return false; } else if (!totalDistanceTraveledInMeters.equals(other.totalDistanceTraveledInMeters)) return false; - if (totalPoints == null) { - if (other.totalPoints != null) + if (netPoints == null) { + if (other.netPoints != null) return false; - } else if (!totalPoints.equals(other.totalPoints)) + } else if (!netPoints.equals(other.netPoints)) return false; if (totalTimeSailedDownwindInSeconds == null) { if (other.totalTimeSailedDownwindInSeconds != null) diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java index e0407e57277..f6017f50239 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceStartTimeInferenceTest.java @@ -1,6 +1,9 @@ package com.sap.sailing.domain.racelogtracking.test.impl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import java.util.Arrays; @@ -35,20 +38,23 @@ import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; import com.sap.sse.common.Duration; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest { private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er"); /** - * tests the precedence order described in {@link TrackedRaceImpl#updateStartAndEndOfTracking()} + * tests the precedence order described in {@link TrackedRaceImpl#updateStartAndEndOfTracking(boolean)} */ @Test public void testStartTimeInferencePrecedenceOrder() throws TransformationException, @@ -73,7 +79,7 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest { trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - //test inference via race start/end time in racelog + // test inference via race start/end time in racelog trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(regattaLog); MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(10000); TimePoint expectedStartOfTracking = startOfRaceInRaceLog.minus(Duration.ONE_MINUTE.times(TrackedRaceImpl.TRACKING_BUFFER_IN_MINUTES)); @@ -82,51 +88,272 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest { assertEquals(expectedStartOfTracking, trackedRace.getStartOfTracking()); assertEquals(expectedEndOfTracking, trackedRace.getEndOfTracking()); - - //test inference via manually set start/end of tracking + // test inference via manually set start/end of tracking setManualTrackingTimesOnTrackedRace(trackedRace, 30000, 40000); assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking()); assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking()); - //shouldn't change when setting time in raceLog again + // shouldn't change when setting time in raceLog again setStartAndEndOfRaceInRaceLog(100000, 200000); assertEquals(new MillisecondsTimePoint(30000), trackedRace.getStartOfTracking()); assertEquals(new MillisecondsTimePoint(40000), trackedRace.getEndOfTracking()); - //test inference via start/end of tracking in RaceLog + // test inference via start/end of tracking in RaceLog setStartAndEndOfTrackingInRaceLog(50000, 60000); MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(50000); MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(60000); assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); - //shouldn't change when setting time in raceLog again + // shouldn't change when setting time in raceLog again setStartAndEndOfRaceInRaceLog(300000, 400000); assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); - //shouldn't change when setting start/end of tracking + // shouldn't change when setting start/end of tracking setManualTrackingTimesOnTrackedRace(trackedRace, 500000, 600000); assertEquals(startOfTrackingInRacelog, trackedRace.getStartOfTracking()); assertEquals(endOfTrackingInRacelog, trackedRace.getEndOfTracking()); } + /** + * tests notification of first start time update through race log; see bug 3660 + */ + @Test + public void testStartTimeChangeNotificationForFirstUpdateThroughRaceLog() throws TransformationException, + NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null, + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark)); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2)); + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark), + new WaypointImpl(mark2) })); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 })); + TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ + null, /* endDate */null, null, null, "a", null)); + final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false, + OneDesignRankingMetric::new, mock(RaceLogResolver.class)); + trackedRace.attachRaceLog(raceLog); + final TimePoint[] oldAndNewStartTimeNotifiedByRace = new TimePoint[2]; + trackedRace.addListener(new AbstractRaceChangeListener() { + @Override + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + oldAndNewStartTimeNotifiedByRace[0] = oldStartOfRace; + oldAndNewStartTimeNotifiedByRace[1] = newStartOfRace; + } + }); + assertNull(trackedRace.getStartOfRace()); + final TimePoint newStartOfRace = MillisecondsTimePoint.now(); + raceLog.add(new RaceLogStartTimeEventImpl(newStartOfRace, author, 0, newStartOfRace)); + assertNull(oldAndNewStartTimeNotifiedByRace[0]); + assertEquals(newStartOfRace, oldAndNewStartTimeNotifiedByRace[1]); + assertEquals(newStartOfRace, trackedRace.getStartOfRace()); + } - public void setStartAndEndOfTrackingInRaceLog(int startTime, int endTime){ + /** + * tests notification of first start time update through race log; see bug 3660 + */ + @Test + public void testStartTimeChangeNotificationForFirstUpdateThroughStartMarkPassing() throws TransformationException, + NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null, + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark)); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2)); + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark), + new WaypointImpl(mark2) })); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 })); + TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ + null, /* endDate */null, null, null, "a", null)); + final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false, + OneDesignRankingMetric::new, mock(RaceLogResolver.class)); + final TimePoint[] oldAndNewStartTimeNotifiedByRace = new TimePoint[2]; + trackedRace.addListener(new AbstractRaceChangeListener() { + @Override + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + oldAndNewStartTimeNotifiedByRace[0] = oldStartOfRace; + oldAndNewStartTimeNotifiedByRace[1] = newStartOfRace; + } + }); + assertNull(trackedRace.getStartOfRace()); + final TimePoint newStartOfRace = MillisecondsTimePoint.now(); + trackedRace.updateMarkPassings(comp, Arrays.asList(new MarkPassingImpl(newStartOfRace, Util.get(course.getWaypoints(), 0), comp), + new MarkPassingImpl(newStartOfRace.plus(Duration.ONE_MINUTE), Util.get(course.getWaypoints(), 1), comp))); + assertNull(oldAndNewStartTimeNotifiedByRace[0]); + assertEquals(newStartOfRace, oldAndNewStartTimeNotifiedByRace[1]); + assertEquals(newStartOfRace, trackedRace.getStartOfRace()); + } + + + + + /** + * tests the precedence order described in {@link TrackedRaceImpl#updateStartAndEndOfTracking(boolean)} + */ + @Test + public void testStartAndEndTrackingTimeInferencePrecedenceOrderTriggersListener() throws TransformationException, + NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null, + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark)); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2)); + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark), + new WaypointImpl(mark2) })); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 })); + TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ + null, /* endDate */null, null, null, "a", null)); + assertTrue(regatta.getRegatta().useStartTimeInference()); + final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false, + OneDesignRankingMetric::new, mock(RaceLogResolver.class)); + trackedRace.attachRaceLog(raceLog); + trackedRace.attachRegattaLog(regattaLog); + final TimePoint[] newStartAndEndOfTrackingNotifiedByRace = new TimePoint[2]; + trackedRace.addListener(new AbstractRaceChangeListener() { + @Override + public void startOfTrackingChanged(TimePoint startOfTracking) { + newStartAndEndOfTrackingNotifiedByRace[0] = startOfTracking; + } + @Override + public void endOfTrackingChanged(TimePoint endOfTracking) { + newStartAndEndOfTrackingNotifiedByRace[1] = endOfTracking; + } + }); + assertNull(trackedRace.getStartOfTracking()); + assertNull(trackedRace.getEndOfTracking()); + + // test inference from implicit startOfRace change through start mark passing update + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + final TimePoint startMarkPassingTimePoint = MillisecondsTimePoint.now(); + trackedRace.updateMarkPassings(comp, Collections.singleton(new MarkPassingImpl(startMarkPassingTimePoint, trackedRace.getRace().getCourse().getFirstWaypoint(), comp))); + assertNotNull(newStartAndEndOfTrackingNotifiedByRace[0]); + assertTrue(trackedRace.getStartOfTracking().before(startMarkPassingTimePoint)); + assertTrue(trackedRace.getStartOfTracking().after(new MillisecondsTimePoint(123456))); + + // test inference from finished time change by new blue flag down event + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + final TimePoint finishedTimePoint = MillisecondsTimePoint.now(); + raceLog.add(new RaceLogRaceStatusEventImpl(finishedTimePoint, finishedTimePoint, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED)); + assertNotNull(newStartAndEndOfTrackingNotifiedByRace[1]); + assertTrue(trackedRace.getEndOfTracking().after(finishedTimePoint)); + + // verify that setting a start and finished time through the race log adjusts the start/end of tracking times + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + setStartAndEndOfRaceInRaceLog(123456, 234567); + assertNotNull(trackedRace.getStartOfTracking()); + assertNotNull(trackedRace.getEndOfTracking()); + assertTrue(trackedRace.getStartOfTracking().before(new MillisecondsTimePoint(123456))); + assertTrue(trackedRace.getEndOfTracking().after(new MillisecondsTimePoint(234567))); + + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + setManualTrackingTimesOnTrackedRace(trackedRace, 1111, 2222); // wrong values; race log should take precedence when available + // assert that the event listener was triggered + assertEquals(new MillisecondsTimePoint(1111), newStartAndEndOfTrackingNotifiedByRace[0]); + assertEquals(new MillisecondsTimePoint(2222), newStartAndEndOfTrackingNotifiedByRace[1]); + // test values set immediately + assertEquals(new MillisecondsTimePoint(1111), trackedRace.getStartOfTracking()); + assertEquals(new MillisecondsTimePoint(2222), trackedRace.getEndOfTracking()); + + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + setStartAndEndOfTrackingInRaceLog(10000, 20000); // correct values, taking precedence + // assert that the event listener was triggered + assertEquals(new MillisecondsTimePoint(10000), newStartAndEndOfTrackingNotifiedByRace[0]); + assertEquals(new MillisecondsTimePoint(20000), newStartAndEndOfTrackingNotifiedByRace[1]); + // test inference via racelog + assertEquals(new MillisecondsTimePoint(10000), trackedRace.getStartOfTracking()); + assertEquals(new MillisecondsTimePoint(20000), trackedRace.getEndOfTracking()); + + // shouldn't change anymore when setting explicitly because race log takes precedence + newStartAndEndOfTrackingNotifiedByRace[0] = null; + newStartAndEndOfTrackingNotifiedByRace[1] = null; + setManualTrackingTimesOnTrackedRace(trackedRace, 1111, 2222); + assertNull(newStartAndEndOfTrackingNotifiedByRace[0]); + assertNull(newStartAndEndOfTrackingNotifiedByRace[1]); + assertEquals(new MillisecondsTimePoint(10000), trackedRace.getStartOfTracking()); + assertEquals(new MillisecondsTimePoint(20000), trackedRace.getEndOfTracking()); + + // test inference when setting null in RaceLog; RaceLog should still take precedence with its null values + newStartAndEndOfTrackingNotifiedByRace[0] = MillisecondsTimePoint.now(); + newStartAndEndOfTrackingNotifiedByRace[1] = MillisecondsTimePoint.now(); + raceLog.add(new RaceLogStartOfTrackingEventImpl(null, author, 0)); + raceLog.add(new RaceLogEndOfTrackingEventImpl(null, author, 0)); + assertNull(trackedRace.getStartOfTracking()); + assertNull(trackedRace.getEndOfTracking()); + assertNull(newStartAndEndOfTrackingNotifiedByRace[0]); + assertNull(newStartAndEndOfTrackingNotifiedByRace[1]); + } + + /** + * tests notification of first start time update through race log; see bug 3660 + */ + @Test + public void testStartAndEndOfTrackingTimeChangeNotificationForFirstUpdateThroughRaceLog() throws TransformationException, + NoCorrespondingServiceRegisteredException, InterruptedException { + Competitor comp2 = DomainFactory.INSTANCE.getOrCreateCompetitor("comp2", "comp2", null, null, null, null, null, + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); + Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(1), author, new MillisecondsTimePoint(1), 0, mark)); + regattaLog.add(new RegattaLogDefineMarkEventImpl(new MillisecondsTimePoint(2), author, new MillisecondsTimePoint(1), 0, mark2)); + Course course = new CourseImpl("course", Arrays.asList(new Waypoint[] { new WaypointImpl(mark), + new WaypointImpl(mark2) })); + RaceDefinition race = new RaceDefinitionImpl("race", course, boatClass, Arrays.asList(new Competitor[] { comp, comp2 })); + TrackedRegatta regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ + null, /* endDate */null, null, null, "a", null)); + final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false, + OneDesignRankingMetric::new, mock(RaceLogResolver.class)); + trackedRace.attachRaceLog(raceLog); + final TimePoint[] oldAndNewStartTimeNotifiedByRace = new TimePoint[2]; + trackedRace.addListener(new AbstractRaceChangeListener() { + @Override + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + oldAndNewStartTimeNotifiedByRace[0] = oldStartOfRace; + oldAndNewStartTimeNotifiedByRace[1] = newStartOfRace; + } + }); + assertNull(trackedRace.getStartOfRace()); + final TimePoint newStartOfRace = MillisecondsTimePoint.now(); + raceLog.add(new RaceLogStartTimeEventImpl(newStartOfRace, author, 0, newStartOfRace)); + assertNull(oldAndNewStartTimeNotifiedByRace[0]); + assertEquals(newStartOfRace, oldAndNewStartTimeNotifiedByRace[1]); + assertEquals(newStartOfRace, trackedRace.getStartOfRace()); + } + + + + + + + + public void setStartAndEndOfTrackingInRaceLog(int startTime, int endTime) { MillisecondsTimePoint startOfTrackingInRacelog = new MillisecondsTimePoint(startTime); MillisecondsTimePoint endOfTrackingInRacelog = new MillisecondsTimePoint(endTime); raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTrackingInRacelog, author, 0)); raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTrackingInRacelog, author, 0)); } - public void setStartAndEndOfRaceInRaceLog(int startTime, int endTime){ + public void setStartAndEndOfRaceInRaceLog(int startTime, int endTime) { MillisecondsTimePoint startTimeInRaceLog2 = new MillisecondsTimePoint(startTime); MillisecondsTimePoint endTimeInRaceLog2 = new MillisecondsTimePoint(endTime); raceLog.add(new RaceLogStartTimeEventImpl(startTimeInRaceLog2, author, 0, startTimeInRaceLog2)); raceLog.add(new RaceLogRaceStatusEventImpl(endTimeInRaceLog2, endTimeInRaceLog2, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED)); } - public void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, int startTime, int endTime){ + public void setManualTrackingTimesOnTrackedRace(DynamicTrackedRace trackedRace, int startTime, int endTime) { trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(startTime)); trackedRace.setEndOfTrackingReceived(new MillisecondsTimePoint(endTime)); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties similarity index 100% rename from java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_en.properties rename to java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages.properties diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/XLF/TrackingStringMessages.properties.xlf b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/XLF/TrackingStringMessages.properties.xlf new file mode 100644 index 00000000000..0cb6d014ff7 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/XLF/TrackingStringMessages.properties.xlf @@ -0,0 +1,52 @@ + + + +
+ + H4sIAAAAAAAAAF2RTU/DMAyG7/0VvnGZEGekHgYSUiVgY+3gnLXeajWNo9jbqBD/naQtbOLm+PXH+zgaTN2ROxTuRGqU2D1xyKs5C5c07DlkUhv3tnnkBlfhnYR0G2zFGzyQKIal5C+mQ5BjQBj4CK05IWiL8HX3DUvvgZyosRabW6hadJDmjQV7tpbPaeM0HjhAbanuRtWS6+AoSY5jA0hvgvqWXZzOEOb1YCQ7o625x4rzjymKBRmtyq1gkLzU2AfxmcxkxjWBqbmWllNqkm2c6SL7Ce0wwlYtyXO0khf7Ec9EzICmSb40atAbssATUYMnqhHOpO34TvjGpvLhcoYFCPXeDjPrDuMREpIkN/fZ7shDha7BkKcQdIwz432pHLCYxlT4qclSw+5GLzdPCwfUBXiLRvB36X1qL1jyuftyDu9n/D/p+hzJwDqSxm/2/tX0mJfLNZSROOE/JHuTnEnMFa6kQ6v/S2HOZz95rIJYdwIAAA== + + + + + +
+ + + Tracking Invitation for + + + Make sure you have the {0} App installed. Then scan the following QRCode or click the link using your smartphone to register as + + + Welcome to + + + Start iOS App + + + Start Android App + + + If you are reading this mail on the device with the App already installed, simply click below to start: + + + buoy tender + + + If don't have the App yet, please install: + + + Install iOS App + + + Install Android App + + + SAP Sailing Buoy Pinger + + + SAP Sail InSight + + +
+
\ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index a5502f25989..ec57b4c8b83 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -134,13 +134,13 @@ public class RaceLogRaceTracker implements RaceTracker, TrackingDataLoader { @Override public void visit(RaceLogStartTimeEvent event) { - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override public void visit(RaceLogRaceStatusEvent event) { if (event.getNextStatus().equals(RaceLogRaceStatus.FINISHED)){ - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } } }; @@ -252,13 +252,13 @@ public class RaceLogRaceTracker implements RaceTracker, TrackingDataLoader { private void onStartOfTrackingEvent(RaceLogStartOfTrackingEvent event) { if (trackedRace != null) { - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } } private void onEndOfTrackingEvent(RaceLogEndOfTrackingEvent event) { if (trackedRace != null) { - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/DurationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/DurationTest.java index 5994a51a7c4..6ca3f6ec4a4 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/DurationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/DurationTest.java @@ -72,7 +72,7 @@ public class DurationTest { assertEquals("1 day", fmt.format(oneDay)); Duration someMinutes = Duration.ONE_MINUTE.times(5).plus(Duration.ONE_SECOND.times(40)); assertEquals(0, someMinutes.asDays(), 0); - assertEquals(0, someMinutes.asHours(), 0); + assertEquals(0, someMinutes.asHours(), (5.+40./60.)/60.); assertEquals(5.6, someMinutes.asMinutes(), 0.1); assertEquals(340, someMinutes.asSeconds(), 0.1); assertEquals(340000, someMinutes.asMillis()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NonGwtUrlHelperTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NonGwtUrlHelperTest.java new file mode 100644 index 00000000000..8ca6a8f896d --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/NonGwtUrlHelperTest.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.common.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.sap.sse.common.util.UrlHelper; +import com.sap.sse.util.impl.NonGwtUrlHelper; + +public class NonGwtUrlHelperTest { + + private final UrlHelper urlHelper = NonGwtUrlHelper.INSTANCE; + + @Test + public void testEncodeQueryStringSimple() { + testCase("TestTest", "TestTest"); + } + + @Test + public void testEncodeQueryStringWithWhitespace() { + testCase("Test%20Test", "Test Test"); + } + + @Test + public void testEncodeQueryStringWithSlash() { + testCase("Test%2FTest", "Test/Test"); + } + + @Test + public void testEncodeQueryStringWithPlusSign() { + testCase("Test%2BTest", "Test+Test"); + } + + @Test + public void testEncodeQueryStringWithAmpersand() { + testCase("Test%26Test", "Test&Test"); + } + + @Test + public void testEncodeQueryStringComplex() { + testCase("Test%20%2B%20Test%20%2F%20Test%20%26%20Test", "Test + Test / Test & Test"); + } + + private void testCase(String expectedEncodedString, String decodedString) { + assertEquals(expectedEncodedString, urlHelper.encodeQueryString(decodedString)); + } + +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java index 9167a804a4d..267a6dc571b 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImplWhiteBoxTest.java @@ -1,23 +1,44 @@ package com.sap.sailing.domain.markpassingcalculation.impl; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.StreamSupport; import org.junit.Before; import org.junit.Test; -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; +import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.ReadonlyRacingProcedureFactory; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.markpassingcalculation.Candidate; +import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; +import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl.AbsoluteGeometricDistanceAndSignedProjectedDistanceToStartLine; +import com.sap.sailing.domain.test.TrackBasedTest; import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; public class CandidateFinderImplWhiteBoxTest { private static class CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances extends CandidateFinderImpl { @@ -33,15 +54,15 @@ public class CandidateFinderImplWhiteBoxTest { } private CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances finder; + private Competitor competitor; + private DynamicTrackedRace trackedRace; + private TimePoint now; @Before public void setUp() { - DynamicTrackedRace trackedRace = mock(DynamicTrackedRace.class); - final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("29er"); - RaceDefinition race = mock(RaceDefinition.class); - when(trackedRace.getRace()).thenReturn(race); - when(race.getBoatClass()).thenReturn(boatClass); - when(race.getCompetitors()).thenReturn(Collections.emptySet()); + now = MillisecondsTimePoint.now(); + competitor = TrackBasedTest.createCompetitor("Competitor"); + trackedRace = TrackBasedTest.createTestTrackedRace("Test Regatta", "Test Race", "505", Collections.singleton(competitor), now, /* useMarkPassingCalculator */ true); finder = new CandidateFinderWithPublicGetProbabilityOfStartBasedOnOtherCompetitorsStartLineDistances(trackedRace); } @@ -53,6 +74,122 @@ public class CandidateFinderImplWhiteBoxTest { assertEquals(1.0, probability, 0.00001); } + @Test + public void testTwoCandidatesForStartLinePassingAtStartOfRace() throws NoSuchFieldException, IllegalAccessException, InterruptedException { + CandidateFinder finder = getCandidateFinderOfTrackedRace(); + trackedRace.setStartOfTrackingReceived(now.minus(Duration.ONE_MINUTE.times(10))); + trackedRace.setStartTimeReceived(now.plus(Duration.ONE_MINUTE)); + final TimePoint timeForStartLinePassing = trackedRace.getStartOfRace(); + createStartLinePassing(timeForStartLinePassing); + waitForMarkPassingCalculatorToFinishComputing(); + final Pair, Iterable> candidates = finder.getAllCandidates(competitor); + assertNotNull(candidates); + assertFalse(Util.isEmpty(candidates.getA())); + assertTrue(Util.isEmpty(candidates.getB())); + final Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + // expecting one distance and one XTE candidate + assertEquals(2, StreamSupport.stream(candidates.getA().spliterator(), /* parallel */ false).filter(c -> c.getWaypoint() == startWaypoint).count()); + } + + private void waitForMarkPassingCalculatorToFinishComputing() throws InterruptedException, NoSuchFieldException, IllegalAccessException { + final MarkPassingCalculator markPassingCalculator = getMarkPassingCalculatorOfTrackedRace(); + markPassingCalculator.stop(); + markPassingCalculator.waitUntilStopped(10000); + } + + @Test + public void testNoCandidatesForStartLinePassingFiveMinutesBeforeStartOfRace() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, InterruptedException { + CandidateFinder finder = getCandidateFinderOfTrackedRace(); + trackedRace.setStartOfTrackingReceived(now.minus(Duration.ONE_MINUTE.times(10))); + trackedRace.setStartTimeReceived(now.plus(Duration.ONE_MINUTE)); + final TimePoint timeForStartLinePassing = trackedRace.getStartOfRace().minus(Duration.ONE_MINUTE.times(5)); + createStartLinePassing(timeForStartLinePassing); + waitForMarkPassingCalculatorToFinishComputing(); + final Pair, Iterable> candidates = finder.getAllCandidates(competitor); + assertNotNull(candidates); + assertTrue(Util.isEmpty(candidates.getA())); + assertTrue(Util.isEmpty(candidates.getB())); + final Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + // expecting one distance and one XTE candidate + assertEquals(0, StreamSupport.stream(candidates.getA().spliterator(), /* parallel */ false).filter(c -> c.getWaypoint() == startWaypoint).count()); + } + + @Test + public void testTwoCandidatesForStartLinePassingOneMinuteBeforerBlueFlagDown() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, InterruptedException { + CandidateFinder finder = getCandidateFinderOfTrackedRace(); + final RaceLogImpl raceLog = new RaceLogImpl("RaceLog"); + trackedRace.attachRaceLog(raceLog); + trackedRace.setStartOfTrackingReceived(now.minus(Duration.ONE_MINUTE.times(10))); + trackedRace.setStartTimeReceived(now.plus(Duration.ONE_MINUTE)); + // blue flag goes down again one minute after start + final TimePoint finishedTime = now.plus(Duration.ONE_MINUTE.times(2)); + new RaceStateImpl(/* raceLogResolver */ null, raceLog, new LogEventAuthorImpl("Me", 0), new ReadonlyRacingProcedureFactory( + new EmptyRegattaConfiguration())).setFinishedTime(finishedTime); + // pass the line a bit after the candidate finder is expected to ignore candidates (5min after blue flag down) + final TimePoint timeForStartLinePassing = finishedTime.minus(Duration.ONE_MINUTE); + createStartLinePassing(timeForStartLinePassing); + waitForMarkPassingCalculatorToFinishComputing(); + final Pair, Iterable> candidates = finder.getAllCandidates(competitor); + assertNotNull(candidates); + assertFalse(Util.isEmpty(candidates.getA())); + assertTrue(Util.isEmpty(candidates.getB())); + final Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + // expecting one distance and one XTE candidate + assertEquals(2, StreamSupport.stream(candidates.getA().spliterator(), /* parallel */ false).filter(c -> c.getWaypoint() == startWaypoint).count()); + } + + @Test + public void testNoCandidatesForStartLinePassingOneMinuteAfterBlueFlagDown() throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException, InterruptedException { + CandidateFinder finder = getCandidateFinderOfTrackedRace(); + final RaceLogImpl raceLog = new RaceLogImpl("RaceLog"); + trackedRace.attachRaceLog(raceLog); + trackedRace.setStartOfTrackingReceived(now.minus(Duration.ONE_MINUTE.times(10))); + trackedRace.setStartTimeReceived(now.plus(Duration.ONE_MINUTE)); + // blue flag goes down again one minute after start + final TimePoint finishedTime = now.plus(Duration.ONE_MINUTE.times(2)); + new RaceStateImpl(/* raceLogResolver */ null, raceLog, new LogEventAuthorImpl("Me", 0), new ReadonlyRacingProcedureFactory( + new EmptyRegattaConfiguration())).setFinishedTime(finishedTime); + // pass the line a bit after the candidate finder is expected to ignore candidates (5min after blue flag down) + final TimePoint timeForStartLinePassing = finishedTime.plus(Duration.ONE_MINUTE.times(6)); + createStartLinePassing(timeForStartLinePassing); + waitForMarkPassingCalculatorToFinishComputing(); + final Pair, Iterable> candidates = finder.getAllCandidates(competitor); + assertNotNull(candidates); + assertTrue(Util.isEmpty(candidates.getA())); + assertTrue(Util.isEmpty(candidates.getB())); + final Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + // expecting one distance and one XTE candidate + assertEquals(0, StreamSupport.stream(candidates.getA().spliterator(), /* parallel */ false).filter(c -> c.getWaypoint() == startWaypoint).count()); + } + + private CandidateFinder getCandidateFinderOfTrackedRace() throws NoSuchFieldException, IllegalAccessException { + final MarkPassingCalculator markPassingCalculator = getMarkPassingCalculatorOfTrackedRace(); + final Field finderField = MarkPassingCalculator.class.getDeclaredField("finder"); + finderField.setAccessible(true); + CandidateFinder finder = (CandidateFinder) finderField.get(markPassingCalculator); + return finder; + } + + private MarkPassingCalculator getMarkPassingCalculatorOfTrackedRace() + throws NoSuchFieldException, IllegalAccessException { + final Field markPassingCalculatorField = TrackedRaceImpl.class.getDeclaredField("markPassingCalculator"); + markPassingCalculatorField.setAccessible(true); + final MarkPassingCalculator markPassingCalculator = (MarkPassingCalculator) markPassingCalculatorField.get(trackedRace); + return markPassingCalculator; + } + + private void createStartLinePassing(final TimePoint timeForStartLinePassing) { + final Waypoint startWaypoint = trackedRace.getRace().getCourse().getFirstWaypoint(); + final SpeedWithBearing startLineCrossingSpeed = new KnotSpeedWithBearingImpl(4, new DegreeBearingImpl(325)); // a classical port tack start + final Position startLinePosition = trackedRace.getApproximatePosition(startWaypoint, timeForStartLinePassing); + final Position beforeStartPosition = startLinePosition.translateGreatCircle(startLineCrossingSpeed.getBearing().reverse(), startLineCrossingSpeed.travel(Duration.ONE_SECOND)); + final Position atStartPosition = startLinePosition; + final Position afterStartPosition = startLinePosition.translateGreatCircle(startLineCrossingSpeed.getBearing(), startLineCrossingSpeed.travel(Duration.ONE_SECOND)); + trackedRace.getTrack(competitor).add(new GPSFixMovingImpl(beforeStartPosition, timeForStartLinePassing.minus(Duration.ONE_SECOND), startLineCrossingSpeed)); + trackedRace.getTrack(competitor).add(new GPSFixMovingImpl(atStartPosition, timeForStartLinePassing, startLineCrossingSpeed)); + trackedRace.getTrack(competitor).add(new GPSFixMovingImpl(afterStartPosition, timeForStartLinePassing.plus(Duration.ONE_SECOND), startLineCrossingSpeed)); + } + @Test public void testHighProbabilityForCloseProximityAndNoLine() { List distancesToStartLineOfOtherCompetitors = new ArrayList<>(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DistanceCacheInvalidationAfterLoadingFinishedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DistanceCacheInvalidationAfterLoadingFinishedTest.java new file mode 100644 index 00000000000..4974542ecc3 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/DistanceCacheInvalidationAfterLoadingFinishedTest.java @@ -0,0 +1,60 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * See bug 3648: When a race is loading, cache invalidation is suspended which also affects the distance cache. + * However, for the distance cache, during resuming caching operations there was a cache invalidation missing. + * This tests, failing before the fix for bug 3648, ensures that this type of invalidation actually happens. + * + * @author Axel Uhl (d043530) + * + */ +public class DistanceCacheInvalidationAfterLoadingFinishedTest extends TrackBasedTest { + private Competitor competitor; + private DynamicTrackedRace trackedRace; + + @Before + public void setUp() { + competitor = createCompetitor("Test Competitor"); + trackedRace = createTestTrackedRace("Test Regatta", "Test Race", "505", Collections.singleton(competitor), MillisecondsTimePoint.now(), /* useMarkPassingCalculator */ false); + } + + @Test + public void testDistanceCalculationWhileLoading() { + final TimePoint now = MillisecondsTimePoint.now(); + trackedRace.updateMarkPassings(competitor, Collections.singleton(new MarkPassingImpl(now, trackedRace.getRace().getCourse().getFirstWaypoint(), competitor))); + // no distance traveled yet because there is no fix yet; this may be cached for now + assertEquals(Distance.NULL, trackedRace.getDistanceTraveled(competitor, now)); + trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, /* progress */ 0.1)); + assertEquals(Distance.NULL, trackedRace.getDistanceTraveled(competitor, now.plus(Duration.ONE_SECOND.times(2)))); + final Position startPos = new DegreePosition(0, 0); + final DegreeBearingImpl bearing = new DegreeBearingImpl(10); + final KnotSpeedWithBearingImpl speed = new KnotSpeedWithBearingImpl(10, bearing); + trackedRace.recordFix(competitor, new GPSFixMovingImpl(startPos, now, speed)); + final Distance distance = speed.travel(Duration.ONE_SECOND); + trackedRace.recordFix(competitor, new GPSFixMovingImpl(startPos.translateGreatCircle(bearing, distance), now.plus(Duration.ONE_SECOND), speed)); + trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, /* progress */ 1.0)); + assertEquals(distance.getMeters(), trackedRace.getDistanceTraveled(competitor, now.plus(Duration.ONE_SECOND.times(2))).getMeters(), 0.000001); // ask 1s after the second fix + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index 94f362b2691..1dbd3f63b85 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -120,7 +120,7 @@ public class LeaderboardCourseChangeTest { DomainFactory baseDomainFactory = new DomainFactoryImpl((srlid)->null); LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false, - trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false); + trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false); assertEquals(6, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator() .next().legDetails.size()); @@ -129,7 +129,7 @@ public class LeaderboardCourseChangeTest { course.removeWaypoint(3); leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint, raceColumnNames, false, trackedRegattaRegistry, - baseDomainFactory, /* fillNetPointsUncorrected */ false); + baseDomainFactory, /* fillTotalPointsUncorrected */ false); assertEquals(4, leaderboardDTO.rows.values().iterator().next().fieldsByRaceColumnName.values().iterator() .next().legDetails.size()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java index 3d496e1d729..d534bcdc2af 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardForKielWeekTest.java @@ -48,24 +48,24 @@ public class LeaderboardForKielWeekTest extends OnlineTracTracBasedTest { loadRace("event_20110609_KielerWoch-505_Race_2.txt", "event_20110609_KielerWoch-505_Race_2.mtb"); // 505 Race 2 Competitor kevin = getCompetitorByName("Kevin"); RaceColumn column = leaderboard.addRace(getTrackedRace(), "Test Race 1", /* medalRace */false); - assertEquals(17, leaderboard.getTotalPoints(kevin, now), 0.000000001); + assertEquals(17, leaderboard.getNetPoints(kevin, now), 0.000000001); Util.Pair key = new Util.Pair(kevin, column); - assertEquals(17, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); - assertEquals(17, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); + assertEquals(17, leaderboard.getContent(now).get(key).getNetPoints(), 0.000000001); + assertEquals(17, leaderboard.getEntry(kevin, column, now).getNetPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_Race_3.txt", "event_20110609_KielerWoch-505_Race_3.mtb"); // 505 Race 3 column = leaderboard.addRace(getTrackedRace(), "Test Race 2", /* medalRace */false); key = new Util.Pair(kevin, column); // In Race 3, Hasso ranked 31st - assertEquals(29, leaderboard.getTotalPoints(kevin, now), 0.000000001); - assertEquals(12, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); - assertEquals(12, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); + assertEquals(29, leaderboard.getNetPoints(kevin, now), 0.000000001); + assertEquals(12, leaderboard.getContent(now).get(key).getNetPoints(), 0.000000001); + assertEquals(12, leaderboard.getEntry(kevin, column, now).getNetPoints(), 0.000000001); loadRace("event_20110609_KielerWoch-505_race_4.txt", "event_20110609_KielerWoch-505_race_4.mtb"); // 505 Race 4 column = leaderboard.addRace(getTrackedRace(), "Test Race 3", /* medalRace */false); key = new Util.Pair(kevin, column); // now the first race is discarded because Kevin ranked worst compared to the other two; in race 4 he ranked 10th - assertEquals(22, leaderboard.getTotalPoints(kevin, now), 0.000000001); - assertEquals(10, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); - assertEquals(10, leaderboard.getEntry(kevin, column, now).getTotalPoints(), 0.000000001); + assertEquals(22, leaderboard.getNetPoints(kevin, now), 0.000000001); + assertEquals(10, leaderboard.getContent(now).get(key).getNetPoints(), 0.000000001); + assertEquals(10, leaderboard.getEntry(kevin, column, now).getNetPoints(), 0.000000001); } private void loadRace(String paramsFile, String storedDataFile) throws MalformedURLException, IOException, InterruptedException, diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java index 0642dcb7ba1..0721b20c5ce 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardOfflineTest.java @@ -146,17 +146,17 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { InterruptedException { } }; - LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertNotNull(leaderboardDTO); - assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // assert it's cached + assertSame(leaderboardDTO, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false)); // assert it's cached leaderboard.getRaceColumnByName(columnName).releaseTrackedRace(defaultFleet); // this should clear the cache - LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + LeaderboardDTO leaderboardDTO2 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertNotSame(leaderboardDTO, leaderboardDTO2); - assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // and cached again + assertSame(leaderboardDTO2, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false)); // and cached again leaderboard.getRaceColumnByName(columnName).setTrackedRace(defaultFleet, testRaces.iterator().next()); // clear cache again; requires listener(s) to still be attached - LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + LeaderboardDTO leaderboardDTO3 = leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertNotSame(leaderboardDTO2, leaderboardDTO3); - assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false)); // and cached again + assertSame(leaderboardDTO3, leaderboard.getLeaderboardDTO(now, emptySet, /* addOverallDetails */ true, trackedRegattaRegistry, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false)); // and cached again } @Test @@ -187,11 +187,11 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { } } leaderboard.getScoreCorrection().setMaxPointsReason(competitor, bestScoringRaceColumn, MaxPointsReason.DSQ); - // assert that best scoring but now disqualified race gets max net points because of disqualification: + // assert that best scoring but now disqualified race gets max total points because of disqualification: assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1, - leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); + leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getTotalPoints(), 0.000000001); // now assert that it gets discarded because due to disqualification it scores worse than all others: - assertEquals(0, leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getTotalPoints(), 0.000000001); + assertEquals(0, leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); } @Test @@ -207,15 +207,15 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { FlexibleLeaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl( new int[] { 2 }), new LowPoint(), null); leaderboard.addRace(testRace, "R1", /* medalRace */false); - assertEquals(1., leaderboard.getTotalPoints(competitor, now), 0.00000001); - assertEquals(1., leaderboard.getTotalPoints(c2, now), 0.00000001); - assertEquals(1., leaderboard.getTotalPoints(c3, now), 0.00000001); + assertEquals(1., leaderboard.getNetPoints(competitor, now), 0.00000001); + assertEquals(1., leaderboard.getNetPoints(c2, now), 0.00000001); + assertEquals(1., leaderboard.getNetPoints(c3, now), 0.00000001); leaderboard.setCarriedPoints(competitor, 100); leaderboard.setCarriedPoints(c2, 50); leaderboard.setCarriedPoints(c3, 25); - assertEquals(101., leaderboard.getTotalPoints(competitor, now), 0.00000001); - assertEquals(51., leaderboard.getTotalPoints(c2, now), 0.00000001); - assertEquals(26., leaderboard.getTotalPoints(c3, now), 0.00000001); + assertEquals(101., leaderboard.getNetPoints(competitor, now), 0.00000001); + assertEquals(51., leaderboard.getNetPoints(c2, now), 0.00000001); + assertEquals(26., leaderboard.getNetPoints(c3, now), 0.00000001); List sortedCompetitors = leaderboard.getCompetitorsFromBestToWorst(now); assertSame(c3, sortedCompetitors.get(0)); assertSame(c2, sortedCompetitors.get(1)); @@ -238,9 +238,9 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { RaceColumn r2 = leaderboard.addRaceColumn("R2", /* medalRace */false); leaderboard.getScoreCorrection().setMaxPointsReason(competitor, r2, MaxPointsReason.DND); // non-discardable disqualification // assert that max points were given before discarding... - assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); - // ...and after... assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getTotalPoints(), 0.000000001); + // ...and after... + assertEquals(4.0, leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); // ...because it's not discarded assertFalse(leaderboard.getEntry(competitor, r2, MillisecondsTimePoint.now()).isDiscarded()); } @@ -273,12 +273,12 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { } } leaderboard.getScoreCorrection().setMaxPointsReason(competitor, bestScoringRaceColumn, MaxPointsReason.DND); - // assert that best scoring but now disqualified race gets max net points because of disqualification: - assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1, - leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); - // now assert that it does not get discarded because it's a DND + // assert that best scoring but now disqualified race gets max total points because of disqualification: assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1, leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getTotalPoints(), 0.000000001); + // now assert that it does not get discarded because it's a DND + assertEquals(Util.size(bestScoringRaceColumn.getTrackedRace(defaultFleet).getRace().getCompetitors())+1, + leaderboard.getEntry(competitor, bestScoringRaceColumn, MillisecondsTimePoint.now()).getNetPoints(), 0.000000001); } protected void testLeaderboard(int numberOfStartedRaces, int numberOfNotStartedRaces, int firstDiscardingThreshold, @@ -314,7 +314,7 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { } Collections.sort(ranksOfNonMedalStartedRaces); int carryInt = (carry == null ? 0 : carry); - int totalPoints = carryInt; + int netPoints = carryInt; int medalRacePoints = getMedalRacePoints(competitor, now, defaultFleet); for (TrackedRace race : testRaces) { RaceColumn raceColumn = raceColumnsInLeaderboard.get(race); @@ -324,9 +324,9 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { assertEquals(rank, leaderboard.getTrackedRank(competitor, raceColumn, now)); assertEquals(rank, leaderboard.getContent(now).get(key).getTrackedRank()); assertEquals(rank, leaderboard.getEntry(competitor, raceColumn, now).getTrackedRank()); - assertEquals(rank, leaderboard.getNetPoints(competitor, raceColumn, now), 0.000000001); - assertEquals(rank, leaderboard.getContent(now).get(key).getNetPoints(), 0.000000001); - assertEquals(rank, leaderboard.getEntry(competitor, raceColumn, now).getNetPoints(), 0.000000001); + assertEquals(rank, leaderboard.getTotalPoints(competitor, raceColumn, now), 0.000000001); + assertEquals(rank, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); + assertEquals(rank, leaderboard.getEntry(competitor, raceColumn, now).getTotalPoints(), 0.000000001); // One race is discarded because four races were started, and for [3-6) one race can be discarded. // The discarded race is the worst of those started, so the one with rank 4. int expectedNumberOfDiscardedRaces = @@ -334,21 +334,21 @@ public class LeaderboardOfflineTest extends AbstractLeaderboardTest { numberOfRacesToCompareToDiscardThresholds < secondDiscardingThreshold ? 1 : 2; boolean discarded = ranksOfNonMedalStartedRaces.indexOf(rank) >= ranksOfNonMedalStartedRaces.size()-expectedNumberOfDiscardedRaces; int expected = discarded ? 0 : rank==medalRacePoints?2*rank:rank; - assertEquals(expected, leaderboard.getTotalPoints(competitor, raceColumn, now), 0.000000001); - assertEquals(expected, leaderboard.getContent(now).get(key).getTotalPoints(), 0.000000001); - assertEquals(expected, leaderboard.getEntry(competitor, raceColumn, now).getTotalPoints(), 0.000000001); - totalPoints += leaderboard.getContent(now).get(key).getTotalPoints(); + assertEquals(expected, leaderboard.getNetPoints(competitor, raceColumn, now), 0.000000001); + assertEquals(expected, leaderboard.getContent(now).get(key).getNetPoints(), 0.000000001); + assertEquals(expected, leaderboard.getEntry(competitor, raceColumn, now).getNetPoints(), 0.000000001); + netPoints += leaderboard.getContent(now).get(key).getNetPoints(); } else { assertEquals(0, leaderboard.getTrackedRank(competitor, raceColumn, now)); - assertNull(leaderboard.getNetPoints(competitor, raceColumn, now)); + assertNull(leaderboard.getTotalPoints(competitor, raceColumn, now)); assertEquals(0, leaderboard.getContent(now).get(key).getTrackedRank()); - assertNull(leaderboard.getContent(now).get(key).getNetPoints()); + assertNull(leaderboard.getContent(now).get(key).getTotalPoints()); assertEquals(0, leaderboard.getEntry(competitor, raceColumn, now).getTrackedRank()); - assertNull(leaderboard.getEntry(competitor, raceColumn, now).getNetPoints()); - // no increment on totalPoints + assertNull(leaderboard.getEntry(competitor, raceColumn, now).getTotalPoints()); + // no increment on netPoints } } - assertEquals(totalPoints, leaderboard.getTotalPoints(competitor, now), 0.000000001); + assertEquals(netPoints, leaderboard.getNetPoints(competitor, now), 0.000000001); } private int getMedalRacePoints(Competitor competitor, TimePoint at, Fleet fleet) throws NoWindException { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index f9deaa0607c..6c8d7fb4e58 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -110,7 +110,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { /** * Regarding bug 912, test adding a disqualification in the middle, with a high-point scoring scheme, and check that * all competitors ranked worse advance by one, including getting more points due to the high-point scoring - * scheme. Note that this does not test the total points given for those competitors. + * scheme. Note that this does not test the net points given for those competitors. */ @Test public void testOneStartedRaceWithDifferentScoresAndDisqualificationUsingHighPointScoringScheme() throws NoWindException { @@ -136,13 +136,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(competitors.subList(6, 10), rankedCompetitors.subList(5, 9)); assertEquals(competitors.get(5), rankedCompetitors.get(9)); - // Now test the total points and make sure the other competitors advanced by one, too - assertEquals(0, leaderboard.getTotalPoints(competitors.get(5), f1Column, now), 0.000000001); + // Now test the net points and make sure the other competitors advanced by one, too + assertEquals(0, leaderboard.getNetPoints(competitors.get(5), f1Column, now), 0.000000001); for (int i=0; i<5; i++) { - assertEquals(10-i, leaderboard.getTotalPoints(competitors.get(i), f1Column, now), 0.000000001); + assertEquals(10-i, leaderboard.getNetPoints(competitors.get(i), f1Column, now), 0.000000001); } for (int i=6; i<10; i++) { - assertEquals(10-(i-1), leaderboard.getTotalPoints(competitors.get(i), f1Column, now), 0.000000001); + assertEquals(10-(i-1), leaderboard.getNetPoints(competitors.get(i), f1Column, now), 0.000000001); } } @@ -170,15 +170,15 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { TrackedRace q2Default = new MockedTrackedRaceWithStartTimeAndRanks(now, reverseCompetitors); q1Column.setTrackedRace(q1Column.getFleetByName("Default"), q1Default); q2Column.setTrackedRace(q2Column.getFleetByName("Default"), q2Default); - assertEquals(Double.valueOf(competitors.size()), leaderboard.getTotalPoints(competitors.get(0), q1Column, later)); - assertEquals(factor * Double.valueOf(1), leaderboard.getTotalPoints(competitors.get(0), q2Column, later), 0.000000001); - assertEquals(Double.valueOf(factor*1.0+competitors.size()), leaderboard.getTotalPoints(competitors.get(0), later), 0.000000001); + assertEquals(Double.valueOf(competitors.size()), leaderboard.getNetPoints(competitors.get(0), q1Column, later)); + assertEquals(factor * Double.valueOf(1), leaderboard.getNetPoints(competitors.get(0), q2Column, later), 0.000000001); + assertEquals(Double.valueOf(factor*1.0+competitors.size()), leaderboard.getNetPoints(competitors.get(0), later), 0.000000001); } /** * Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one * column only one group has raced (expressed by a mocked TrackedRace attached to the column). Ensure that the column does - * count for the total points sum but that the competitor ordering is controlled by the number of races. + * count for the net points sum but that the competitor ordering is controlled by the number of races. */ @Test public void testUnorderedGroupsWithOneGroupNotHavingRacedInAColumn() throws NoWindException { @@ -203,8 +203,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (int i=0; i<5; i++) { assertTrue(competitors.get(i) == rankedCompetitors.get(2*i) || competitors.get(i) == rankedCompetitors.get(2*i+1)); assertTrue(competitors.get(i+5) == rankedCompetitors.get(2*i) || competitors.get(i+5) == rankedCompetitors.get(2*i+1)); - assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001); - assertEquals((double) (i+1), leaderboard.getTotalPoints(competitors.get(i+5), later), 0.0000000001); + assertEquals((double) (i+1), leaderboard.getNetPoints(competitors.get(i), later), 0.000000001); + assertEquals((double) (i+1), leaderboard.getNetPoints(competitors.get(i+5), later), 0.0000000001); } // now add one race for yellow fleet and test that it counts for the score but not the ordering // because blue fleet is still missing its race for Q2 @@ -220,15 +220,15 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { competitors.get(4), competitors.get(7) }), rankedCompetitorsWithOneRaceMissingInQ2); double[] points = new double[] { 1, 2, 3, 5, 7, 4, 6, 8, 4, 5 }; for (int i=0; i<9; i++) { - assertEquals(points[i], leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001); + assertEquals(points[i], leaderboard.getNetPoints(competitors.get(i), later), 0.000000001); } - // now add a tracked race for the blue fleet for Q2 and assert that the Q2 scores count for the total points sum + // now add a tracked race for the blue fleet for Q2 and assert that the Q2 scores count for the net points sum TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2), competitors.get(8), competitors.get(9) })); q2Column.setTrackedRace(q2Column.getFleetByName("Blue"), q2Blue); // the new order in Q2 expected to be { (0, 3), (1, 4), (2, 5), (8, 6), (9, 7) } - // therefore the new total points, in ascending order, are expected to be: + // therefore the new net points, in ascending order, are expected to be: // { 0: 1+1=2; 5: 1+3=4; 1: 2+2=4; 3: 4+1=5; 2: 3+3=6; 6: 2+4=6; 4: 5+2=7; 7: 3+5=8; 8: 4+4=8; 9: 5+5=10 } double[] expectedTotalPoints = new double[] { 2, 4, 6, 5, 7, 4, 6, 8, 8, 10 }; int[] expectedOrderAfterTwoFullRacesPlusMinusOne = new int[] { 0, 5, 1, 3, 2, 6, 4, 7, 8, 9 }; @@ -238,8 +238,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i]) == rankedCompetitorsWithAllRacesInQ2.get(2*i+1)); assertTrue(competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i+1]) == rankedCompetitorsWithAllRacesInQ2.get(2*i) || competitors.get(expectedOrderAfterTwoFullRacesPlusMinusOne[2*i+1]) == rankedCompetitorsWithAllRacesInQ2.get(2*i+1)); - assertEquals(expectedTotalPoints[2*i], leaderboard.getTotalPoints(competitors.get(2*i), later), 0.000000001); - assertEquals(expectedTotalPoints[2*i+1], leaderboard.getTotalPoints(competitors.get(2*i+1), later), 0.000000001); + assertEquals(expectedTotalPoints[2*i], leaderboard.getNetPoints(competitors.get(2*i), later), 0.000000001); + assertEquals(expectedTotalPoints[2*i+1], leaderboard.getNetPoints(competitors.get(2*i+1), later), 0.000000001); } } @@ -276,7 +276,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { TrackedRace q2Yellow = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(3, 8)); RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); q2Column.setTrackedRace(q2Column.getFleetByName("Yellow"), q2Yellow); - // now add a tracked race for the blue fleet for Q2 that hasn't started yet and assert that the Q2 scores still don't count for the total points sum + // now add a tracked race for the blue fleet for Q2 that hasn't started yet and assert that the Q2 scores still don't count for the net points sum TimePoint muchLater = later.plus(10000000000l); TrackedRace q2Blue = new MockedTrackedRaceWithStartTimeAndRanks(muchLater, Arrays.asList(new Competitor[] { competitors.get(0), competitors.get(1), competitors.get(2), @@ -291,22 +291,22 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { competitors.get(4), competitors.get(7) }), rankedCompetitorsWithOneRaceMissingInQ2); double[] points = new double[] { 1, 2, 3, 5, 7, 4, 6, 8, 4, 5 }; for (int i=0; i<9; i++) { - assertEquals(points[i], leaderboard.getTotalPoints(competitors.get(i), later), 0.000000001); + assertEquals(points[i], leaderboard.getNetPoints(competitors.get(i), later), 0.000000001); } // expect all results to be valid because a fleet will have its score counted even if not all fleets have raced; // see discussion for bug 961, but more importantly later on bug 1023. for (final Competitor competitor : competitors) { - assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, competitor, later)); + assertTrue(leaderboard.getScoringScheme().isValidInNetScore(leaderboard, q2Column, competitor, later)); } // now add a score correction for Q2/Blue to make it count: leaderboard.getScoreCorrection().correctScore(competitors.get(9), q2Column, 42.); for (final Competitor competitor : competitors) { - assertTrue(leaderboard.getScoringScheme().isValidInTotalScore(leaderboard, q2Column, competitor, later)); + assertTrue(leaderboard.getScoringScheme().isValidInNetScore(leaderboard, q2Column, competitor, later)); } // the new order in Q2 expected to be { (9, 3), ... } (we don't know about any competitor in q2Blue but #9 - // therefore the new total points for #9 are + // therefore the new net points for #9 are // { 9: 5+42=47 } - assertEquals(47.0, leaderboard.getTotalPoints(competitors.get(9), later), 0.00000001); + assertEquals(47.0, leaderboard.getNetPoints(competitors.get(9), later), 0.00000001); } /** @@ -401,7 +401,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { /** * Asserts that the competitors ranking worse than the disqualified competitor advance by one in the * {@link Leaderboard#getCompetitorsFromBestToWorst(TimePoint)} ordering. Note that this does not test - * the total points given for those competitors. + * the net points given for those competitors. */ @Test public void testOneStartedRaceWithDifferentScoresAndDisqualification() throws NoWindException { @@ -426,13 +426,13 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(competitors.subList(6, 10), rankedCompetitors.subList(5, 9)); assertEquals(competitors.get(5), rankedCompetitors.get(9)); - // Now test the total points and make sure the other competitors advanced by one, too - assertEquals(11, leaderboard.getTotalPoints(competitors.get(5), f1Column, now), 0.000000001); + // Now test the net points and make sure the other competitors advanced by one, too + assertEquals(11, leaderboard.getNetPoints(competitors.get(5), f1Column, now), 0.000000001); for (int i=0; i<5; i++) { - assertEquals(i+1, leaderboard.getTotalPoints(competitors.get(i), f1Column, now), 0.000000001); + assertEquals(i+1, leaderboard.getNetPoints(competitors.get(i), f1Column, now), 0.000000001); } for (int i=6; i<10; i++) { - assertEquals(i, leaderboard.getTotalPoints(competitors.get(i), f1Column, now), 0.000000001); + assertEquals(i, leaderboard.getNetPoints(competitors.get(i), f1Column, now), 0.000000001); } } @@ -493,14 +493,14 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { for (Competitor goldCompetitor : gold) { assertEquals(rank, f1Column.getTrackedRace(goldCompetitor).getRank(goldCompetitor, later)); assertEquals(rank, leaderboard.getTrackedRank(goldCompetitor, f1Column, later)); - assertEquals(NUMBER_OF_COMPETITORS/2+1-rank, leaderboard.getNetPoints(goldCompetitor, f1Column, later), 0.00000001); + assertEquals(NUMBER_OF_COMPETITORS/2+1-rank, leaderboard.getTotalPoints(goldCompetitor, f1Column, later), 0.00000001); rank++; } rank=1; for (Competitor silverCompetitor : silver) { assertEquals(rank, f1Column.getTrackedRace(silverCompetitor).getRank(silverCompetitor, later)); assertEquals(rank, leaderboard.getTrackedRank(silverCompetitor, f1Column, later)); - assertEquals(NUMBER_OF_COMPETITORS/2+1-rank, leaderboard.getNetPoints(silverCompetitor, f1Column, later), 0.00000001); + assertEquals(NUMBER_OF_COMPETITORS/2+1-rank, leaderboard.getTotalPoints(silverCompetitor, f1Column, later), 0.00000001); rank++; } List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); @@ -592,7 +592,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { // assert that all competitors have equal points now Competitor firstCompetitor = competitors.iterator().next(); for (Competitor competitor : competitors) { - assertEquals(leaderboard.getTotalPoints(firstCompetitor, later), leaderboard.getTotalPoints(competitor, later)); + assertEquals(leaderboard.getNetPoints(firstCompetitor, later), leaderboard.getNetPoints(competitor, later)); } // assert that the ordering of competitors equals that of the last race assertEquals(reversedCompetitors, leaderboard.getCompetitorsFromBestToWorst(later)); @@ -652,7 +652,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); assertEquals(Arrays.asList(new Competitor[] { c[0], c[1], c[2] }), rankedCompetitors); } @@ -671,7 +671,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); assertTrue(rankedCompetitors.indexOf(c[0]) == rankedCompetitors.indexOf(c[1])-1); } @@ -704,8 +704,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[] { 4 }); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(33.0, leaderboard.getTotalPoints(GER2105, later), 0.0000001); - assertEquals(33.0, leaderboard.getTotalPoints(GER2254, later), 0.0000001); + assertEquals(33.0, leaderboard.getNetPoints(GER2105, later), 0.0000001); + assertEquals(33.0, leaderboard.getNetPoints(GER2254, later), 0.0000001); assertTrue(rankedCompetitors.indexOf(GER2105) == rankedCompetitors.indexOf(GER2254)-1); } @@ -756,26 +756,26 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); assertSame(c[0], rankedCompetitors.get(0)); // should be the winner of the final round's Final heat and take the "crown" for the elimination - assertEquals(0.7, leaderboard.getTotalPoints(c[0], later), 0.000000001); + assertEquals(0.7, leaderboard.getNetPoints(c[0], later), 0.000000001); assertSame(c[1], rankedCompetitors.get(1)); // should be the winner of the final round's Final heat and take the "crown" for the elimination - assertEquals(2, leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(2, leaderboard.getNetPoints(c[1], later), 0.000000001); // first four of second heat get promoted to quarter final but lose their heat - assertNull(leaderboard.getTotalPoints(c[ 8], series.get(0).getRaceColumns().iterator().next(), later)); - assertNull(leaderboard.getTotalPoints(c[ 9], series.get(0).getRaceColumns().iterator().next(), later)); - assertNull(leaderboard.getTotalPoints(c[10], series.get(0).getRaceColumns().iterator().next(), later)); - assertNull(leaderboard.getTotalPoints(c[11], series.get(0).getRaceColumns().iterator().next(), later)); - assertEquals(18.5, leaderboard.getTotalPoints(c[ 8], later), 0.000000001); - assertEquals(22.5, leaderboard.getTotalPoints(c[ 9], later), 0.000000001); - assertEquals(26.5, leaderboard.getTotalPoints(c[10], later), 0.000000001); - assertEquals(30.5, leaderboard.getTotalPoints(c[11], later), 0.000000001); + assertNull(leaderboard.getNetPoints(c[ 8], series.get(0).getRaceColumns().iterator().next(), later)); + assertNull(leaderboard.getNetPoints(c[ 9], series.get(0).getRaceColumns().iterator().next(), later)); + assertNull(leaderboard.getNetPoints(c[10], series.get(0).getRaceColumns().iterator().next(), later)); + assertNull(leaderboard.getNetPoints(c[11], series.get(0).getRaceColumns().iterator().next(), later)); + assertEquals(18.5, leaderboard.getNetPoints(c[ 8], later), 0.000000001); + assertEquals(22.5, leaderboard.getNetPoints(c[ 9], later), 0.000000001); + assertEquals(26.5, leaderboard.getNetPoints(c[10], later), 0.000000001); + assertEquals(30.5, leaderboard.getNetPoints(c[11], later), 0.000000001); // last four competitors in last heat of first round don't get promoted and have null scores in all other rounds but the first - assertEquals(36.5, leaderboard.getTotalPoints(c[60], later), 0.000000001); - assertEquals(44.5, leaderboard.getTotalPoints(c[61], later), 0.000000001); - assertEquals(52.5, leaderboard.getTotalPoints(c[62], later), 0.000000001); - assertEquals(60.5, leaderboard.getTotalPoints(c[63], later), 0.000000001); + assertEquals(36.5, leaderboard.getNetPoints(c[60], later), 0.000000001); + assertEquals(44.5, leaderboard.getNetPoints(c[61], later), 0.000000001); + assertEquals(52.5, leaderboard.getNetPoints(c[62], later), 0.000000001); + assertEquals(60.5, leaderboard.getNetPoints(c[63], later), 0.000000001); for (int i=1; i<=3; i++) { for (int j=60; j<=63; j++) { - assertNull(leaderboard.getTotalPoints(c[j], series.get(i).getRaceColumns().iterator().next(), later)); + assertNull(leaderboard.getNetPoints(c[j], series.get(i).getRaceColumns().iterator().next(), later)); } } } @@ -830,29 +830,29 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } } for (int numberOfEliminationsExpectedToScore=NUMBER_OF_ELIMINATIONS; numberOfEliminationsExpectedToScore>=NUMBER_OF_ELIMINATIONS-1; numberOfEliminationsExpectedToScore--) { - assertEquals(numberOfEliminationsExpectedToScore*18.5, leaderboard.getTotalPoints(c[ 8], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*22.5, leaderboard.getTotalPoints(c[ 9], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*26.5, leaderboard.getTotalPoints(c[10], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*30.5, leaderboard.getTotalPoints(c[11], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*18.5, leaderboard.getNetPoints(c[ 8], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*22.5, leaderboard.getNetPoints(c[ 9], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*26.5, leaderboard.getNetPoints(c[10], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*30.5, leaderboard.getNetPoints(c[11], later), 0.000000001); // last four competitors in last heat of first round don't get promoted and have null scores in all other rounds but the first - assertEquals(numberOfEliminationsExpectedToScore*36.5, leaderboard.getTotalPoints(c[60], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*44.5, leaderboard.getTotalPoints(c[61], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*52.5, leaderboard.getTotalPoints(c[62], later), 0.000000001); - assertEquals(numberOfEliminationsExpectedToScore*60.5, leaderboard.getTotalPoints(c[63], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*36.5, leaderboard.getNetPoints(c[60], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*44.5, leaderboard.getNetPoints(c[61], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*52.5, leaderboard.getNetPoints(c[62], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*60.5, leaderboard.getNetPoints(c[63], later), 0.000000001); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); assertSame(c[0], rankedCompetitors.get(0)); // should be the winner of the final round's Final heat and take the "crown" for the elimination - assertEquals(numberOfEliminationsExpectedToScore*0.7, leaderboard.getTotalPoints(c[0], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*0.7, leaderboard.getNetPoints(c[0], later), 0.000000001); assertSame(c[1], rankedCompetitors.get(1)); // should be the winner of the final round's Final heat and take the "crown" for the elimination - assertEquals(numberOfEliminationsExpectedToScore*2, leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(numberOfEliminationsExpectedToScore*2, leaderboard.getNetPoints(c[1], later), 0.000000001); for (int elimination=0; elimination rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); // assert that both have equal score - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); // assert that c[0] ranks better than c[1] (reason: c[0] ranked better in medal race) assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1); } @@ -1203,7 +1203,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { createAndAttachTrackedRaces(series.get(2), "Medal", /* withScores */ true, m1, m2, m3); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); // assert that both have equal score - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); // assert that c[1] ranks better than c[0] (reason: c[1] ranked better in medal race) assertEquals(rankedCompetitors.indexOf(c[1]), rankedCompetitors.indexOf(c[0])-1); } @@ -1224,7 +1224,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1); } @@ -1244,7 +1244,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2, f3, f4, f5, f6); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(leaderboard.getTotalPoints(c[0], later), leaderboard.getTotalPoints(c[1], later), 0.000000001); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); assertEquals(rankedCompetitors.indexOf(c[0]), rankedCompetitors.indexOf(c[1])-1); } @@ -1263,28 +1263,28 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { new HighPointFirstGets10Or8AndLastBreaksTie(), null); leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R1", /* medalRace */ false); assertTrue(leaderboardHighPoint10Or8AndLastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3 + leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[3], later)) < 0); // c0 better than c3 assertEquals(10, leaderboardHighPoint10Or8AndLastBreaksTie.getCompetitorsFromBestToWorst(later).size()); - assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(9), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); - assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(10), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(9), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[1], later)); + assertEquals(new Double(1), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R2", /* medalRace */ false); - assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); - assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[1], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); RaceLog raceLogForRace2 = leaderboardHighPoint10Or8AndLastBreaksTie.getRaceColumnByName("R2").getRaceLog(leaderboardHighPoint10Or8AndLastBreaksTie.getFleet(null)); raceLogForRace2.add(new RaceLogAdditionalScoringInformationEventImpl(now, later, new LogEventAuthorImpl("Plopp", 1), "12345678", 0, AdditionalScoringInformationType.UNKNOWN)); - assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); - assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(20), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[1], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); raceLogForRace2.add(new RaceLogAdditionalScoringInformationEventImpl(now, later.plus(Duration.ONE_MINUTE), new LogEventAuthorImpl("Plopp", 1), "123456789873773762", 0, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); - assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(16), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); - assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(18), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(16), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[1], later)); + assertEquals(new Double(2), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); leaderboardHighPoint10Or8AndLastBreaksTie.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors), dummyRegatta), "R3", /* medalRace */ false); - assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(25), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[1], later)); - assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(25), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[1], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); AdditionalScoringInformationFinder finder = new AdditionalScoringInformationFinder(raceLogForRace2); RaceLogAdditionalScoringInformationEvent event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assert event != null; @@ -1295,18 +1295,18 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } event = finder.analyze(/*filterBy*/AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assert event == null; - assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); RaceState raceStateForRace2 = RaceStateImpl.create(mock(RaceLogResolver.class), raceLogForRace2, new LogEventAuthorImpl("Simon", 1)); assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assertTrue(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); - assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(28), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(10000)), /*enable*/false, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); assertFalse(raceStateForRace2.isAdditionalScoringInformationEnabled(AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE)); - assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getTotalPoints(competitors[9], later)); + assertEquals(new Double(30), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(3), leaderboardHighPoint10Or8AndLastBreaksTie.getNetPoints(competitors[9], later)); LeaderboardDTO leaderboardDTO = null; try { leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false); @@ -1316,7 +1316,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { e.printStackTrace(); } assert leaderboardDTO != null; - assertEquals(30.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001); + assertEquals(30.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).netPoints, 0.000000000001); raceStateForRace2.setAdditionalScoringInformationEnabled(later.plus(Duration.ONE_MINUTE).plus(Duration.ONE_SECOND.plus(20000)), /*enable*/true, AdditionalScoringInformationType.MAX_POINTS_DECREASE_MAX_SCORE); try { leaderboardDTO = leaderboardHighPoint10Or8AndLastBreaksTie.getLeaderboardDTO(later.plus(Duration.ONE_HOUR), Collections.emptyList(), false, trackedRegattaRegistry, DomainFactory.INSTANCE, false); @@ -1325,8 +1325,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } catch (ExecutionException e) { e.printStackTrace(); } - assertEquals(28.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).totalPoints, 0.000000000001); - assertEquals(25.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(1)).totalPoints, 0.000000000001); + assertEquals(28.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(0)).netPoints, 0.000000000001); + assertEquals(25.0, leaderboardDTO.rows.get(leaderboardDTO.competitors.get(1)).netPoints, 0.000000000001); } @Test @@ -1340,35 +1340,35 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { .addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false); assertTrue(leaderboardHighPoint10LastBreaksTie.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[0], later), leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[3], later)) < 0); // c0 better than c3 + leaderboardHighPoint10LastBreaksTie.getNetPoints(competitors[0], later), leaderboardHighPoint10LastBreaksTie.getNetPoints(competitors[3], later)) < 0); // c0 better than c3 assertEquals(16, leaderboardHighPoint10LastBreaksTie.getCompetitorsFromBestToWorst(later).size()); - assertEquals(new Double(10), leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[0], later)); - assertEquals(new Double(1), leaderboardHighPoint10LastBreaksTie.getTotalPoints(competitors[15], later)); + assertEquals(new Double(10), leaderboardHighPoint10LastBreaksTie.getNetPoints(competitors[0], later)); + assertEquals(new Double(1), leaderboardHighPoint10LastBreaksTie.getNetPoints(competitors[15], later)); // Normal HighPoint leaderboard has no max so that winner gets as many points as there are competitors FlexibleLeaderboard leaderboardHighPoint = new FlexibleLeaderboardImpl("Test ESS Highpoint", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPoint(), null); leaderboardHighPoint.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors)), "R1", /* medalRace */ false); assertEquals(16, leaderboardHighPoint.getCompetitorsFromBestToWorst(later).size()); - assertEquals(new Double(16), leaderboardHighPoint.getTotalPoints(competitors[0], later)); + assertEquals(new Double(16), leaderboardHighPoint.getNetPoints(competitors[0], later)); LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("ESS", "ESS", /* displayName */ null, /* displayGroupsInReverseOrder */ false, Arrays.asList(new Leaderboard[] { leaderboardHighPoint10LastBreaksTie })); LeaderboardGroupMetaLeaderboard leaderboardHighPointESSOverall = new LeaderboardGroupMetaLeaderboard( leaderboardGroup, new HighPointExtremeSailingSeriesOverall(), new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0])); assertEquals(16, leaderboardHighPointESSOverall.getCompetitorsFromBestToWorst(later).size()); - assertEquals(new Double(10), leaderboardHighPointESSOverall.getTotalPoints(competitors[0], later)); - assertEquals(new Double(9), leaderboardHighPointESSOverall.getTotalPoints(competitors[1], later)); - assertEquals(new Double(8), leaderboardHighPointESSOverall.getTotalPoints(competitors[2], later)); - assertEquals(new Double(7), leaderboardHighPointESSOverall.getTotalPoints(competitors[3], later)); - assertEquals(new Double(6), leaderboardHighPointESSOverall.getTotalPoints(competitors[4], later)); - assertEquals(new Double(5), leaderboardHighPointESSOverall.getTotalPoints(competitors[5], later)); - assertEquals(new Double(4), leaderboardHighPointESSOverall.getTotalPoints(competitors[6], later)); - assertEquals(new Double(3), leaderboardHighPointESSOverall.getTotalPoints(competitors[7], later)); - assertEquals(new Double(2), leaderboardHighPointESSOverall.getTotalPoints(competitors[8], later)); - assertEquals(new Double(1), leaderboardHighPointESSOverall.getTotalPoints(competitors[9], later)); - assertEquals(new Double(1), leaderboardHighPointESSOverall.getTotalPoints(competitors[14], later)); - assertEquals(new Double(1), leaderboardHighPointESSOverall.getTotalPoints(competitors[15], later)); + assertEquals(new Double(10), leaderboardHighPointESSOverall.getNetPoints(competitors[0], later)); + assertEquals(new Double(9), leaderboardHighPointESSOverall.getNetPoints(competitors[1], later)); + assertEquals(new Double(8), leaderboardHighPointESSOverall.getNetPoints(competitors[2], later)); + assertEquals(new Double(7), leaderboardHighPointESSOverall.getNetPoints(competitors[3], later)); + assertEquals(new Double(6), leaderboardHighPointESSOverall.getNetPoints(competitors[4], later)); + assertEquals(new Double(5), leaderboardHighPointESSOverall.getNetPoints(competitors[5], later)); + assertEquals(new Double(4), leaderboardHighPointESSOverall.getNetPoints(competitors[6], later)); + assertEquals(new Double(3), leaderboardHighPointESSOverall.getNetPoints(competitors[7], later)); + assertEquals(new Double(2), leaderboardHighPointESSOverall.getNetPoints(competitors[8], later)); + assertEquals(new Double(1), leaderboardHighPointESSOverall.getNetPoints(competitors[9], later)); + assertEquals(new Double(1), leaderboardHighPointESSOverall.getNetPoints(competitors[14], later)); + assertEquals(new Double(1), leaderboardHighPointESSOverall.getNetPoints(competitors[15], later)); } @Test @@ -1392,7 +1392,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R2", /* medalRace */ false); assertTrue(leaderboard1.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - leaderboard1.getTotalPoints(c[0], later), leaderboard1.getTotalPoints(c[3], later)) < 0); // c0 better than c3 + leaderboard1.getNetPoints(c[0], later), leaderboard1.getNetPoints(c[3], later)) < 0); // c0 better than c3 FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPoint(), null); leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f3)), "R1", /* medalRace */ @@ -1400,7 +1400,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f4)), "R2", /* medalRace */ false); assertTrue(leaderboard2.getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - leaderboard2.getTotalPoints(c[3], later), leaderboard2.getTotalPoints(c[0], later)) < 0); // c3 better than c0 + leaderboard2.getNetPoints(c[3], later), leaderboard2.getNetPoints(c[0], later)) < 0); // c3 better than c0 FlexibleLeaderboard leaderboard3 = new FlexibleLeaderboardImpl("Leaderboard 3", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPoint(), null); leaderboard3.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f5)), "R1", /* medalRace */ @@ -1419,8 +1419,8 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertFalse(rankedCompetitors.contains(c[1])); assertFalse(rankedCompetitors.contains(c[2])); assertEquals(2, rankedCompetitors.size()); - assertEquals(28 /* one win, two second */, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[0], later), 0.000000001); - assertEquals(29 /* two wins, one second */, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[3], later), 0.000000001); + assertEquals(28 /* one win, two second */, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[0], later), 0.000000001); + assertEquals(29 /* two wins, one second */, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[3], later), 0.000000001); } @Test @@ -1434,9 +1434,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { new HighPointFirstGets10Or8AndLastBreaksTie(), null); leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */ false); - assertTrue(leaderboard1.getTotalPoints(c[2], later) > leaderboard1.getTotalPoints(c[0], later)); // c2 better than c[0] - assertEquals(10.0, leaderboard1.getTotalPoints(c[2], later), 0.1); - assertEquals(9.0, leaderboard1.getTotalPoints(c[0], later), 0.1); + assertTrue(leaderboard1.getNetPoints(c[2], later) > leaderboard1.getNetPoints(c[0], later)); // c2 better than c[0] + assertEquals(10.0, leaderboard1.getNetPoints(c[2], later), 0.1); + assertEquals(9.0, leaderboard1.getNetPoints(c[0], later), 0.1); FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Regatta 2", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]), new HighPointFirstGets10Or8AndLastBreaksTie(), null); leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R1", /* medalRace */ @@ -1447,10 +1447,10 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { new ThresholdBasedResultDiscardingRuleImpl(new int[0]))); List rankedCompetitors = leaderboardGroup.getOverallLeaderboard().getCompetitorsFromBestToWorst(later); assertEquals(4, rankedCompetitors.size()); - assertEquals(19, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[0], later), 0.000000001); - assertEquals(19, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[2], later), 0.000000001); - assertEquals(16, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[1], later), 0.000000001); - assertEquals(14, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[3], later), 0.000000001); + assertEquals(19, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[0], later), 0.000000001); + assertEquals(19, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[2], later), 0.000000001); + assertEquals(16, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[1], later), 0.000000001); + assertEquals(14, leaderboardGroup.getOverallLeaderboard().getNetPoints(c[3], later), 0.000000001); assertEquals(c[0], rankedCompetitors.get(0)); assertEquals(c[2], rankedCompetitors.get(1)); } @@ -1492,34 +1492,34 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(8, rankedCompetitorsFleet1.size()); for (int i = 0; i < expectedResultsFleet1.length; i++) { assertTrue(rankedCompetitorsFleet1.get(i) == competitors.get(i)); - assertEquals(expectedResultsFleet1[i], leaderboard.getTotalPoints(rankedCompetitorsFleet1.get(i), later), 0.000000001); + assertEquals(expectedResultsFleet1[i], leaderboard.getNetPoints(rankedCompetitorsFleet1.get(i), later), 0.000000001); } List rankedCompetitorsFleet2 = r1Fleet2.getCompetitorsFromBestToWorst(later); assertEquals(7, rankedCompetitorsFleet2.size()); for (int i = 0; i < expectedResultsFleet2.length; i++) { assertTrue(rankedCompetitorsFleet2.get(i) == competitors.get(8+i)); - assertEquals(expectedResultsFleet2[i], leaderboard.getTotalPoints(rankedCompetitorsFleet2.get(i), later), 0.000000001); + assertEquals(expectedResultsFleet2[i], leaderboard.getNetPoints(rankedCompetitorsFleet2.get(i), later), 0.000000001); } List rankedCompetitorsFleet3 = r1Fleet3.getCompetitorsFromBestToWorst(later); assertEquals(6, rankedCompetitorsFleet3.size()); for (int i = 0; i < expectedResultsFleet3.length; i++) { assertTrue(rankedCompetitorsFleet3.get(i) == competitors.get(15+i)); - assertEquals(expectedResultsFleet3[i], leaderboard.getTotalPoints(rankedCompetitorsFleet3.get(i), later), 0.000000001); + assertEquals(expectedResultsFleet3[i], leaderboard.getNetPoints(rankedCompetitorsFleet3.get(i), later), 0.000000001); } List allCompetitorsFromBestToWorst = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(competitorsCount, allCompetitorsFromBestToWorst.size()); // check that the fleet winners have all 8 points - assertEquals(8.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(0), later), 0.000000001); - assertEquals(8.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(1), later), 0.000000001); - assertEquals(8.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(2), later), 0.000000001); + assertEquals(8.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(0), later), 0.000000001); + assertEquals(8.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(1), later), 0.000000001); + assertEquals(8.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(2), later), 0.000000001); // check that the fleet looser have all 1 point - assertEquals(1.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(competitorsCount-1), later), 0.000000001); - assertEquals(1.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(competitorsCount-2), later), 0.000000001); - assertEquals(1.0, leaderboard.getTotalPoints(allCompetitorsFromBestToWorst.get(competitorsCount-3), later), 0.000000001); + assertEquals(1.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(competitorsCount-1), later), 0.000000001); + assertEquals(1.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(competitorsCount-2), later), 0.000000001); + assertEquals(1.0, leaderboard.getNetPoints(allCompetitorsFromBestToWorst.get(competitorsCount-3), later), 0.000000001); } @Test @@ -1552,14 +1552,14 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { List rankedCompetitorsInLeaderboard = leaderboard2.getCompetitorsFromBestToWorst(later); List scoresForCompetitors = new ArrayList(); for (Competitor competitor : rankedCompetitorsInLeaderboard) { - scoresForCompetitors.add(leaderboard2.getTotalPoints(competitor, later)); + scoresForCompetitors.add(leaderboard2.getNetPoints(competitor, later)); } assertEquals(12, rankedCompetitorsInLeaderboard.size()); assertEquals(c[1], rankedCompetitorsInLeaderboard.get(0)); - assertEquals(29, leaderboard2.getTotalPoints(c[1], later), 0.000000001); - assertEquals(7, leaderboard2.getTotalPoints(c[8], later), 0.000000001); - assertEquals(4, leaderboard2.getTotalPoints(c[9], later), 0.000000001); - assertEquals(3, leaderboard2.getTotalPoints(c[11], later), 0.000000001); + assertEquals(29, leaderboard2.getNetPoints(c[1], later), 0.000000001); + assertEquals(7, leaderboard2.getNetPoints(c[8], later), 0.000000001); + assertEquals(4, leaderboard2.getNetPoints(c[9], later), 0.000000001); + assertEquals(3, leaderboard2.getNetPoints(c[11], later), 0.000000001); LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group ESS Overall", "Leaderboard Group", /* displayName */ null, false, Arrays.asList(leaderboard1, leaderboard2)); @@ -1600,9 +1600,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { }, "R1", /* medalRace */false); RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.); - assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column - assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column - assertEquals(0., leaderboard1.getTotalPoints(c[0], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(2. + 123., leaderboard1.getNetPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column + assertEquals(2. + 0., leaderboard1.getNetPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column + assertEquals(0., leaderboard1.getNetPoints(c[0], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 } /** @@ -1629,9 +1629,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); assertEquals(MaxPointsReason.DNS, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1)); assertEquals(MaxPointsReason.DNS, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1)); - assertNull(leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); - assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); - assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); + assertNull(leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(123., leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); + assertEquals(123., leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); } /** @@ -1658,9 +1658,9 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); assertEquals(MaxPointsReason.NONE, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1)); assertEquals(MaxPointsReason.DNF, leaderboard1.getMaxPointsReason(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1)); - assertNull(leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); - assertEquals(1.0, leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); // tracked rank is 1 - assertEquals(123., leaderboard1.getNetPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); + assertNull(leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), beforeStartOfR1)); + assertEquals(1.0, leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), withinR1), 0.00000001); // tracked rank is 1 + assertEquals(123., leaderboard1.getTotalPoints(c[0], leaderboard1.getRaceColumnByName("R1"), afterEndOfR1), 0.00000001); } @@ -1691,7 +1691,7 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { } }, "R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c1[0], r2, .9); - assertEquals(1. + .9, leaderboard1.getTotalPoints(c1[0], afterEndOfR1), 0.00000001); + assertEquals(1. + .9, leaderboard1.getNetPoints(c1[0], afterEndOfR1), 0.00000001); assertEquals(1, leaderboard1.getTotalRankOfCompetitor(c1[0], afterEndOfR1)); // now assert that the competitor c1[0] is best in r2 because of the score correction, although c1[0] doesn't have a fleet assertEquals(0, leaderboard1.getCompetitorsFromBestToWorst(r2, afterEndOfR1).indexOf(c1[0])); @@ -1732,35 +1732,35 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { RaceColumn r1 = leaderboard1.addRace(trackedRace, "R1", /* medalRace */false); RaceColumn r2 = leaderboard1.addRaceColumn("R2", /* medalRace */false); leaderboard1.getScoreCorrection().correctScore(c[0], r2, 123.); - assertEquals(2. + 123., leaderboard1.getTotalPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column - assertEquals(2. + 0., leaderboard1.getTotalPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column - assertEquals(0., leaderboard1.getTotalPoints(c[0], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(2. + 123., leaderboard1.getNetPoints(c[0], afterEndOfR1), 0.00000001); // correction expected to apply after end of last tracked race before the corrected column + assertEquals(2. + 0., leaderboard1.getNetPoints(c[0], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column + assertEquals(0., leaderboard1.getNetPoints(c[0], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 leaderboard1.getScoreCorrection().setMaxPointsReason(c[1], r2, MaxPointsReason.DNS); - assertEquals(3. + 5., leaderboard1.getTotalPoints(c[1], afterEndOfR1), 0.00000001); // DNS is applied at race start (not known), so at least after the end of R1 - assertEquals(3. + 0., leaderboard1.getTotalPoints(c[1], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column - assertEquals(0., leaderboard1.getTotalPoints(c[1], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(3. + 5., leaderboard1.getNetPoints(c[1], afterEndOfR1), 0.00000001); // DNS is applied at race start (not known), so at least after the end of R1 + assertEquals(3. + 0., leaderboard1.getNetPoints(c[1], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column + assertEquals(0., leaderboard1.getNetPoints(c[1], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 leaderboard1.getScoreCorrection().setMaxPointsReason(c[2], r2, MaxPointsReason.DNF); - assertEquals(1. + 5., leaderboard1.getTotalPoints(c[2], afterEndOfR1), 0.00000001); // DNF is applied after R2 finish (not known), so at least after the end of R1 - assertEquals(1. + 0., leaderboard1.getTotalPoints(c[2], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column - assertEquals(0., leaderboard1.getTotalPoints(c[2], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(1. + 5., leaderboard1.getNetPoints(c[2], afterEndOfR1), 0.00000001); // DNF is applied after R2 finish (not known), so at least after the end of R1 + assertEquals(1. + 0., leaderboard1.getNetPoints(c[2], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column + assertEquals(0., leaderboard1.getNetPoints(c[2], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 leaderboard1.getScoreCorrection().setMaxPointsReason(c[3], r1, MaxPointsReason.DNF); - assertEquals(0., leaderboard1.getTotalPoints(c[3], beforeStartOfR1), 0.00000001); // DNF does not apply before the start - assertEquals(4., leaderboard1.getTotalPoints(c[3], withinR1), 0.00000001); // correction expected NOT to apply before end of race - assertEquals(5., leaderboard1.getTotalPoints(c[3], afterEndOfR1), 0.00000001); // after race is finished, DNF applies + assertEquals(0., leaderboard1.getNetPoints(c[3], beforeStartOfR1), 0.00000001); // DNF does not apply before the start + assertEquals(4., leaderboard1.getNetPoints(c[3], withinR1), 0.00000001); // correction expected NOT to apply before end of race + assertEquals(5., leaderboard1.getNetPoints(c[3], afterEndOfR1), 0.00000001); // after race is finished, DNF applies leaderboard1.getScoreCorrection().setMaxPointsReason(c[3], r1, MaxPointsReason.DNS); - assertEquals(0., leaderboard1.getTotalPoints(c[3], beforeStartOfR1), 0.00000001); // DNS does not apply before the start - assertEquals(5., leaderboard1.getTotalPoints(c[3], withinR1), 0.00000001); // correction expected to apply after the start - assertEquals(5., leaderboard1.getTotalPoints(c[3], afterEndOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(0., leaderboard1.getNetPoints(c[3], beforeStartOfR1), 0.00000001); // DNS does not apply before the start + assertEquals(5., leaderboard1.getNetPoints(c[3], withinR1), 0.00000001); // correction expected to apply after the start + assertEquals(5., leaderboard1.getNetPoints(c[3], afterEndOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 leaderboard1.getScoreCorrection().setMaxPointsReason(c[3], r1, null); leaderboard1.getScoreCorrection().correctScore(c[3], r1, 123.); - assertEquals(4., leaderboard1.getTotalPoints(c[3], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column - assertEquals(123., leaderboard1.getTotalPoints(c[3], afterEndOfR1), 0.00000001); // correction is applied after R1 finish - assertEquals(0., leaderboard1.getTotalPoints(c[3], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 + assertEquals(4., leaderboard1.getNetPoints(c[3], withinR1), 0.00000001); // correction expected NOT to apply before end of last tracked race before the corrected column + assertEquals(123., leaderboard1.getNetPoints(c[3], afterEndOfR1), 0.00000001); // correction is applied after R1 finish + assertEquals(0., leaderboard1.getNetPoints(c[3], beforeStartOfR1), 0.00000001); // not even the R1 scores apply before the start time of R1 } private TimePoint createAndAttachTrackedRaces(Series theSeries, String fleetName, boolean withScores, Competitor[]... competitorLists) { @@ -2013,21 +2013,21 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest { r2Column.setTrackedRace(r2Column.getFleetByName("Default"), r2Default); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); - Map totalPoints = new LinkedHashMap<>(); + Map netPoints = new LinkedHashMap<>(); for (Competitor rankedCompetitor : rankedCompetitors) { - totalPoints.put(rankedCompetitor, leaderboard.getTotalPoints(rankedCompetitor, later)); + netPoints.put(rankedCompetitor, leaderboard.getNetPoints(rankedCompetitor, later)); } // assert that the final column competitors are ranked from top to bottom: double scoreInFinalRace = 10.0; for (Competitor rankedCompetitor : leaderboard.getCompetitorsFromBestToWorst(f1Column, later)) { - assertEquals(scoreInFinalRace, leaderboard.getTotalPoints(rankedCompetitor, f1Column, later), 0.000001); + assertEquals(scoreInFinalRace, leaderboard.getNetPoints(rankedCompetitor, f1Column, later), 0.000001); scoreInFinalRace = Math.max(scoreInFinalRace-1, 1); } double lastScore = Double.MAX_VALUE; // assert that only the points matter for ranking; not the fleet assignment for (Competitor rankedCompetitor : rankedCompetitors) { // with a high-point scoring scheme, assert that as competitors get worse, scores get less - double rankedCompetitorScore = totalPoints.get(rankedCompetitor); + double rankedCompetitorScore = netPoints.get(rankedCompetitor); assertTrue("Expected " + rankedCompetitor + " with rank " + (rankedCompetitors.indexOf(rankedCompetitor) + 1) + " to have worse (lesser) score than its immediate better competitor who scored " + lastScore diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java index 06b302eccac..35e19bcd554 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LineAnalysisTest.java @@ -30,7 +30,7 @@ public class LineAnalysisTest extends TrackBasedTest { public void setUp() { final List emptyCompetitorList = Collections.emptyList(); now = MillisecondsTimePoint.now(); - trackedRace = createTestTrackedRace("Test Regatta", "Test Race", "505", emptyCompetitorList, now); + trackedRace = createTestTrackedRace("Test Regatta", "Test Race", "505", emptyCompetitorList, now, /* useMarkPassingCalculator */ false); } @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ParseTracTracJSONServiceOutputTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ParseTracTracJSONServiceOutputTest.java index 7a5a922795d..85a08187426 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ParseTracTracJSONServiceOutputTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ParseTracTracJSONServiceOutputTest.java @@ -95,7 +95,7 @@ public class ParseTracTracJSONServiceOutputTest { List races = jsonService.getRaceRecords(); assertFalse(races.isEmpty()); for (RaceRecord race : races) { - if ("REPLAY".equals(race.getRaceStatus())) { + if (race.hasReplay()) { // replay races may have an empty / null live URI assertTrue(race.getLiveURI() == null || new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":4400").equals(race.getLiveURI())); // and they point to stored data in the form of a binary ".mtb" file @@ -103,7 +103,7 @@ public class ParseTracTracJSONServiceOutputTest { assertTrue(race.getStoredURI().toString().endsWith(expectedSuffix) || new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":4401").equals(race.getStoredURI())); } else { - if (!"HIDDEN".equals(race.getRaceStatus())) { + if (!"HIDDEN".equals(race.getRaceVisibility())) { // hidden races may have a null live URI and perhaps have already been converted to an .mtb, so only assert if not a HIDDEN race assertEquals(new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":4400"), race.getLiveURI()); assertEquals(new URI("tcp://" + TracTracConnectionConstants.HOST_NAME + ":4401"), race.getStoredURI()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java index fbb03acad0b..4f4bc51fb91 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceExecutionOrderProvdiderAttachDetachTest.java @@ -76,7 +76,7 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest @Test public void testRaceExecutionOrderProviderAttachDetachWithRaceColumn() { trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); + MillisecondsTimePoint.now(), /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); @@ -89,9 +89,9 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest public void testWindInRegularIntervalWithPreviousRaceStillTracking() { final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_MINUTE.times(5)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace, /* useMarkPassingCalculator */ false); previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace, /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); @@ -107,10 +107,10 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace, /* useMarkPassingCalculator */ false); previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace, /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); @@ -129,7 +129,7 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); final TimePoint startOfSecondRace = startOfFirstRace.plus(Duration.ONE_HOUR); - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace, /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(trackedRace, RACECOLUMN_FLEXIBLELEADERBOARD, false); @@ -145,10 +145,10 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.times(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace, /* useMarkPassingCalculator */ false); previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace, /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); @@ -166,10 +166,10 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest final TimePoint startOfFirstRace = MillisecondsTimePoint.now(); final TimePoint endOfFirstRace = startOfFirstRace.plus(Duration.ONE_SECOND); final TimePoint startOfSecondRace = startOfFirstRace.plus(TrackedRaceImpl.EXTRA_LONG_TIME_BEFORE_START_TO_TRACK_WIND_MILLIS.divide(2)); - DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace); + DynamicTrackedRaceImpl previousTrackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), startOfFirstRace, /* useMarkPassingCalculator */ false); previousTrackedRace.setStartOfTrackingReceived(startOfFirstRace); previousTrackedRace.setEndOfTrackingReceived(endOfFirstRace); // a very short race... - trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace); + trackedRace = createTestTrackedRace(REGATTA, "TestRace2", BOATCLASS, Collections. emptyList(), startOfSecondRace, /* useMarkPassingCalculator */ false); flexibleLeaderboard = new FlexibleLeaderboardImpl(FLEXIBLELEADERBOARD, new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 6 }), new LowPoint(), null); flexibleLeaderboard.addRace(previousTrackedRace, RACECOLUMN_FLEXIBLELEADERBOARD+"1", false); @@ -215,7 +215,7 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest private void createTestSetupWithRegattaAndSeries(boolean linkSeriesToRegatta) { trackedRace = createTestTrackedRace(REGATTA, RACE, BOATCLASS, Collections. emptyList(), - MillisecondsTimePoint.now()); + MillisecondsTimePoint.now(), /* useMarkPassingCalculator */ false); fleet = new FleetImpl(FLEET); Set fleets = new HashSet<>(); fleets.add(fleet); @@ -285,7 +285,7 @@ public class RaceExecutionOrderProvdiderAttachDetachTest extends TrackBasedTest } private DynamicTrackedRace createTrackedRace(final String name, final TimePoint startOfRace, final TimePoint endOfRace) { - DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace); + DynamicTrackedRace trackedRace = createTestTrackedRace(REGATTA, name, BOATCLASS, Collections. emptyList(), startOfRace, /* useMarkPassingCalculator */ false); trackedRace.setStartOfTrackingReceived(startOfRace); trackedRace.setEndOfTrackingReceived(endOfRace); return trackedRace; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java index 6555282c269..7c536831842 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RegattaLogEventNotificationForwardingTest.java @@ -163,16 +163,16 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa final TimePoint now = MillisecondsTimePoint.now(); leaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); LeaderboardDTO dto = leaderboard.getLeaderboardDTO(now.plus(10), Collections.emptySet(), /* addOverallDetails */ false, - /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertTrue(dto.competitors.isEmpty()); final RegattaLogRegisterCompetitorEvent event = createRegattaLogEvent(); regatta.getRegattaLog().add(event); LeaderboardDTO dto2 = leaderboard.computeDTO(now.plus(20), Collections.emptySet(), /* addOverallDetails */ false, - /* waitForLatestAnalyses */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* waitForLatestAnalyses */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertFalse(dto2.competitors.isEmpty()); assertEquals(event.getCompetitor().getName(), dto2.competitors.get(0).getName()); LeaderboardDTO dto3 = leaderboard.getLeaderboardDTO(now.plus(20), Collections.emptySet(), /* addOverallDetails */ false, - /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertFalse(dto3.competitors.isEmpty()); assertEquals(event.getCompetitor().getName(), dto3.competitors.get(0).getName()); } @@ -195,16 +195,16 @@ public class RegattaLogEventNotificationForwardingTest extends AbstractSerializa leaderboard.getScoreCorrection().setTimePointOfLastCorrectionsValidity(now); RegattaLeaderboard deserializedLeaderboard = cloneBySerialization(leaderboard, DomainFactory.INSTANCE); LeaderboardDTO dto = deserializedLeaderboard.getLeaderboardDTO(now.plus(10), Collections.emptySet(), /* addOverallDetails */ false, - /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertTrue(dto.competitors.isEmpty()); final RegattaLogRegisterCompetitorEvent event = createRegattaLogEvent(); deserializedLeaderboard.getRegatta().getRegattaLog().add(event); LeaderboardDTO dto2 = deserializedLeaderboard.computeDTO(now.plus(20), Collections.emptySet(), /* addOverallDetails */ false, - /* waitForLatestAnalyses */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* waitForLatestAnalyses */ false, /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertFalse(dto2.competitors.isEmpty()); assertEquals(event.getCompetitor().getName(), dto2.competitors.get(0).getName()); LeaderboardDTO dto3 = deserializedLeaderboard.getLeaderboardDTO(now.plus(20), Collections.emptySet(), /* addOverallDetails */ false, - /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillNetPointsUncorrected */ false); + /* trackedRegattaRegistry */ null, DomainFactory.INSTANCE, /* fillTotalPointsUncorrected */ false); assertFalse(dto3.competitors.isEmpty()); assertEquals(event.getCompetitor().getName(), dto3.competitors.get(0).getName()); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTestWithTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTestWithTrackedRace.java index 7fdf55aceac..8912ec0893d 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTestWithTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTestWithTrackedRace.java @@ -21,7 +21,7 @@ public class StoredTrackBasedTestWithTrackedRace extends StoredTrackBasedTest { public void setUp() throws FileNotFoundException, IOException { Map> tracks = loadTracks(); setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", tracks.keySet(), - new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()))); + new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()), /* useMarkPassingCalculator */ false)); copyTracks(tracks); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java index e9f1099044b..8c321f83ad5 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java @@ -42,7 +42,7 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace { /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Wolfgang Hunger's boat", new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", Collections.singleton(competitor), - new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()))); + new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()), /* useMarkPassingCalculator */ false)); } @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 0013699ab37..8f6d3b1d2c6 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -87,19 +87,21 @@ public abstract class TrackBasedTest { /** * Creates a simple two-lap upwind-downwind course for a race/event with given name and boat class name with the - * competitors specified. The marks are laid out such that the upwind/downwind leg detection should be alright. - * Wind is coming from the north. A single wind fix with bearing 180deg (from=0deg) is added to the {@link WindSourceType#WEB} - * wind track using timePointForFixes as time point. - * + * competitors specified. The marks are laid out such that the upwind/downwind leg detection should be alright. The + * start line equals the finish line and the leeward gate. Wind is coming from the north. A single wind fix with + * bearing 180deg (from=0deg) is added to the {@link WindSourceType#WEB} wind track using + * timePointForFixes as time point. The leeward gate is located at N54.4680424, E10.234451 and + * N54.4680424, E10.24. The windward mark is located at N54.48, E10.24. * @param timePointForFixes * a wind fix will be inserted into the {@link WindSourceType#WEB} wind track which is aligned with the * course layout; the value of this parameter will be used as the time stamp for this wind fix. Using a * time that is reasonably within the race time (mark passing times or whatever is collected for the * tracked race returned by this method) is important because otherwise confidences of wind readouts may * be ridiculously low. + * @param useMarkPassingCalculator whether or not to use the internal mark passing calculator */ public static DynamicTrackedRaceImpl createTestTrackedRace(String regattaName, String raceName, String boatClassName, - Iterable competitors, TimePoint timePointForFixes) { + Iterable competitors, TimePoint timePointForFixes, boolean useMarkPassingCalculator) { BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true); Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, @@ -121,7 +123,7 @@ public abstract class TrackBasedTest { DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, - /* delay for wind estimation cache invalidation */ 0, /*useMarkPassingCalculator*/ false, + /* delay for wind estimation cache invalidation */ 0, useMarkPassingCalculator, OneDesignRankingMetric::new, mock(RaceLogResolver.class)); // in this simplified artificial course, the top mark is exactly north of the right leeward gate DegreePosition topPosition = new DegreePosition(54.48, 10.24); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java index 33818cfdc3a..38799401bfc 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindEstimationOnConstructedTracksTest.java @@ -60,7 +60,7 @@ public class WindEstimationOnConstructedTracksTest extends StoredTrackBasedTest private void initRace(int numberOfCompetitorsToUse, int[] numberOfMarksPassed, TimePoint timePointForFixes) { setTrackedRace(createTestTrackedRace("Kieler Woche", "505 Race 2", "505", - competitors.subList(0, numberOfCompetitorsToUse), timePointForFixes)); + competitors.subList(0, numberOfCompetitorsToUse), timePointForFixes, /* useMarkPassingCalculator */ false)); for (int i=0; i getTrackedLegs() { return null; @@ -539,7 +544,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { + public int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { return 0; } @@ -1043,11 +1048,11 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void updateStartAndEndOfTracking() { + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { } @Override - public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + public void setFinishedTime(TimePoint newFinishedTime) { } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index f55dd0722b8..0469ecd207d 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -118,6 +118,11 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { return startTime; } + @Override + public TimePoint getFinishedTime() { + return null; + } + @Override public TimePoint getEndOfRace() { return null; @@ -658,7 +663,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public void updateStartAndEndOfTracking() { + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { } @Override diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceRecord.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceRecord.java index 334e36e4e7f..4549c617d7d 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceRecord.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/RaceRecord.java @@ -39,11 +39,12 @@ public class RaceRecord { private final List boatClassNames; private final String raceStatus; private final String raceVisibility; + private final boolean hasReplay; private final URL jsonUrl; public RaceRecord(URL jsonURL, String regattaName, String name, String replayURL, String paramURLAsString, String ID, String trackingstarttime, String trackingendtime, String racestarttime, - String commaSeparatedBoatClassNames, String status, String visibility, boolean loadLiveAndStoredURI) + String commaSeparatedBoatClassNames, String status, String visibility, boolean hasReplay, boolean loadLiveAndStoredURI) throws URISyntaxException, IOException { super(); this.regattaName = regattaName; @@ -51,6 +52,7 @@ public class RaceRecord { this.replayURL = replayURL; this.raceStatus = status; this.raceVisibility = visibility; + this.hasReplay = hasReplay; this.ID = ID; this.jsonUrl = jsonURL; this.boatClassNames = new ArrayList(); @@ -141,6 +143,10 @@ public class RaceRecord { public boolean hasLiveAndStoredURI() { return liveURI != null; } + + public boolean hasReplay() { + return hasReplay; + } public String getName() { return name; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/JSONServiceImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/JSONServiceImpl.java index 6cee93e752e..221c5f7c01b 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/JSONServiceImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/JSONServiceImpl.java @@ -44,6 +44,7 @@ public class JSONServiceImpl implements JSONService { (String) jsonRaceEntry.get("classes"), (String) jsonRaceEntry.get("status"), (String) jsonRaceEntry.get("visibility"), + Boolean.valueOf((Boolean) jsonRaceEntry.get("has_replay")), /*loadLiveAndStoreURI*/ loadLiveAndStoredURI); return raceRecord; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index 6d6c6c4264b..c928934db85 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -54,15 +54,15 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { */ public interface Entry { int getTrackedRank(); - Double getNetPoints(); - Double getNetPointsUncorrected(); Double getTotalPoints(); + Double getTotalPointsUncorrected(); + Double getNetPoints(); MaxPointsReason getMaxPointsReason(); boolean isDiscarded(); /** - * Tells if the net points have been corrected by a {@link ScoreCorrection} + * Tells if the total points have been corrected by a {@link ScoreCorrection} */ - boolean isNetPointsCorrected(); + boolean isTotalPointsCorrected(); /** * @return null, if the competitor's fleet in the race column cannot be determined, the @@ -74,7 +74,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { LeaderboardDTO computeDTO(final TimePoint timePoint, final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, - DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException; + DomainFactory baseDomainFactory, boolean fillTotalPointsUncorrected) throws NoWindException; /** * Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this @@ -164,7 +164,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException; /** - * Computes the competitor's total points sum as they were or would have been after each race column (from left to right) + * Computes the competitor's net points sum as they were or would have been after each race column (from left to right) * was completed.

* * A leaderboard fills up over time, usually "from left to right" with one race after another finishing. @@ -173,14 +173,14 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * exactly n races. Still, this method pretends such a time point would have existed, actually ignoring * the times at which a race took place but only looking at the resulting scores and discards.

* - * When computing the total points sum after all columns up to and including the race column that is the key of the resulting + * When computing the net points sum after all columns up to and including the race column that is the key of the resulting * map, the method applies the discarding and tie breaking rules as they would have had to be applied had the races * in the respective column just completed. * * @return The resulting map is guaranteed to have the same iteration order regarding the race columns * as {@link #getRaceColumns()}. */ - Map> getTotalPointsSumAfterRaceColumn(TimePoint timePoint) throws NoWindException; + Map> getNetPointsSumAfterRaceColumn(TimePoint timePoint) throws NoWindException; /** * Tells the number of points carried over from previous races not tracked by this leaderboard for @@ -225,7 +225,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * @return null if the competitor didn't participate in the race or the race hasn't started yet at * timePoint */ - Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint); + Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint); /** * Tells if and why a competitor received "penalty" points for a race (however the scoring rules define the @@ -235,7 +235,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { /** * A possibly corrected number of points for the race specified. Defaults to the result of calling - * {@link #getNetPoints(Competitor, TrackedRace, TimePoint)} but may be corrected by the regatta rules for + * {@link #getTotalPoints(Competitor, TrackedRace, TimePoint)} but may be corrected by the regatta rules for * discarding results. If {@link #isDiscarded(Competitor, RaceColumn, TimePoint) discarded}, the points returned * will be 0. * @@ -249,7 +249,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * competitor doesn't appear in any of the race column's attached tracked races. A 0.0 score is returned * if the competitor's result for race is discarded. */ - Double getTotalPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException; + Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException; /** * Tells whether the contribution of raceColumn is discarded in the current leaderboard's standings for @@ -260,17 +260,17 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint); /** - * Sums up the {@link #getTotalPoints(Competitor, TrackedRace, TimePoint) total points} of competitor + * Sums up the {@link #getNetPoints(Competitor, TrackedRace, TimePoint) net points} of competitor * across all races tracked by this leaderboard, respecting the {@link RaceColumn#isStartsWithZeroScore()} property. */ - Double getTotalPoints(Competitor competitor, TimePoint timePoint); + Double getNetPoints(Competitor competitor, TimePoint timePoint); /** - * Sums up the {@link #getTotalPoints(Competitor, RaceColumn, TimePoint) total points} of competitor + * Sums up the {@link #getNetPoints(Competitor, RaceColumn, TimePoint) net points} of competitor * across all race columns listed in raceColumnsToconsider, respecting the * {@link RaceColumn#isStartsWithZeroScore()} property. */ - Double getTotalPoints(Competitor competitor, Iterable raceColumnsToConsider, TimePoint timePoint) + Double getNetPoints(Competitor competitor, Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException; /** @@ -283,8 +283,8 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * {@link Fleet#compareTo(Fleet) ordered fleets} and {@link RaceColumn#isMedalRace() medal races}. The ordering * does not consider result discarding because when sorting for a race column it is of interest how the competitor * performed in that race and not how the score affected the overall regatta score. Therefore, it is based on - * {@link #getNetPoints(Competitor, RaceColumn, TimePoint)} and not on - * {@link #getTotalPoints(Competitor, RaceColumn, TimePoint)}. + * {@link #getTotalPoints(Competitor, RaceColumn, TimePoint)} and not on + * {@link #getNetPoints(Competitor, RaceColumn, TimePoint)}. */ List getCompetitorsFromBestToWorst(RaceColumn raceColumn, TimePoint timePoint) throws NoWindException; @@ -306,7 +306,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * computational effort compared to fetching all entries separately, particularly because all * {@link #isDiscarded(Competitor, RaceColumn, TimePoint) discarded races} of a competitor are computed in one * sweep using {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint)} only once. - * Note that in order to get the {@link #getTotalPoints(Competitor, TimePoint) total points} for a competitor + * Note that in order to get the {@link #getNetPoints(Competitor, TimePoint) total points} for a competitor * for the entire leaderboard, the {@link #getCarriedPoints(Competitor) carried-over points} need to be added. */ Map, Entry> getContent(TimePoint timePoint) throws NoWindException; @@ -326,7 +326,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { /** * A leaderboard can carry over points from races that are not tracked by this leaderboard in detail, * so for which no {@link RaceColumn} column is present in this leaderboard. These scores are - * simply added to the scores tracked by this leaderboard in the {@link #getTotalPoints(Competitor, TimePoint)} + * simply added to the scores tracked by this leaderboard in the {@link #getNetPoints(Competitor, TimePoint)} * method. */ void setCarriedPoints(Competitor competitor, double carriedPoints); @@ -370,7 +370,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * that column shall be considered for discarding and counts for determining the number of races so far. Also, if a * tracked race is connected to the column and has started already, the column is to be considered for discarding * unless the column has several unordered fleets and not all fleets have started their race yet (see - * {@link ScoringScheme#isValidInTotalScore(Leaderboard, RaceColumn, Competitor, TimePoint)}). + * {@link ScoringScheme#isValidInNetScore(Leaderboard, RaceColumn, Competitor, TimePoint)}). */ boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint); @@ -458,14 +458,14 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint); /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, TimePoint)}, only that for determining the discarded + * Same as {@link #getNetPoints(Competitor, RaceColumn, TimePoint)}, only that for determining the discarded * results only raceColumnsToConsider are considered. */ - Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, Iterable raceColumnsToConsider, + Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException; /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can + * Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can * be specified which is useful when total points are to be computed for more than one column for the same * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done * once and not again for each column (which would lead to quadratic effort). @@ -475,7 +475,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * {@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. */ - Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, + Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns); TimePoint getNowMinusDelay(); @@ -522,7 +522,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { */ LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection namesOfRaceColumnsForWhichToLoadLegDetails, - boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, + boolean addOverallDetails, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillTotalPointsUncorrected) throws NoWindException, InterruptedException, ExecutionException; NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java index a24a15b96db..1ed0b8a0c4c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java @@ -96,13 +96,13 @@ public interface ScoringScheme extends Serializable { /** * Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used - * for the leaderboard's total scores. This may, e.g., be the case if a column is split into more than one fleet and + * for the leaderboard's net scores. This may, e.g., be the case if a column is split into more than one fleet and * those fleets are unordered. In that case, scores need to be available for all fleets before the column counts - * for the total scores. Another example is a scoring scheme that defines elimination rounds and awards no points + * for the net scores. Another example is a scoring scheme that defines elimination rounds and awards no points * to a competitor in a round from which the competitor got promoted to the next round. Such promotion columns * then have no scores and don't count in the number of races starting from where discards are applied.

*/ - boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at); + boolean isValidInNetScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at); /** * Some scoring schemes are applied to {@link LeaderboardGroupMetaLeaderboard} instances. These instances of a diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java index b1ba96700a0..a2dbb03795c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LeaderboardDTOCache.java @@ -166,7 +166,7 @@ public class LeaderboardDTOCache implements LeaderboardCache { try { LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, - waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false); + waitForLatestAnalyses, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false); return result; } finally { LockUtil.unpropagateLockSetFrom(callerThread); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java index fca7707a2f4..c1b211c7d3b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java @@ -297,7 +297,7 @@ public class LiveLeaderboardUpdater implements Runnable { final boolean addOverallDetails = getOverallDetails(timePoint); LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, - /* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory, /* fillNetPointsUncorrected */ false); + /* waitForLatestAnalyses */false, trackedRegattaRegistry, baseDomainFactory, /* fillTotalPointsUncorrected */ false); updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, newCacheValue); } catch (NoWindException e) { logger.log(Level.SEVERE, "Exception during re-calculating the live leaderboard "+leaderboard.getName(), e); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java index 7505bb92988..98e4dcb9c60 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java @@ -122,15 +122,15 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP List randomAccessLeaderboardList = new ArrayList(Util.size(overallLeaderboard.getLeaderboards())); Util.addAll(overallLeaderboard.getLeaderboards(), randomAccessLeaderboardList); for (Leaderboard leaderboardInOverall : overallLeaderboard.getLeaderboards()) { - o1PointsInLeaderboardsOfTheGroup.add(leaderboardInOverall.getTotalPoints(o1, timePoint)); - o2PointsInLeaderboardsOfTheGroup.add(leaderboardInOverall.getTotalPoints(o2, timePoint)); + o1PointsInLeaderboardsOfTheGroup.add(leaderboardInOverall.getNetPoints(o1, timePoint)); + o2PointsInLeaderboardsOfTheGroup.add(leaderboardInOverall.getNetPoints(o2, timePoint)); } int localResult = 0; for (ListIterator reverseLeaderbaordIterator=randomAccessLeaderboardList.listIterator(randomAccessLeaderboardList.size()); reverseLeaderbaordIterator.hasPrevious(); ) { Leaderboard leaderboardInOverall = reverseLeaderbaordIterator.previous(); - final Double o1PointsForLeaderboard = leaderboardInOverall.getTotalPoints(o1, timePoint); - final Double o2PointsForLeaderboard = leaderboardInOverall.getTotalPoints(o2, timePoint); + final Double o1PointsForLeaderboard = leaderboardInOverall.getNetPoints(o1, timePoint); + final Double o2PointsForLeaderboard = leaderboardInOverall.getNetPoints(o2, timePoint); if (o1PointsForLeaderboard != null && o2PointsForLeaderboard != null) { // we're in a scheme where points never get 0 so we can safely assume // that the last total points that are no 0 are the ones that we want to diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index 793934f0919..65fe5bcaad6 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -78,7 +78,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { * more races. */ @Override - public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { + public boolean isValidInNetScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { boolean result; Iterable fleets = raceColumn.getFleets(); if (Util.size(fleets) <= 1 || allFleetsOrdered(fleets)) { @@ -101,7 +101,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { for (Competitor competitor : leaderboard.getCompetitors()) { Fleet fleet = raceColumn.getFleetOfCompetitor(competitor); if (fleetsForWhichNoScoreWasFound.contains(fleet)) { - if (leaderboard.getNetPoints(competitor, raceColumn, at) != null) { + if (leaderboard.getTotalPoints(competitor, raceColumn, at) != null) { fleetsForWhichNoScoreWasFound.remove(fleet); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index 8c9e08365df..336895a9a6f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -130,7 +130,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race /** * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points - * provided by this map are considered by {@link #getTotalPoints(Competitor, TimePoint)}. + * provided by this map are considered by {@link #getNetPoints(Competitor, TimePoint)}. */ private final Map carriedPoints; @@ -172,23 +172,23 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race */ class EntryImpl implements Entry { private final Callable trackedRankProvider; + private final Double totalPoints; + private final Callable totalPointsUncorrectedProvider; + private final boolean isTotalPointsCorrected; private final Double netPoints; - private final Callable netPointsUncorrectedProvider; - private final boolean isNetPointsCorrected; - private final Double totalPoints; private final MaxPointsReason maxPointsReason; private final boolean discarded; private final Fleet fleet; - private EntryImpl(Callable trackedRankProvider, Double netPoints, - Callable netPointsUncorrectedProvider, boolean isNetPointsCorrected, - Double totalPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { + private EntryImpl(Callable trackedRankProvider, Double totalPoints, + Callable totalPointsUncorrectedProvider, boolean isTotalPointsCorrected, + Double netPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { super(); this.trackedRankProvider = trackedRankProvider; + this.totalPoints = totalPoints; + this.totalPointsUncorrectedProvider = totalPointsUncorrectedProvider; + this.isTotalPointsCorrected = isTotalPointsCorrected; this.netPoints = netPoints; - this.netPointsUncorrectedProvider = netPointsUncorrectedProvider; - this.isNetPointsCorrected = isNetPointsCorrected; - this.totalPoints = totalPoints; this.maxPointsReason = maxPointsReason; this.discarded = discarded; this.fleet = fleet; @@ -202,16 +202,16 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } } @Override - public Double getNetPoints() { - return netPoints; + public Double getTotalPoints() { + return totalPoints; } @Override - public boolean isNetPointsCorrected() { - return isNetPointsCorrected; + public boolean isTotalPointsCorrected() { + return isTotalPointsCorrected; } @Override - public Double getTotalPoints() { - return totalPoints; + public Double getNetPoints() { + return netPoints; } @Override public MaxPointsReason getMaxPointsReason() { @@ -226,9 +226,9 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race return fleet; } @Override - public Double getNetPointsUncorrected() { + public Double getTotalPointsUncorrected() { try { - return netPointsUncorrectedProvider.call(); + return totalPointsUncorrectedProvider.call(); } catch (Exception e) { throw new RuntimeException(e); } @@ -437,7 +437,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Double getNetPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { + public Double getTotalPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { return getScoreCorrection().getCorrectedScore( ()->getTrackedRank(competitor, raceColumn, timePoint), competitor, raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); @@ -462,7 +462,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race /** * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same + * be specified which is useful when net points are to be computed for more than one column for the same * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done * once and not again for each column (which would lead to quadratic effort). * @@ -479,21 +479,21 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - return getTotalPoints(competitor, raceColumn, getRaceColumns(), timePoint); + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + return getNetPoints(competitor, raceColumn, getRaceColumns(), timePoint); } @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { final Set discardedRaceColumns = getResultDiscardingRule() .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); - return getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns); } /** - * Same as {@link #getTotalPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when total points are to be computed for more than one column for the same + * Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can + * be specified which is useful when net points are to be computed for more than one column for the same * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done * once and not again for each column (which would lead to quadratic effort). * @@ -503,28 +503,28 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. */ @Override - public Double getTotalPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { Double result; if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { result = 0.0; } else { - final Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints == null) { + final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint); + if (totalPoints == null) { result = null; } else { - result = raceColumn.getFactor() * netPoints; + result = raceColumn.getFactor() * totalPoints; } } return result; } @Override - public Double getTotalPoints(Competitor competitor, TimePoint timePoint) { - return getTotalPoints(competitor, getRaceColumns(), timePoint); + public Double getNetPoints(Competitor competitor, TimePoint timePoint) { + return getNetPoints(competitor, getRaceColumns(), timePoint); } @Override - public Double getTotalPoints(Competitor competitor, final Iterable raceColumnsToConsider, + public Double getNetPoints(Competitor competitor, final Iterable raceColumnsToConsider, TimePoint timePoint) { // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on boolean needToResetScoreUponNextNonEmptyEntry = false; @@ -535,14 +535,14 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race if (raceColumn.isStartsWithZeroScore()) { needToResetScoreUponNextNonEmptyEntry = true; } - if (getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint)) { - final Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns); - if (totalPoints != null) { + if (getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint)) { + final Double netPoints = getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns); + if (netPoints != null) { if (needToResetScoreUponNextNonEmptyEntry) { result = 0; needToResetScoreUponNextNonEmptyEntry = false; } - result += totalPoints; + result += netPoints; } } } @@ -550,19 +550,19 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } /** - * All competitors with non-null net points are added to the result which is then sorted by net points in ascending - * order. The fleet, if ordered, is the primary ordering criterion, followed by the net points. + * All competitors with non-null total points are added to the result which is then sorted by total points in ascending + * order. The fleet, if ordered, is the primary ordering criterion, followed by the total points. */ @Override public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { - final Map> netPointsAndFleet = new HashMap>(); + final Map> totalPointsAndFleet = new HashMap>(); for (Competitor competitor : getCompetitors()) { - Double netPoints = getNetPoints(competitor, raceColumn, timePoint); - if (netPoints != null) { - netPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(netPoints, raceColumn.getFleetOfCompetitor(competitor))); + Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint); + if (totalPoints != null) { + totalPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(totalPoints, raceColumn.getFleetOfCompetitor(competitor))); } } - List result = new ArrayList(netPointsAndFleet.keySet()); + List result = new ArrayList(totalPointsAndFleet.keySet()); Collections.sort(result, new Comparator() { @Override public int compare(Competitor o1, Competitor o2) { @@ -573,8 +573,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { // only check fleets if there are more than one and the column is not to be contiguously scored even in case // of split fleets - final Fleet o1Fleet = netPointsAndFleet.get(o1).getB(); - final Fleet o2Fleet = netPointsAndFleet.get(o2).getB(); + final Fleet o1Fleet = totalPointsAndFleet.get(o1).getB(); + final Fleet o2Fleet = totalPointsAndFleet.get(o2).getB(); if (o1Fleet == null) { if (o2Fleet == null) { comparisonResult = 0; @@ -596,7 +596,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } if (comparisonResult == 0) { comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - netPointsAndFleet.get(o1).getA(), netPointsAndFleet.get(o2).getA()); + totalPointsAndFleet.get(o1).getA(), totalPointsAndFleet.get(o2).getA()); } } return comparisonResult; @@ -654,7 +654,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race @Override public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { TrackedRace trackedRaceForCompetitorInColumn; - return getScoringScheme().isValidInTotalScore(this, raceColumn, competitor, timePoint) && + return getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint) && (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && trackedRaceForCompetitorInColumn.hasStarted(timePoint) && @@ -703,7 +703,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } @Override - public Map> getTotalPointsSumAfterRaceColumn(final TimePoint timePoint) + public Map> getNetPointsSumAfterRaceColumn(final TimePoint timePoint) throws NoWindException { final Map> result = new LinkedHashMap<>(); List raceColumnsToConsider = new ArrayList<>(); @@ -714,12 +714,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race futures.put(raceColumn, executor.submit(new Callable>() { @Override public Map call() { - Map totalPointsSumPerCompetitorInColumn = new HashMap<>(); + Map netPointsSumPerCompetitorInColumn = new HashMap<>(); for (Competitor competitor : getCompetitors()) { - totalPointsSumPerCompetitorInColumn.put(competitor, getTotalPoints(competitor, finalRaceColumnsToConsider, timePoint)); + netPointsSumPerCompetitorInColumn.put(competitor, getNetPoints(competitor, finalRaceColumnsToConsider, timePoint)); } synchronized (result) { - return totalPointsSumPerCompetitorInColumn; + return netPointsSumPerCompetitorInColumn; } } })); @@ -1205,7 +1205,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race public LeaderboardDTO computeDTO(final TimePoint timePoint, final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, - final boolean fillNetPointsUncorrected) + final boolean fillTotalPointsUncorrected) throws NoWindException { long startOfRequestHandling = System.currentTimeMillis(); final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); @@ -1313,7 +1313,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race row.competitor = competitorDTO; row.fieldsByRaceColumnName = new HashMap(); row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null; - row.totalPoints = this.getTotalPoints(competitor, timePoint); + row.netPoints = this.getNetPoints(competitor, timePoint); if (addOverallDetails) { addOverallDetailsToRow(timePoint, competitor, row); } @@ -1327,7 +1327,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race namesOfRaceColumnsForWhichToLoadLegDetails != null && namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn .getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory, - fillNetPointsUncorrected, cache); + fillTotalPointsUncorrected, cache); }); executor.execute(future); futuresForColumnName.put(raceColumn.getName(), future); @@ -1403,8 +1403,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race * if false, this method is allowed to read the maneuver analysis results from a cache that * may not reflect all data already received; otherwise, the method will always block for the latest * cache updates to have happened before returning. - * @param fillNetPointsUncorrected - * tells if {@link LeaderboardEntryDTO#netPointsUncorrected} shall be filled; filling it is rather + * @param fillTotalPointsUncorrected + * tells if {@link LeaderboardEntryDTO#totalPointsUncorrected} shall be filled; filling it is rather * expensive, especially when compared to simply retrieving a score correction, and particularly if in a * larger fleet a number of competitors haven't properly finished the race. This should only be used for * leaderboard editing where a user needs to see what the uncorrected score was that would be used when @@ -1413,16 +1413,16 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor, TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses, Map> legRanksCache, DomainFactory baseDomainFactory, - boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) { + boolean fillTotalPointsUncorrected, WindLegTypeAndLegBearingCache cache) { LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO(); TrackedRace trackedRace = raceColumn.getTrackedRace(competitor); entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier(); + entryDTO.totalPoints = entry.getTotalPoints(); + if (fillTotalPointsUncorrected) { + entryDTO.totalPointsUncorrected = entry.getTotalPointsUncorrected(); + } + entryDTO.totalPointsCorrected = entry.isTotalPointsCorrected(); entryDTO.netPoints = entry.getNetPoints(); - if (fillNetPointsUncorrected) { - entryDTO.netPointsUncorrected = entry.getNetPointsUncorrected(); - } - entryDTO.netPointsCorrected = entry.isNetPointsCorrected(); - entryDTO.totalPoints = entry.getTotalPoints(); entryDTO.reasonForMaxPoints = entry.getMaxPointsReason(); entryDTO.discarded = entry.isDiscarded(); final GPSFixTrack track = trackedRace == null ? null : trackedRace.getTrack(competitor); @@ -1882,7 +1882,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race @Override public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillNetPointsUncorrected) throws NoWindException, + TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory, boolean fillTotalPointsUncorrected) throws NoWindException, InterruptedException, ExecutionException { LeaderboardDTO result = null; if (timePoint == null) { @@ -1892,7 +1892,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race // when the tracked race associations or score corrections or tracked race contents changes: final TimePoint nowMinusDelay = this.getNowMinusDelay(); final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification(); - if (fillNetPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { + if (fillTotalPointsUncorrected || (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification))) { // if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode // and pull the result from the regular leaderboard cache: timePoint = timePointOfLatestModification; @@ -1903,10 +1903,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } } if (timePoint != null) { - if (fillNetPointsUncorrected) { - // explicitly filling the uncorrected net points requires uncached recalculation + if (fillTotalPointsUncorrected) { + // explicitly filling the uncorrected total points requires uncached recalculation result = computeDTO(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, /* waitForLatestAnalyses */ true, - trackedRegattaRegistry, baseDomainFactory, fillNetPointsUncorrected); + trackedRegattaRegistry, baseDomainFactory, fillTotalPointsUncorrected); } else { // in replay we'd like up-to-date results; they are still cached // which is OK because the cache is invalidated whenever any of the tracked races attached to the diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java index 54db8e785e6..a6eb7de1f08 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java @@ -74,7 +74,7 @@ public class HighPoint extends AbstractScoringSchemeImpl { } @Override - public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { + public boolean isValidInNetScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { return true; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java index def8cd5503f..b2d40b3bb9e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java @@ -51,8 +51,8 @@ import com.sap.sse.common.Util.Pair; public class LeaderboardTotalRankComparator implements Comparator { private final Leaderboard leaderboard; private final ScoringScheme scoringScheme; - private final Map, Double> totalPointsCache; private final Map, Double> netPointsCache; + private final Map, Double> totalPointsCache; private final boolean nullScoresAreBetter; private final TimePoint timePoint; @@ -82,15 +82,15 @@ public class LeaderboardTotalRankComparator implements Comparator { this.timePoint = timePoint; this.scoringScheme = scoringScheme; this.nullScoresAreBetter = nullScoresAreBetter; - totalPointsCache = new HashMap, Double>(); netPointsCache = new HashMap, Double>(); + totalPointsCache = new HashMap, Double>(); for (Competitor competitor : leaderboard.getCompetitors()) { Set discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns( competitor, leaderboard, raceColumnsToConsider, timePoint); for (RaceColumn raceColumn : raceColumnsToConsider) { Pair key = new Util.Pair(competitor, raceColumn); - totalPointsCache.put(key, leaderboard.getTotalPoints(competitor, raceColumn, timePoint, discardedRaceColumns)); - netPointsCache.put(key, leaderboard.getNetPoints(competitor, raceColumn, timePoint)); + netPointsCache.put(key, leaderboard.getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns)); + totalPointsCache.put(key, leaderboard.getTotalPoints(competitor, raceColumn, timePoint)); } } } @@ -103,8 +103,8 @@ public class LeaderboardTotalRankComparator implements Comparator { public int compare(Competitor o1, Competitor o2) { List> o1Scores = new ArrayList<>(); List> o2Scores = new ArrayList<>(); - List> o1NetPoints = new ArrayList<>(); - List> o2NetPoints = new ArrayList<>(); + List> o1TotalPoints = new ArrayList<>(); + List> o2TotalPoints = new ArrayList<>(); double o1ScoreSum = getLeaderboard().getCarriedPoints(o1); double o2ScoreSum = getLeaderboard().getCarriedPoints(o2); Double o1MedalRaceScore = 0.0; @@ -116,12 +116,12 @@ public class LeaderboardTotalRankComparator implements Comparator { for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) { needToResetO1ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore(); needToResetO2ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore(); - final boolean o1ValidInTotalScore = getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, o1, timePoint); - final boolean o2ValidInTotalScore = getLeaderboard().getScoringScheme().isValidInTotalScore(getLeaderboard(), raceColumn, o2, timePoint); + final boolean o1ValidInNetScore = getLeaderboard().getScoringScheme().isValidInNetScore(getLeaderboard(), raceColumn, o1, timePoint); + final boolean o2ValidInNetScore = getLeaderboard().getScoringScheme().isValidInNetScore(getLeaderboard(), raceColumn, o2, timePoint); final Double o1Score; - if (o1ValidInTotalScore) { + if (o1ValidInNetScore) { Pair key = new Util.Pair<>(o1, raceColumn); - o1Score = totalPointsCache.get(key); + o1Score = netPointsCache.get(key); if (o1Score != null) { o1Scores.add(new Util.Pair<>(raceColumn, o1Score)); if (needToResetO1ScoreUponNextValidResult) { @@ -130,17 +130,17 @@ public class LeaderboardTotalRankComparator implements Comparator { } o1ScoreSum += o1Score; } - final Double o1Net = netPointsCache.get(key); - if (o1Net != null) { - o1NetPoints.add(new Util.Pair<>(raceColumn, o1Net)); + final Double o1Total = totalPointsCache.get(key); + if (o1Total != null) { + o1TotalPoints.add(new Util.Pair<>(raceColumn, o1Total)); } } else { o1Score = null; } final Double o2Score; - if (o2ValidInTotalScore) { + if (o2ValidInNetScore) { Pair key = new Util.Pair(o2, raceColumn); - o2Score = totalPointsCache.get(key); + o2Score = netPointsCache.get(key); if (o2Score != null) { o2Scores.add(new Util.Pair(raceColumn, o2Score)); if (needToResetO2ScoreUponNextValidResult) { @@ -149,14 +149,14 @@ public class LeaderboardTotalRankComparator implements Comparator { } o2ScoreSum += o2Score; } - final Double o2Net = netPointsCache.get(key); - if (o2Net != null) { - o2NetPoints.add(new Util.Pair<>(raceColumn, o2Net)); + final Double o2Total = totalPointsCache.get(key); + if (o2Total != null) { + o2TotalPoints.add(new Util.Pair<>(raceColumn, o2Total)); } } else { o2Score = null; } - if (o1ValidInTotalScore && o2ValidInTotalScore) { + if (o1ValidInNetScore && o2ValidInNetScore) { int preemptiveColumnResult = 0; if (raceColumn.isMedalRace()) { // only count the score for the medal race score if it wasn't a carry-forward column @@ -192,7 +192,7 @@ public class LeaderboardTotalRankComparator implements Comparator { result = compareByBetterScore(o1, Collections.unmodifiableList(o1Scores), o2, Collections.unmodifiableList(o2Scores), timePoint); if (result == 0) { // compare by last race: - result = scoringScheme.compareByLastRace(o1NetPoints, o2NetPoints, nullScoresAreBetter); + result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter); if (result == 0) { try { result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint); @@ -209,7 +209,7 @@ public class LeaderboardTotalRankComparator implements Comparator { /** * Precondition: either both scored in medal race or both didn't. If both scored, the better score wins. - * This is to be applied only if the total score of both competitors are equal to each other. + * This is to be applied only if the net score of both competitors are equal to each other. */ private int compareByMedalRaceScore(Double o1MedalRaceScore, Double o2MedalRaceScore) { assert o1MedalRaceScore != null || o2MedalRaceScore == null; @@ -290,7 +290,7 @@ public class LeaderboardTotalRankComparator implements Comparator { } /** - * Assuming both competitors scored in the same number of races, and assuming they scored the same total score, + * Assuming both competitors scored in the same number of races, and assuming they scored the same net score, * break the tie according to the {@link #scoringScheme scoring scheme} set for this comparator. * @see ScoringScheme#compareByBetterScore(Competitor, List, Competitor, List, boolean, TimePoint) */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java index cea11c2c6e1..3c4a9e7c02b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java @@ -73,7 +73,7 @@ public class LowPoint extends AbstractScoringSchemeImpl { } @Override - public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { + public boolean isValidInNetScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { return true; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java index 282efc8d03c..d65c17f4235 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java @@ -46,12 +46,12 @@ public class LowPointForLeagueOverallLeaderboard extends LowPoint { assert Math.abs(o1ScoreSum - o2ScoreSum) < 0.00001; assert o1Scores.stream().allMatch(rcAndScore->rcAndScore.getA() instanceof MetaLeaderboardColumn); assert o2Scores.stream().allMatch(rcAndScore->rcAndScore.getA() instanceof MetaLeaderboardColumn); - double totalPointsSumO1 = getTotalPoints(o1, o1Scores.stream().map(rcAndScore->rcAndScore.getA()), timePoint); - double totalPointsSumO2 = getTotalPoints(o2, o1Scores.stream().map(rcAndScore->rcAndScore.getA()), timePoint); - return getScoreComparator(nullScoresAreBetter).compare(totalPointsSumO1, totalPointsSumO2); + double netPointsSumO1 = getNetPoints(o1, o1Scores.stream().map(rcAndScore->rcAndScore.getA()), timePoint); + double netPointsSumO2 = getNetPoints(o2, o1Scores.stream().map(rcAndScore->rcAndScore.getA()), timePoint); + return getScoreComparator(nullScoresAreBetter).compare(netPointsSumO1, netPointsSumO2); } - private double getTotalPoints(Competitor competitor, Stream raceColumns, TimePoint timePoint) { - return raceColumns.collect(Collectors.summingDouble(rc->((MetaLeaderboardColumn) rc).getLeaderboard().getTotalPoints(competitor, timePoint))); + private double getNetPoints(Competitor competitor, Stream raceColumns, TimePoint timePoint) { + return raceColumns.collect(Collectors.summingDouble(rc->((MetaLeaderboardColumn) rc).getLeaderboard().getNetPoints(competitor, timePoint))); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminationsAndRoundsWinnerGets07.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminationsAndRoundsWinnerGets07.java index 337e864477c..b9a17f1f586 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminationsAndRoundsWinnerGets07.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithEliminationsAndRoundsWinnerGets07.java @@ -187,7 +187,7 @@ public class LowPointWithEliminationsAndRoundsWinnerGets07 extends LowPoint { } @Override - public boolean isValidInTotalScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { + public boolean isValidInNetScore(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, TimePoint at) { final RaceColumnInSeries raceColumnInSeries = (RaceColumnInSeries) raceColumn; final Iterator seriesInRegattaIter = raceColumnInSeries.getSeries().getRegatta().getSeries().iterator(); while (seriesInRegattaIter.hasNext() && seriesInRegattaIter.next() != raceColumnInSeries.getSeries()) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ThresholdBasedResultDiscardingRuleImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ThresholdBasedResultDiscardingRuleImpl.java index 3cd4a1da53a..122974ac73c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ThresholdBasedResultDiscardingRuleImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ThresholdBasedResultDiscardingRuleImpl.java @@ -79,12 +79,12 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes int resultsToDiscard = getNumberOfResultsToDiscard(competitor, raceColumnsToConsider, leaderboard, timePoint); Set result; if (resultsToDiscard > 0) { - final Map netPointsForCompetitorPerColumn = new HashMap<>(); + final Map totalPointsForCompetitorPerColumn = new HashMap<>(); List sortedRaces = new ArrayList(); for (RaceColumn raceColumn : raceColumnsToConsider) { if (raceColumn.isDiscardable()) { sortedRaces.add(raceColumn); - netPointsForCompetitorPerColumn.put(raceColumn, leaderboard.getNetPoints(competitor, raceColumn, timePoint)); + totalPointsForCompetitorPerColumn.put(raceColumn, leaderboard.getTotalPoints(competitor, raceColumn, timePoint)); } } result = new HashSet(); @@ -96,8 +96,8 @@ public class ThresholdBasedResultDiscardingRuleImpl implements ThresholdBasedRes return -leaderboard .getScoringScheme() .getScoreComparator(/* nullScoresAreBetter */true) - .compare(netPointsForCompetitorPerColumn.get(raceColumn1), - netPointsForCompetitorPerColumn.get(raceColumn2)); + .compare(totalPointsForCompetitorPerColumn.get(raceColumn1), + totalPointsForCompetitorPerColumn.get(raceColumn2)); } }; Collections.sort(sortedRaces, comparator); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java index bbe1b82fbdd..0a135da9c46 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java @@ -5,6 +5,7 @@ import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Timed; /** * Represent the possible {@link MarkPassing} of a {@link Waypoint}. It contains the {@link Waypoint} it might be passing, a @@ -16,14 +17,12 @@ import com.sap.sse.common.TimePoint; * @author Nicolas Klose * */ -public interface Candidate extends Comparable { +public interface Candidate extends Comparable, Timed { Waypoint getWaypoint(); int getOneBasedIndexOfWaypoint(); - TimePoint getTimePoint(); - /** * @return the probability that this actually is a passing. */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java index 6ffbe795fb9..797237e2fd1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.markpassingcalculation; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -9,18 +10,16 @@ import com.sap.sse.common.TimePoint; public interface CandidateChooser { /** - * Calculates any new {@link MarkPassing}s and notifies the {@link DynamicTrackedRace}. + * Calculates any new {@link MarkPassing}s and notifies the {@link DynamicTrackedRace} using the + * {@link DynamicTrackedRace#updateMarkPassings(Competitor, Iterable)} method. * * @param candidateDeltas * new {@link CandidateImpl}s and those that should be removed. */ - public void calculateMarkPassDeltas(Competitor c, Iterable newCans, Iterable oldCans); void removeWaypoints(Iterable ways); - void addWaypoints(Iterable waypoints); - void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t); void removeFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint); @@ -29,5 +28,9 @@ public interface CandidateChooser { void stopSuppressingMarkPassings(Competitor c); - + /** + * Updates the end proxy node's waypoint index according to the current course. Precondition: + * the caller holds the {@link Course}'s read lock. + */ + void updateEndProxyNodeWaypointIndex(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index 18c46ce1ac6..85ae0e2e1f0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -7,12 +7,15 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.tracking.GPSFix; +import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; /** * Converts the incoming GPSFixes of competitors and marks into {@link Candidate}s for each competitor. * * @author Nicolas Klose + * @author Axel Uhl (d043530) */ public interface CandidateFinder { /** @@ -26,6 +29,8 @@ public interface CandidateFinder { /** * When initializing or refreshing the calculator, the whole race until now is evaluated. For that purpose all of the * {@link Candidate}s are needed instead of just the deltas. + * + * @return new {@link Candidate}s and those that should be removed */ Util.Pair, Iterable> getAllCandidates(Competitor c); @@ -36,4 +41,25 @@ public interface CandidateFinder { * mark fixes.. */ Map> calculateFixesAffectedByNewMarkFixes(Map> newMarkFixes); + + /** + * Notifies this finder about the race's start time having changed. The finder is only interested in the non-inferred + * start time and double-checks whether this non-inferred start time has changed. If so, the candidates collections + * are adjusted accordingly: new candidates may be added if the start time was moved towards the past and therefore + * extends the time range for candidates; candidates are removed if the start time was moved to a later point in time + * and the candidate is no longer in the time range valid for mark passings. The candidates added and removed are + * returned in a map keyed by the competitors. The {@link Pair#getA() first} element of the value pair has the + * candidates added for this competitor, the {@link Pair#getB() second} element has the candidates removed. + */ + Map, Iterable>> getCandidateDeltasAfterRaceStartTimeChange(); + + /** + * Notifies this finder about the race's finished time having changed. The candidates collections are adjusted + * accordingly: new candidates may be added if the finished time was moved to a later point in time and therefore + * extends the time range for candidates; candidates are removed if the finished time was moved to an earlier point + * in time and the candidate is no longer in the time range valid for mark passings. The candidates added and + * removed are returned in a map keyed by the competitors. The {@link Pair#getA() first} element of the value pair + * has the candidates added for this competitor, the {@link Pair#getB() second} element has the candidates removed. + */ + Map, Iterable>> getCandidateDeltasAfterRaceFinishedTimeChange(TimePoint oldFinishedTime, TimePoint newFinishedTime); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 6e5837f52fc..7863238dbba 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -17,6 +17,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.tracking.GPSFix; @@ -58,6 +59,20 @@ public class MarkPassingCalculator { /* workQueue */new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1, /* daemon */ true)); private boolean suspended = false; + + /** + * If the constructor is called with the {@code listen} parameter set to {@code true}, this field will hold a thread + * when the constructor has terminated normally. The thread may not yet have been started because, depending on the + * constructor's {@code waitForInitialMarkPassingCalculation} argument, start-up may be asynchronous, but eventually + * it will be started at some point. The {@link #waitUntilStopped} method can be used to wait until the thread has + * started and then terminated again. + */ + private final Thread listenerThread; + + /** + * Synchronized using the {@link #listenerThread} as monitor object. + */ + private boolean listenerThreadStarted; public MarkPassingCalculator(DynamicTrackedRace race, boolean listen, boolean waitForInitialMarkPassingCalculation) { if (listen) { @@ -66,17 +81,34 @@ public class MarkPassingCalculator { listener = null; } this.race = race; - finder = new CandidateFinderImpl(race); + finder = new CandidateFinderImpl(race, executor); chooser = new CandidateChooserImpl(race); + if (listen) { + listenerThread = new Thread(new Listen(race.getRace().getName()), "MarkPassingCalculator for race " + race.getRace().getName()); + } else { + listenerThread = null; + } Thread t = new Thread(() -> { + final Set> tasks = new HashSet<>(); for (Competitor c : race.getRace().getCompetitors()) { - Util.Pair, Iterable> allCandidates = finder.getAllCandidates(c); - chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB()); + tasks.add(()->{ + Util.Pair, Iterable> allCandidates = finder.getAllCandidates(c); + chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB()); + return null; + }); + } + try { + executor.invokeAll(tasks); + } catch (Exception e) { + logger.log(Level.SEVERE, "Error trying to compute initial set of mark passings for race "+race.getRace().getName(), e); } if (listen) { - final Thread listenerThread = new Thread(new Listen(race.getRace().getName()), "MarkPassingCalculator for race " + race.getRace().getName()); listenerThread.setDaemon(true); listenerThread.start(); + synchronized (listenerThread) { + listenerThreadStarted = true; + listenerThread.notifyAll(); + } } }, "MarkPassingCalculator for race "+race.getRace().getName()+" initialization"); if (waitForInitialMarkPassingCalculation) { @@ -85,6 +117,27 @@ public class MarkPassingCalculator { t.start(); } } + + /** + * Waits for the listening thread to terminate. If no listening thread has been requested ({@code listen} constructor + * parameter {@code false}), the method returns immediately. Otherwise it waits for the listening thread to have started + * and then joins on the thread, using the timeout specified.

+ * + * This method can be useful for test cases that want to wait until all calculations have terminated for sure. + * + * @param timeoutInMillis same as for {@link Thread#join(long)}. + */ + public void waitUntilStopped(final long timeoutInMillis) throws InterruptedException { + final long start = System.currentTimeMillis(); + if (listenerThread != null) { + synchronized (listenerThread) { + while (!listenerThreadStarted && System.currentTimeMillis()-start < timeoutInMillis) { + listenerThread.wait(timeoutInMillis); + } + } + listenerThread.join(timeoutInMillis); + } + } /** * Waits until an object is in the queue, then drains it entirely. After that the information is sorted depending on @@ -136,20 +189,35 @@ public class MarkPassingCalculator { } else { fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, - suppressedMarkPassings, unsuppressedMarkPassings); + suppressedMarkPassings, unsuppressedMarkPassings, finder, chooser); } } if (!finished && !suspended) { if (smallestChangedWaypointIndex.value != -1) { - Map, List>> candidateDeltas = finder - .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex.value); - chooser.removeWaypoints(removedWaypoints); - chooser.addWaypoints(addedWaypoints); - for (Entry, List>> entry : candidateDeltas - .entrySet()) { - Util.Pair, List> pair = entry.getValue(); - chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + final Course course = race.getRace().getCourse(); + final Map, List>> candidateDeltas; + // obtain the course's read lock so that the finder creates the candidate deltas under the + // premise of the same course as the chooser updates its end proxy node's index; they have + // to be consistent. See also bug 3657. + course.lockForRead(); + try { + candidateDeltas = finder.updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex.value); + chooser.updateEndProxyNodeWaypointIndex(); + } finally { + course.unlockAfterRead(); } + if (!removedWaypoints.isEmpty()) { + chooser.removeWaypoints(removedWaypoints); + } + Set> tasks = new HashSet<>(); + for (Entry, List>> entry : candidateDeltas.entrySet()) { + tasks.add(()->{ + Util.Pair, List> pair = entry.getValue(); + chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + return null; + }); + } + executor.invokeAll(tasks); } updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, unsuppressedMarkPassings); @@ -278,7 +346,7 @@ public class MarkPassingCalculator { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { } }); } @@ -288,7 +356,7 @@ public class MarkPassingCalculator { } public void recalculateEverything() { - finder = new CandidateFinderImpl(race); + finder = new CandidateFinderImpl(race, executor); chooser = new CandidateChooserImpl(race); for (Competitor c : race.getRace().getCompetitors()) { Util.Pair, Iterable> allCandidates = finder.getAllCandidates(c); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java index fd08651b813..4c0ccc67f2f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.markpassingcalculation; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Logger; @@ -39,7 +40,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { Map> markFixes, List addedWaypoints, List removedWaypoints, IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { } }; @@ -50,7 +51,6 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { raceName = race.getRace().getName(); queue = new LinkedBlockingQueue<>(); race.addListener(this); - race.getRace().getCourse().addCourseListener(this); } public BlockingQueue getQueue() { @@ -66,7 +66,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { List list = competitorFixes.get(competitor); if (list == null) { list = new ArrayList<>(); @@ -86,7 +86,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { List list = markFixes.get(mark); if (list == null) { list = new ArrayList<>(); @@ -115,9 +115,9 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { addedWaypoints.add(waypointThatGotAdded); - if (smallestChangedWaypointIndex.value == -1|| smallestChangedWaypointIndex.value > zeroBasedIndex) { + if (smallestChangedWaypointIndex.value == -1 || smallestChangedWaypointIndex.value > zeroBasedIndex) { smallestChangedWaypointIndex.value = zeroBasedIndex; } } @@ -133,7 +133,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { removedWaypoints.add(waypointThatGotRemoved); if (smallestChangedWaypointIndex.value == -1 || smallestChangedWaypointIndex.value > zeroBasedIndex) { smallestChangedWaypointIndex.value = zeroBasedIndex; @@ -151,7 +151,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { fixedMarkPassings.add(new Triple(c, zeroBasedIndexOfWaypoint, timePointOfFixedPassing)); } @@ -166,7 +166,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { removedMarkPassings.add(new Pair(c, zeroBasedIndexOfWaypoint)); } @@ -181,7 +181,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { suppressedMarkPassings.add(new Pair(c, zeroBasedIndexOfWaypoint)); } @@ -196,9 +196,47 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { unSuppressedMarkPassings.add(c); } }); } + + @Override + public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, + Map> markFixes, List addedWaypoints, List removedWaypoints, + IntHolder smallestChangedWaypointIndex, + List> fixedMarkPassings, + List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { + final Map, Iterable>> newAndRemovedCandidatesPerCompetitor = + candidateFinder.getCandidateDeltasAfterRaceStartTimeChange(); + for (final Entry, Iterable>> i : newAndRemovedCandidatesPerCompetitor.entrySet()) { + candidateChooser.calculateMarkPassDeltas(i.getKey(), i.getValue().getA(), i.getValue().getB()); + } + } + }); + } + + @Override + public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, + Map> markFixes, List addedWaypoints, List removedWaypoints, + IntHolder smallestChangedWaypointIndex, + List> fixedMarkPassings, + List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser) { + final Map, Iterable>> newAndRemovedCandidatesPerCompetitor = + candidateFinder.getCandidateDeltasAfterRaceFinishedTimeChange(oldFinishedTime, newFinishedTime); + for (final Entry, Iterable>> i : newAndRemovedCandidatesPerCompetitor.entrySet()) { + candidateChooser.calculateMarkPassDeltas(i.getKey(), i.getValue().getA(), i.getValue().getB()); + } + } + }); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java index 89e9ed55e29..4e15ba5898b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java @@ -24,5 +24,5 @@ public interface StorePositionUpdateStrategy { void storePositionUpdate(Map> competitorFixes, Map> markFixes, List addedWaypoints, List removedWaypoints, IntHolder smallestChangedWaypointIndex, List> fixedMarkPassings, List> removedMarkPassings, - List> suppressedMarkPassings, List unSuppressedMarkPassings); + List> suppressedMarkPassings, List unSuppressedMarkPassings, CandidateFinder candidateFinder, CandidateChooser candidateChooser); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java index 657e33e83a1..272e6db711b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -56,7 +56,7 @@ public class CandidateChooserImpl implements CandidateChooser { * leg the candidates that require more distance than the minimum distance required receive an increasing penalty. * The maximum penalty for finishing candidates that have required * {@link #MAX_REASONABLE_RATIO_BETWEEN_DISTANCE_TRAVELED_AND_LEG_LENGTH} times the leg distance is expressed by - * this consttant. + * this constant. */ private static final double PENALTY_FOR_LATEST_FINISH_PASSING = 0.95; @@ -103,7 +103,6 @@ public class CandidateChooserImpl implements CandidateChooser { * {@link #EARLY_STARTS_CONSIDERED_THIS_MUCH_BEFORE_STARTTIME} milliseconds before the race start time or null * in case the race start time is not known. */ - private final CandidateWithSettableTime start; private final CandidateWithSettableWaypointIndex end; private final DynamicTrackedRace race; @@ -185,12 +184,11 @@ public class CandidateChooserImpl implements CandidateChooser { currentMarkPasses.get(c).remove(w); } } - end.setOneBasedWaypointIndex(end.getOneBasedIndexOfWaypoint()-Util.size(waypoints)); } @Override - public void addWaypoints(Iterable waypoints) { - end.setOneBasedWaypointIndex(end.getOneBasedIndexOfWaypoint()+Util.size(waypoints)); + public void updateEndProxyNodeWaypointIndex() { + end.setOneBasedWaypointIndex(race.getRace().getCourse().getNumberOfWaypoints()+1); } @Override @@ -561,7 +559,8 @@ public class CandidateChooserImpl implements CandidateChooser { } } - private class CandidateWithSettableTime extends CandidateImpl { + private static class CandidateWithSettableTime extends CandidateImpl { + private static final long serialVersionUID = -1792983349299883266L; private TimePoint variableTimePoint; public CandidateWithSettableTime(int oneBasedIndexOfWaypoint, TimePoint p, double distanceProbability, Waypoint w) { @@ -579,7 +578,8 @@ public class CandidateChooserImpl implements CandidateChooser { } } - private class CandidateWithSettableWaypointIndex extends CandidateImpl { + private static class CandidateWithSettableWaypointIndex extends CandidateImpl { + private static final long serialVersionUID = 5868551535609781722L; private int variableOneBasedWaypointIndex; public CandidateWithSettableWaypointIndex(int oneBasedIndexOfWaypoint, TimePoint p, double distanceProbability, Waypoint w) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java index 8af8dd018e7..36017b9736e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java @@ -5,6 +5,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -12,6 +13,13 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; @@ -33,10 +41,15 @@ import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.impl.TimedComparator; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Timed; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.util.impl.ThreadFactoryWithPriority; /** * The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all, @@ -46,6 +59,7 @@ import com.sap.sse.common.Util.Pair; * direction of its waypoint. * * @author Nicolas Klose + * @author Axel Uhl (d043530) * */ public class CandidateFinderImpl implements CandidateFinder { @@ -91,6 +105,19 @@ public class CandidateFinderImpl implements CandidateFinder { private static final double WORST_PENALTY_FOR_OTHER_COMPETITORS_BEING_FAR_FROM_START = 0.1; private static final double NUMBER_OF_HULL_LENGTHS_DISTANCE_FROM_START_AT_WHICH_WORST_PENALTY_APPLIES = 10; + + /** + * If a {@link TrackedRace#getStartOfRace(boolean)) non-inferred start time} is known for the {@link #race}, + * candidates are not accepted if they are more than this duration earlier than the start time. + */ + private static final Duration EARLIEST_START_MARK_PASSING_THIS_MUCH_BEFORE_START = Duration.ONE_MINUTE; + + /** + * If a {@link TrackedRace#getFinishedTime()) finished time} ("Blue Flag Down") is known for the {@link #race}, + * candidates are not accepted if they are more than this duration later than the time when the race has officially + * finished. + */ + private static final Duration LATEST_FINISH_MARK_PASSING_THIS_MUCH_AFTER_RACE_FINISHED = Duration.ONE_MINUTE.times(5); private static final Logger logger = Logger.getLogger(CandidateFinderImpl.class.getName()); @@ -101,18 +128,40 @@ public class CandidateFinderImpl implements CandidateFinder { private Map, Candidate>>> xteCandidates = new HashMap<>(); private Map>> distanceCandidates = new HashMap<>(); private final DynamicTrackedRace race; + + /** + * Fixes are not considered eligible as candidates if a non-inferred start time is known for the race and the fix is + * {@link #EARLIEST_START_MARK_PASSING_THIS_MUCH_BEFORE_START reasonably} earlier than this start time point. They + * are also ignored if a non-inferred finished time for the race is known and the fix is + * {@link #LATEST_FINISH_MARK_PASSING_THIS_MUCH_AFTER_RACE_FINISHED reasonably} after that time point. Always + * holds a non-{@code null} object after the constructor has terminated. + */ + private TimeRange timeRangeForValidCandidates; + private final double penaltyForSkipping = Edge.getPenaltyForSkipping(); private final Map passingInstructions = new LinkedHashMap<>(); - private final Comparator comp = new Comparator() { - @Override - public int compare(GPSFix arg0, GPSFix arg1) { - return arg0.getTimePoint().compareTo(arg1.getTimePoint()); - } - }; - + private final Comparator comp = TimedComparator.INSTANCE; + + private final ExecutorService executor; + + /** + * Like {@link #CandidateFinderImpl(DynamicTrackedRace, ExecutorService)} but creates a default executor service + */ public CandidateFinderImpl(DynamicTrackedRace race) { + this(race, new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime + .getRuntime().availableProcessors() - 1, 3), + /* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3), + /* keepAliveTime */60, TimeUnit.SECONDS, + /* workQueue */new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1, /* daemon */ true))); + } + + public CandidateFinderImpl(DynamicTrackedRace race, ExecutorService executor) { this.race = race; - RaceDefinition raceDefinition = race.getRace(); + this.executor = executor; + this.timeRangeForValidCandidates = new TimeRangeImpl( + getTimePointWhenToStartConsideringCandidates(race.getStartOfRace(/* inferred */ false)), + getTimePointWhenToFinishConsideringCandidates(race.getFinishedTime())); + final RaceDefinition raceDefinition = race.getRace(); for (Competitor c : raceDefinition.getCompetitors()) { xteCache.put(c, new LimitedLinkedHashMap>>(25)); distanceCache.put(c, new LimitedLinkedHashMap>>(25)); @@ -121,8 +170,106 @@ public class CandidateFinderImpl implements CandidateFinder { } } - private class LimitedLinkedHashMap extends LinkedHashMap { + /** + * For a given race start time {@code startOfRace} which is not inferred from any mark passings determines + * the earliest start mark passing time point. No candidates earlier than this time point need to be considered. + */ + private TimePoint getTimePointWhenToStartConsideringCandidates(TimePoint startOfRace) { + return startOfRace == null ? null : startOfRace.minus(EARLIEST_START_MARK_PASSING_THIS_MUCH_BEFORE_START); + } + + /** + * For a given race finished time {@code finishedTime} which is not inferred from any mark passings determines + * the latest finish mark passing time point. No candidates later than this time point need to be considered. + */ + private TimePoint getTimePointWhenToFinishConsideringCandidates(TimePoint finishedTime) { + return finishedTime == null ? null : finishedTime.plus(LATEST_FINISH_MARK_PASSING_THIS_MUCH_AFTER_RACE_FINISHED); + } + + /** + * Adds or removes candidates for a time range. If the {@code startOfRangeToAdd} is at or before + * {@code endOfRangeToAdd}, all fixes from this range for all competitors will be submitted for candidate analysis, + * and new candidates may emerge from this. If {@code startOfRangeToAdd} is after {@code endOfRangeToAdd}, all + * candidates within that (inverse) range are removed, and so are all edges attached to any of these candidates. + * + * @param startOfRangeToAdd + * the start of a range of fixes valid for candidate analysis if {@link TimePoint#before(TimePoint)} + * {@code endOfRangeToAdd}; the end of the now invalid time range of fixes no longer valid for candidate + * analysis otherwise; may be {@code null}, meaning that no start of the valid time range is known and + * therefore all fixes from the beginning of the track up to {@code endOfRangeToAdd} are to be analyzed + * @param endOfRangeToAdd + * the end of a range of fixes valid for candidate analysis if {@link TimePoint#after(TimePoint)} + * {@code startOfRangeToAdd}; the start of the now invalid time range of fixes no longer valid for + * candidate analysis otherwise; may be {@code null}, meaning that no end of the valid time range is + * known and therefore all fixes starting at {@code startOfRangeToAdd} until the end of the track are to + * be analyzed + * @return the fixes added and removed, keyed by competitor; the value pair's {@link Pair#getA() first} component + * has the candidates added, the {@link Pair#getB() second} has the candidates removed + */ + private Map, Iterable>> updateCandiatesAfterRaceTimeRangeChanged( + final TimePoint startOfRangeToAdd, final TimePoint endOfRangeToAdd) { + final Map, Iterable>> result = new HashMap<>(); + if (startOfRangeToAdd.after(endOfRangeToAdd)) { + final TimeRange timeRangeToRemoveCandidatesFrom = new TimeRangeImpl(endOfRangeToAdd, startOfRangeToAdd); + // remove candidates in the time range starting at endOfRangeToAdd and ending at startOfRangeToAdd: + final Map> candidatesToRemovePerCompetitor = new HashMap<>(); + removeCandidatesInTimeRange(timeRangeToRemoveCandidatesFrom, candidatesToRemovePerCompetitor, distanceCandidates); + removeCandidatesInTimeRange(timeRangeToRemoveCandidatesFrom, candidatesToRemovePerCompetitor, xteCandidates); + for (final Entry> e : candidatesToRemovePerCompetitor.entrySet()) { + result.put(e.getKey(), new Pair<>(Collections.emptySet(), e.getValue())); + } + } else { + for (final Competitor competitor : race.getRace().getCompetitors()) { + final List newFixesForCompetitor = new ArrayList<>(); + final DynamicGPSFixTrack track = race.getTrack(competitor); + if (track != null) { + track.lockForRead(); + try { + for (final Iterator i=track.getFixesIterator(startOfRangeToAdd, /* inclusive */ true); i.hasNext(); ) { + final GPSFixMoving fix = i.next(); + if (fix.getTimePoint().after(endOfRangeToAdd)) { + break; + } + newFixesForCompetitor.add(fix); + } + } finally { + track.unlockAfterRead(); + } + } + final Pair, Iterable> newAndRemovedCandidatesForCompetitor = getCandidateDeltas(competitor, newFixesForCompetitor); + result.put(competitor, newAndRemovedCandidatesForCompetitor); + } + } + return result; + } + /** + * @param candidatesRemovedPerCompetitor + * updated by this method; candidates removed from {@code candidatePerWaypointPerCompetitor} are added to + * this map, keyed by the respective competitor + */ + private void removeCandidatesInTimeRange(final TimeRange timeRangeToRemoveCandidatesFrom, + final Map> candidatesRemovedPerCompetitor, + Map>> candidatePerWaypointPerCompetitor) { + for (final Entry>> e : candidatePerWaypointPerCompetitor.entrySet()) { + Set candidatesRemoved = candidatesRemovedPerCompetitor.get(e.getKey()); + if (candidatesRemoved == null) { + candidatesRemoved = new HashSet<>(); + candidatesRemovedPerCompetitor.put(e.getKey(), candidatesRemoved); + } + for (final Map m : e.getValue().values()) { + for (final Iterator candidateIter=m.values().iterator(); candidateIter.hasNext(); ) { + final Candidate candidate = candidateIter.next(); + if (timeRangeToRemoveCandidatesFrom.includes(candidate.getTimePoint())) { + candidateIter.remove(); + candidatesRemoved.add(candidate); + } + } + } + } + } + + private class LimitedLinkedHashMap extends LinkedHashMap { private static final long serialVersionUID = 1L; private int limit; @@ -218,8 +365,13 @@ public class CandidateFinderImpl implements CandidateFinder { return new Util.Pair, Iterable>(newCans, wrongCans); } - private Map, List>> invalidateAfterCourseChange(int indexOfChange) { - Map, List>> result = new HashMap<>(); + /** + * determines the candidates that are new and those that have been removed, keyed by the competitors; the + * {@link Pair#getA() first} element of each value pair has the candidates that are new, the {@link Pair#getB() + * second} element has those that have been removed. + */ + private Map, List>> invalidateAfterCourseChange(int zeroBasedIndexOfWaypointChanged) { + ConcurrentHashMap, List>> result = new ConcurrentHashMap<>(); Course course = race.getRace().getCourse(); for (Competitor c : race.getRace().getCompetitors()) { distanceCache.get(c).clear(); @@ -229,26 +381,38 @@ public class CandidateFinderImpl implements CandidateFinder { course.lockForRead(); try { for (Waypoint w : course.getWaypoints()) { - if (course.getIndexOfWaypoint(w) > indexOfChange - 2) { + // The candidates for the predecessor of the first waypoint added/removed are also affected + // because their adjacent leg will have changed its direction, so roundings / passings have + // to be decided differently + // TODO but why are *all* subsequent waypoints "invalidated" this way? Wouldn't we by that same token only have to move to the successor of the last waypoint changed? + if (course.getIndexOfWaypoint(w) > zeroBasedIndexOfWaypointChanged - 2) { changedWaypoints.add(w); } } + final Set> tasks = new HashSet<>();; for (Competitor c : race.getRace().getCompetitors()) { - List badCans = new ArrayList<>(); - List newCans = new ArrayList<>(); - for (Waypoint w : changedWaypoints) { - Map, Candidate> xteCans = getXteCandidates(c, w); - badCans.addAll(xteCans.values()); - xteCans.clear(); - Map distanceCans = getDistanceCandidates(c, w); - badCans.addAll(distanceCans.values()); - distanceCans.clear(); - } - Set allFixes = getAllFixes(c); - newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA()); - newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA()); - result.put(c, new Util.Pair, List>(newCans, badCans)); + tasks.add(()->{ + List badCans = new ArrayList<>(); + List newCans = new ArrayList<>(); + for (Waypoint w : changedWaypoints) { + Map, Candidate> xteCans = getXteCandidates(c, w); + badCans.addAll(xteCans.values()); + xteCans.clear(); + Map distanceCans = getDistanceCandidates(c, w); + badCans.addAll(distanceCans.values()); + distanceCans.clear(); + } + Set allFixes = getAllFixes(c); + newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA()); + newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA()); + result.put(c, new Util.Pair, List>(newCans, badCans)); + return null; + }); } + executor.invokeAll(tasks); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "Problem trying to update competitor candidate sets after waypoints starting at zero-based index "+ + zeroBasedIndexOfWaypointChanged+" have changed", e); } finally { course.unlockAfterRead(); } @@ -256,6 +420,11 @@ public class CandidateFinderImpl implements CandidateFinder { return result; } + /** + * determines the candidates that are new and those that have been removed, keyed by the competitors; the + * {@link Pair#getA() first} element of each value pair has the candidates that are new, the {@link Pair#getB() + * second} element has those that have been removed. + */ @Override public Map, List>> updateWaypoints( Iterable addedWaypoints, Iterable removedWaypoints, int smallestIndex) { @@ -290,6 +459,10 @@ public class CandidateFinderImpl implements CandidateFinder { return result; } + /** + * @return a live collection of {@link #distanceCandidates distance candidates} for competitor {@code c} passing + * waypoint {@code w}. + */ private Map getDistanceCandidates(Competitor c, Waypoint w) { synchronized (distanceCandidates) { Map result = distanceCandidates.get(c).get(w); @@ -301,6 +474,10 @@ public class CandidateFinderImpl implements CandidateFinder { } } + /** + * @return a live collection of {@link #xteCandidates cross track error candidates} for competitor {@code c} passing + * waypoint {@code w}. + */ private Map, Candidate> getXteCandidates(Competitor c, Waypoint w) { synchronized (xteCandidates) { Map, Candidate> result = xteCandidates.get(c).get(w); @@ -347,10 +524,8 @@ public class CandidateFinderImpl implements CandidateFinder { try { track.lockForRead(); for (GPSFix fix : track.getFixes( - race.getStartOfTracking() == null ? TimePoint.BeginningOfTime : race.getStartOfTracking(), - /* fromInclusive */true, - race.getEndOfTracking() == null ? TimePoint.EndOfTime : race.getEndOfTracking(), - /* toInclusive */true)) { + timeRangeForValidCandidates.from(), /* fromInclusive */ true, + timeRangeForValidCandidates.to(), /* toInclusive */ true)) { fixes.add(fix); } } finally { @@ -371,22 +546,24 @@ public class CandidateFinderImpl implements CandidateFinder { TreeSet affectedFixes = new TreeSet(comp); GPSFixTrack track = race.getTrack(c); for (GPSFix fix : fixes) { - affectedFixes.add(fix); - GPSFix fixBefore; - GPSFix fixAfter; - try { - track.lockForRead(); - TimePoint timePoint = fix.getTimePoint(); - fixBefore = track.getLastFixBefore(timePoint); - fixAfter = track.getFirstFixAfter(timePoint); - } finally { - track.unlockAfterRead(); - } - if (fixBefore != null) { - affectedFixes.add(fixBefore); - } - if (fixAfter != null) { - affectedFixes.add(fixAfter); + if (timeRangeForValidCandidates.includes(fix.getTimePoint())) { + affectedFixes.add(fix); + GPSFix fixBefore; + GPSFix fixAfter; + try { + track.lockForRead(); + TimePoint timePoint = fix.getTimePoint(); + fixBefore = track.getLastFixBefore(timePoint); + fixAfter = track.getFirstFixAfter(timePoint); + } finally { + track.unlockAfterRead(); + } + if (fixBefore != null) { + affectedFixes.add(fixBefore); + } + if (fixAfter != null) { + affectedFixes.add(fixAfter); + } } } for (GPSFix fix : affectedFixes) { @@ -524,108 +701,110 @@ public class CandidateFinderImpl implements CandidateFinder { new ArrayList(), new ArrayList()); DynamicGPSFixTrack track = race.getTrack(c); for (GPSFix fix : fixes) { - TimePoint t = fix.getTimePoint(); - GPSFix fixBefore; - GPSFix fixAfter; - track.lockForRead(); - try { - fixBefore = track.getLastFixBefore(t); - fixAfter = track.getFirstFixAfter(t); - } finally { - track.unlockAfterRead(); - } - Map> xtesBefore = null; - Map> xtesAfter = null; - TimePoint tBefore = null; - TimePoint tAfter = null; - if (fixBefore != null) { - xtesBefore = getXTE(c, fixBefore); - tBefore = fixBefore.getTimePoint(); - } - if (fixAfter != null) { - xtesAfter = getXTE(c, fixAfter); - tAfter = fixAfter.getTimePoint(); - } - Map> xtes = getXTE(c, fix); - for (Waypoint w : waypoints) { - List> oldCandidates = new ArrayList<>(); - Map, Candidate> newCandidates = new HashMap, Candidate>(); - Map, Candidate> waypointCandidates = getXteCandidates(c, w); - for (List fixPair : waypointCandidates.keySet()) { - if (fixPair.contains(fix)) { - oldCandidates.add(fixPair); - } + if (timeRangeForValidCandidates.includes(fix.getTimePoint())) { + TimePoint t = fix.getTimePoint(); + GPSFix fixBefore; + GPSFix fixAfter; + track.lockForRead(); + try { + fixBefore = track.getLastFixBefore(t); + fixAfter = track.getFirstFixAfter(t); + } finally { + track.unlockAfterRead(); } - List wayPointXTEs = xtes.get(w); - int size = wayPointXTEs == null ? 0 : wayPointXTEs.size(); - if (size > 0) { - Double xte = wayPointXTEs.get(0).getMeters(); - if (xte == 0) { - newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, true)); - } else { - if (fixAfter != null && xtesAfter != null && !xtesAfter.get(w).isEmpty()) { - Double xteAfter = xtesAfter.get(w).get(0).getMeters(); - if (xteAfter != null && xte < 0 != xteAfter <= 0) { - newCandidates.put(Arrays.asList(fix, fixAfter), - createCandidate(c, xte, xteAfter, t, tAfter, w, true)); - } + Map> xtesBefore = null; + Map> xtesAfter = null; + TimePoint tBefore = null; + TimePoint tAfter = null; + if (fixBefore != null) { + xtesBefore = getXTE(c, fixBefore); + tBefore = fixBefore.getTimePoint(); + } + if (fixAfter != null) { + xtesAfter = getXTE(c, fixAfter); + tAfter = fixAfter.getTimePoint(); + } + Map> xtes = getXTE(c, fix); + for (Waypoint w : waypoints) { + List> oldCandidates = new ArrayList<>(); + Map, Candidate> newCandidates = new HashMap, Candidate>(); + Map, Candidate> waypointCandidates = getXteCandidates(c, w); + for (List fixPair : waypointCandidates.keySet()) { + if (fixPair.contains(fix)) { + oldCandidates.add(fixPair); } - if (fixBefore != null && !xtesBefore.get(w).isEmpty()) { - Double xteBefore = xtesBefore.get(w).get(0).getMeters(); - if (xte < 0 != xteBefore <= 0) { - newCandidates.put(Arrays.asList(fixBefore, fix), - createCandidate(c, xteBefore, xte, tBefore, t, w, true)); + } + List wayPointXTEs = xtes.get(w); + int size = wayPointXTEs == null ? 0 : wayPointXTEs.size(); + if (size > 0) { + Double xte = wayPointXTEs.get(0).getMeters(); + if (xte == 0) { + newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, true)); + } else { + if (fixAfter != null && xtesAfter != null && !xtesAfter.get(w).isEmpty()) { + Double xteAfter = xtesAfter.get(w).get(0).getMeters(); + if (xteAfter != null && xte < 0 != xteAfter <= 0) { + newCandidates.put(Arrays.asList(fix, fixAfter), + createCandidate(c, xte, xteAfter, t, tAfter, w, true)); + } + } + if (fixBefore != null && !xtesBefore.get(w).isEmpty()) { + Double xteBefore = xtesBefore.get(w).get(0).getMeters(); + if (xte < 0 != xteBefore <= 0) { + newCandidates.put(Arrays.asList(fixBefore, fix), + createCandidate(c, xteBefore, xte, tBefore, t, w, true)); + } } } } - } - if (size > 1) { - Double xte = wayPointXTEs.get(1).getMeters(); - if (xte == 0) { - newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, false)); - } else { - if (fixAfter != null && xtesAfter != null && xtesAfter.get(w).size() >= 2) { - Double xteAfter = xtesAfter.get(w).get(1).getMeters(); - if (xte < 0 != xteAfter <= 0) { - newCandidates.put(Arrays.asList(fix, fixAfter), - createCandidate(c, xte, xteAfter, t, tAfter, w, false)); + if (size > 1) { + Double xte = wayPointXTEs.get(1).getMeters(); + if (xte == 0) { + newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, false)); + } else { + if (fixAfter != null && xtesAfter != null && xtesAfter.get(w).size() >= 2) { + Double xteAfter = xtesAfter.get(w).get(1).getMeters(); + if (xte < 0 != xteAfter <= 0) { + newCandidates.put(Arrays.asList(fix, fixAfter), + createCandidate(c, xte, xteAfter, t, tAfter, w, false)); + } } - } - if (fixBefore != null && xtesBefore.get(w).size() >= 2) { - Double xteBefore = xtesBefore.get(w).get(1).getMeters(); - if (xte < 0 != xteBefore <= 0) { - newCandidates.put(Arrays.asList(fixBefore, fix), - createCandidate(c, xteBefore, xte, tBefore, t, w, false)); + if (fixBefore != null && xtesBefore.get(w).size() >= 2) { + Double xteBefore = xtesBefore.get(w).get(1).getMeters(); + if (xte < 0 != xteBefore <= 0) { + newCandidates.put(Arrays.asList(fixBefore, fix), + createCandidate(c, xteBefore, xte, tBefore, t, w, false)); + } } } } - } - for (Entry, Candidate> candidateWithFixes : newCandidates.entrySet()) { - Candidate newCan = candidateWithFixes.getValue(); - List canFixes = candidateWithFixes.getKey(); - if (oldCandidates.contains(canFixes)) { - oldCandidates.remove(canFixes); - Candidate oldCan = waypointCandidates.get(canFixes); - if (newCan.compareTo(oldCan) != 0) { - result.getB().add(oldCan); - waypointCandidates.remove(canFixes); + for (Entry, Candidate> candidateWithFixes : newCandidates.entrySet()) { + Candidate newCan = candidateWithFixes.getValue(); + List canFixes = candidateWithFixes.getKey(); + if (oldCandidates.contains(canFixes)) { + oldCandidates.remove(canFixes); + Candidate oldCan = waypointCandidates.get(canFixes); + if (newCan.compareTo(oldCan) != 0) { + result.getB().add(oldCan); + waypointCandidates.remove(canFixes); + if (newCan.getProbability() > penaltyForSkipping) { + result.getA().add(newCan); + logger.finest("Added XTE " + newCan.toString() + "for " + c); + waypointCandidates.put(canFixes, newCan); + } + } + } else { if (newCan.getProbability() > penaltyForSkipping) { result.getA().add(newCan); logger.finest("Added XTE " + newCan.toString() + "for " + c); waypointCandidates.put(canFixes, newCan); } } - } else { - if (newCan.getProbability() > penaltyForSkipping) { - result.getA().add(newCan); - logger.finest("Added XTE " + newCan.toString() + "for " + c); - waypointCandidates.put(canFixes, newCan); - } } - } - for (List badCanFixes : oldCandidates) { - result.getB().add(waypointCandidates.get(badCanFixes)); - waypointCandidates.remove(badCanFixes); + for (List badCanFixes : oldCandidates) { + result.getB().add(waypointCandidates.get(badCanFixes)); + waypointCandidates.remove(badCanFixes); + } } } } @@ -1215,4 +1394,35 @@ public class CandidateFinderImpl implements CandidateFinder { return new Util.Pair(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); } + @Override + public Map, Iterable>> getCandidateDeltasAfterRaceStartTimeChange() { + final Map, Iterable>> result; + final TimePoint newNonInferredStartTime = race.getStartOfRace(/* inferred */ false); + final TimePoint newTimePointWhenToStartConsideringCandidates = getTimePointWhenToStartConsideringCandidates(newNonInferredStartTime); + if (!Util.equalsWithNull(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from())) { + timeRangeForValidCandidates = new TimeRangeImpl(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.to()); + result = updateCandiatesAfterRaceTimeRangeChanged(newTimePointWhenToStartConsideringCandidates, timeRangeForValidCandidates.from()); + } else { + result = Collections.emptyMap(); + } + return result; + } + + @Override + public Map, Iterable>> getCandidateDeltasAfterRaceFinishedTimeChange( + TimePoint oldFinishedTime, TimePoint newFinishedTime) { + final Map, Iterable>> result; + final TimePoint newTimePointWhenToFinishConsideringCandidates = getTimePointWhenToFinishConsideringCandidates(newFinishedTime); + final TimePoint oldTimePointWhenToFinishConsideringCandidates = timeRangeForValidCandidates.to(); + if (!Util.equalsWithNull(timeRangeForValidCandidates.to(), newTimePointWhenToFinishConsideringCandidates)) { + timeRangeForValidCandidates = new TimeRangeImpl(timeRangeForValidCandidates.from(), newTimePointWhenToFinishConsideringCandidates); + result = updateCandiatesAfterRaceTimeRangeChanged( + oldTimePointWhenToFinishConsideringCandidates, + newTimePointWhenToFinishConsideringCandidates); + } else { + result = Collections.emptyMap(); + } + return result; + } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java index b8a0931605a..a4fde06e2c3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java @@ -1,20 +1,25 @@ package com.sap.sailing.domain.markpassingcalculation.impl; +import java.util.Comparator; + import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.markpassingcalculation.Candidate; import com.sap.sse.common.TimePoint; public class CandidateImpl implements Candidate { + private static final long serialVersionUID = -4626280455738918911L; private final Waypoint w; private final TimePoint p; private final double probability; private final Integer oneBasedIndexOfWaypoint; + private final Comparator nullSafeTimePointComparator; public CandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w) { this.w = w; this.p = p; this.probability = probability; this.oneBasedIndexOfWaypoint = oneBasedIndexOfWaypoint; + this.nullSafeTimePointComparator = Comparator.nullsLast(Comparator.naturalOrder()); } @Override @@ -51,7 +56,7 @@ public class CandidateImpl implements Candidate { public int compareTo(Candidate arg0) { return getOneBasedIndexOfWaypoint() != arg0.getOneBasedIndexOfWaypoint() ? Integer.valueOf( getOneBasedIndexOfWaypoint()).compareTo(arg0.getOneBasedIndexOfWaypoint()) - : getTimePoint() != arg0.getTimePoint() ? getTimePoint().compareTo(arg0.getTimePoint()) : getProbability().compareTo( - arg0.getProbability()); + : getTimePoint() != arg0.getTimePoint() ? nullSafeTimePointComparator.compare(getTimePoint(), arg0.getTimePoint()) : + getProbability().compareTo(arg0.getProbability()); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java index cf82ee24fc6..6ffb714bf85 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateWithSide.java @@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sse.common.TimePoint; public abstract class CandidateWithSide extends CandidateImpl { + private static final long serialVersionUID = -1670977291984975178L; private final double onCorrectSideOfWaypoint; private final Double startProbabilityBasedOnOtherCompetitors; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java index c9cca5c756c..de1672f84ed 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/DistanceCandidateImpl.java @@ -5,6 +5,7 @@ import com.sap.sailing.domain.common.Distance; import com.sap.sse.common.TimePoint; public class DistanceCandidateImpl extends CandidateWithSide { + private static final long serialVersionUID = -9125694086262205898L; private final Distance distance; public DistanceCandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Double startProbabilityBasedOnOtherCompetitors, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java index e478568d72b..d6e8544d206 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/XTECandidateImpl.java @@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Waypoint; import com.sap.sse.common.TimePoint; public class XTECandidateImpl extends CandidateWithSide { + private static final long serialVersionUID = 700807010904741849L; private final Double passesInTheRightDirectionProbability; public XTECandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index 5ed042a7d7f..79c706740e1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -117,6 +117,14 @@ public interface DynamicTrackedRace extends TrackedRace { * significantly off. */ void setStartTimeReceived(TimePoint start); + + /** + * A new finished time has been received by the {@link DynamicTrackedRaceLogListener} and is announced to this race + * by calling this method. The {@link RaceChangeListener}s will be + * {@link RaceChangeListener#finishedTimeChanged(TimePoint, TimePoint) notified} about this change, and the result + * of {@link #getFinishedTime()} will return the {@code newFinishedTime} after this call returns. + */ + void setFinishedTime(final TimePoint newFinishedTime); /** Sets the start of tracking as received from the tracking infrastructure. * This isn't necessarily what {@link #getStartOfTracking()} will deliver because we might consider other values to @@ -172,7 +180,6 @@ public interface DynamicTrackedRace extends TrackedRace { void onCourseDesignChangedByRaceCommittee(CourseBase courseDesign); void onStartTimeChangedByRaceCommittee(TimePoint newStartTime); - void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime); void onAbortedByRaceCommittee(Flags flag); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java index a9694ce8b2f..2a4eb8373cf 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java @@ -2,6 +2,10 @@ package com.sap.sailing.domain.tracking; import java.util.Map; +import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseListener; import com.sap.sailing.domain.base.Mark; @@ -49,6 +53,13 @@ public interface RaceChangeListener extends CourseListener { * passing-based start time inference is active or not. */ void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace); + + /** + * Fired when in any of the attached {@link RaceLog}s a {@link RaceLogRaceStatusEvent} or a {@link RaceLogPassChangeEvent} + * or a {@link RaceLogRevokeEvent} has caused a change in the {@link RaceState#getFinishedTime() finished time} inferred + * from that race log. + */ + void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime); void delayToLiveChanged(long delayToLiveInMillis); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java old mode 100644 new mode 100755 index 649449ac533..9e1e57f7f71 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -7,6 +7,7 @@ import java.util.Set; import java.util.SortedSet; import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; @@ -39,6 +40,7 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLog; import com.sap.sailing.domain.common.dto.TrackedRaceDTO; import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; @@ -112,6 +114,15 @@ public interface TrackedRace extends Serializable, IsManagedByCachemanually set start/end of tracking via {@link #setStartOfTrackingReceived(TimePoint, boolean)} and {@link #setEndOfTrackingReceived(TimePoint, boolean)} *

  • start/end of race in Racelog +/- TRACKING_BUFFER_IN_MINUTES
  • * + * @param waitForGPSFixesToLoad TODO */ - public void updateStartAndEndOfTracking(); + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad); default void addRegattaLogAttachmentListener(RegattaLogAttachmentListener listener) { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java index c0ba2006c9b..ab8d39edd27 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java @@ -92,7 +92,7 @@ public interface TrackedRegatta extends Serializable { void removeRaceListener(RaceListener listener); - int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException; + int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException; void removeTrackedRace(RaceDefinition raceDefinition); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java index e97a24b13e3..aaff0188362 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java @@ -40,6 +40,11 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener { public void startOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { defaultAction(); } + + @Override + public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + defaultAction(); + } @Override public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java index e97285ef6f1..7b9c3d651db 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java @@ -220,4 +220,18 @@ public class DistanceCache { private Util.Pair>> createDummy(TimePoint to) { return new Util.Pair>>(to, null); } + + /** + * Removes all contents from this cache + */ + public void clear() { + LockUtil.lockForWrite(lock); + try { + distanceCache.clear(); + lruCache.clear(); + } finally { + LockUtil.unlockAfterWrite(lock); + } + + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java index 9d74c644459..a3b7963da9c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java @@ -26,7 +26,7 @@ import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sse.common.TimePoint; -public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl implements DynamicGPSFixTrack{ +public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl implements DynamicGPSFixTrack { private static final long serialVersionUID = 9111448573301259784L; private static final double MAX_SPEED_FACTOR_COMPARED_TO_MEASURED_SPEED_FOR_FILTERING = 2; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java old mode 100644 new mode 100755 index 94e55791185..fd78239f599 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -347,13 +347,11 @@ DynamicTrackedRace, GPSTrackListener { @Override public void invalidateStartTime() { - TimePoint oldStartOfRace = getStartOfRace(); + final TimePoint oldStartOfRace = getStartOfRace(); super.invalidateStartTime(); - TimePoint newStartOfRace = getStartOfRace(); - if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); + if (!Util.equalsWithNull(oldStartOfRace, getStartOfRace())) { + notifyListenersStartOfRaceChanged(oldStartOfRace, getStartOfRace()); } - updateStartAndEndOfTracking(); } @Override @@ -452,6 +450,17 @@ DynamicTrackedRace, GPSTrackListener { } } + @Override + public void setFinishedTime(final TimePoint newFinishedTime) { + final TimePoint oldFinishedTime = getFinishedTime(); + if (!Util.equalsWithNull(newFinishedTime, oldFinishedTime)) { + logger.info("Finished time of race " + getRace().getName() + " updated from " + getFinishedTime() + " to " + newFinishedTime); + super.setFinishedTime(newFinishedTime); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); + notifyListenersFinishedTimeChanged(oldFinishedTime, newFinishedTime); + } + } + private void notifyListenersWindSourcesToExcludeChanged(Iterable windSourcesToExclude) { notifyListeners(listener -> listener.windSourcesToExcludeChanged(windSourcesToExclude)); } @@ -472,6 +481,10 @@ DynamicTrackedRace, GPSTrackListener { notifyListeners(listener -> listener.startOfRaceChanged(oldStartOfRace, newStartOfRace)); } + private void notifyListenersFinishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + notifyListeners(listener -> listener.finishedTimeChanged(oldFinishedTime, newFinishedTime)); + } + private void notifyListenersWaypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { notifyListeners(listener -> listener.waypointAdded(zeroBasedIndex, waypointThatGotAdded)); } @@ -670,7 +683,7 @@ DynamicTrackedRace, GPSTrackListener { /** * Updates the {@code markPassings} into the {@link #getMarkPassing(Competitor, Waypoint) mark passing data * structure for the waypoints passed} and the {@link #getMarkPassings(Competitor) mark passing data structure for - * the competitor}. The mark passings are use as-is, without considering any + * the competitor}. The mark passings are used as-is, without considering any * {@link CompetitorResult#getFinishingTime() finishing times} for competitors coming from any {@link RaceLog}. * See also {@link #updateMarkPassings(Competitor, Iterable)} which does consider those. */ @@ -679,8 +692,6 @@ DynamicTrackedRace, GPSTrackListener { try { Map oldMarkPassings = new HashMap(); MarkPassing oldStartMarkPassing = null; - TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after - // updating the mark passings boolean requiresStartTimeUpdate = true; final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); lockForRead(markPassingsForCompetitor); @@ -759,16 +770,7 @@ DynamicTrackedRace, GPSTrackListener { triggerManeuverCacheRecalculation(competitor); // update the race times like start, end and the leg times if (requiresStartTimeUpdate) { - TimePoint interimsStartOfRace = getStartOfRace(); invalidateStartTime(); - TimePoint newStartOfRace = getStartOfRace(); - if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) - && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - // invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it - // already saw the new - // start of race time; we have to throw the notification here: - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } } invalidateMarkPassingTimes(); invalidateEndTime(); @@ -850,13 +852,8 @@ DynamicTrackedRace, GPSTrackListener { @Override public void setStartTimeReceived(TimePoint startTimeReceived) { if (!Util.equalsWithNull(startTimeReceived, getStartTimeReceived())) { - TimePoint oldStartOfRace = getStartOfRace(); super.setStartTimeReceived(startTimeReceived); notifyListenersStartTimeReceivedChanged(getStartTimeReceived()); - TimePoint newStartOfRace = getStartOfRace(); - if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } } } @@ -865,6 +862,9 @@ DynamicTrackedRace, GPSTrackListener { setStartOfTrackingReceived(startOfTrackingReceived, /* waitForGPSFixesToLoad */ false); } + /** + * Making this method publicly visible for those tests seeing this implementation class + */ public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived, final boolean waitForGPSFixesToLoad) { TimePoint oldStartOfTracking = getStartOfTracking(); if (!Util.equalsWithNull(startOfTrackingReceived, oldStartOfTracking)) { @@ -873,14 +873,8 @@ DynamicTrackedRace, GPSTrackListener { } } - @Override - public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { - setEndOfTrackingReceived(endOfTrackingReceived, /* waitForGPSFixesToLoad */ false); - } - /** - * Non-interface method, mainly for testing purposes; callers can ask to wait for the loading of fixes in the - * potentially extended tracking interval to finish before returning from this method. + * Making this method publicly visible for those tests seeing this implementation class */ public void setEndOfTrackingReceived(final TimePoint endOfTrackingReceived, final boolean waitForGPSFixesToLoad) { TimePoint oldEndOfTracking = getEndOfTracking(); @@ -890,6 +884,11 @@ DynamicTrackedRace, GPSTrackListener { } } + @Override + public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { + setEndOfTrackingReceived(endOfTrackingReceived, /* waitForGPSFixesToLoad */ false); + } + /** * In addition to calling the superclass implementation, for a stored wind track whose fixes were loaded by this * call, all listeners are notified about these existing wind fixes using their @@ -1034,13 +1033,7 @@ DynamicTrackedRace, GPSTrackListener { } catch (IOException e) { logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); } - updateStartAndEndOfTracking(); - } - - @Override - public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { - logger.info("Finished time of race "+getRace().getName()+" updated by race committee to "+newFinishedTime); - updateStartAndEndOfTracking(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index 1886af12f0d..93dd7356ab5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -1,7 +1,6 @@ package com.sap.sailing.domain.tracking.impl; -import java.util.HashSet; -import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import com.sap.sailing.domain.abstractlog.race.RaceLog; @@ -25,6 +24,10 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.MarkPassingDataFin import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.WindFixesFinder; import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; +import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.common.Wind; @@ -42,12 +45,17 @@ import com.sap.sse.common.Util.Triple; /** * Listens for changes on a {@link RaceLog} and forwards the relevant ones to a {@link TrackedRace}. Examples: start time changes; * fixed mark passings changes; course design changes; wind fixes entered by the race committee.

    + * + * For each {@link RaceLog} {@link #addTo(RaceLog) attached}, a {@link ReadonlyRaceState} is constructed that is used to observe + * some properties in an easier to use form than observing the {@link RaceLog}s directly. For example, the {@link ReadonlyRaceState} + * can tell the {@link ReadonlyRaceState#getFinishedTime() finished time} and it is possible to receive change notifications for + * this value. */ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { private static final Logger logger = Logger.getLogger(DynamicTrackedRaceLogListener.class.getName()); - private Set raceLogs = new HashSet<>(); + private ConcurrentHashMap raceLogs = new ConcurrentHashMap<>(); private DynamicTrackedRace trackedRace; @@ -67,13 +75,36 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { logger.severe("Trying to add " + this + " as listener to a null race log for tracked race " + trackedRace.getRace()); } else { raceLog.addListener(this); - raceLogs.add(raceLog); + final ReadonlyRaceState raceState = ReadonlyRaceStateImpl.create(trackedRace.getRaceLogResolver(), raceLog); + raceLogs.put(raceLog, raceState); + raceState.addChangedListener(new BaseRaceStateChangedListener() { + @Override + public void onFinishedTimeChanged(ReadonlyRaceState state) { + trackedRace.setFinishedTime(state.getFinishedTime()); + } + }); trackedRace.invalidateStartTime(); trackedRace.invalidateEndTime(); analyze(raceLog); } } + /** + * @return the first non-{@code null} {@link ReadonlyRaceState#getFinishedTime() finished time} that is found in any + * of the {@link RaceState}s constructed for all the {@link RaceLog}s observed by this listener, or {@code null} + * if no {@link ReadonlyRaceState} exists that returns a non-{@code null} finished time. + */ + public TimePoint getFinishedTime() { + TimePoint result = null; + for (final ReadonlyRaceState raceState : raceLogs.values()) { + result = raceState.getFinishedTime(); + if (result != null) { + break; + } + } + return result; + } + private LastPublishedCourseDesignFinder createCourseDesignFinder(RaceLog raceLog) { return new LastPublishedCourseDesignFinder(raceLog, /* onlyCoursesWithValidWaypointList */ false); // we also want by-name courses to forward, e.g., to TracTrac } @@ -148,6 +179,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { } private void analyze(RaceLog raceLog) { + trackedRace.setFinishedTime(getFinishedTime()); analyzeCourseDesign(null); initializeWindTrack(raceLog); if (markPassingUpdateListener != null) { @@ -159,7 +191,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { private void analyzeCourseDesign(CourseBase courseBaseProvidedByEvent) { CourseBase courseDesign = null; - for (RaceLog raceLog : raceLogs) { + for (RaceLog raceLog : raceLogs.keySet()) { courseDesign = createCourseDesignFinder(raceLog).analyze(); if (courseDesign != null) { break; @@ -180,10 +212,10 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { } private void analyzeStartTime(TimePoint startTimeProvidedByEvent) { - /* start time will be set by StartTimeFinder in TrackedRace.getStartTime() */ + /* start time will be set by StartTimeFinder in TrackedRace.getStartOfRace() */ trackedRace.invalidateStartTime(); TimePoint startTime = null; - for (RaceLog raceLog : raceLogs) { + for (RaceLog raceLog : raceLogs.keySet()) { startTime = createStartTimeFinder(raceLog).analyze().getStartTime(); if (startTime != null) { break; @@ -217,7 +249,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { /* reset start time */ trackedRace.onStartTimeChangedByRaceCommittee(null); RaceLogFlagEvent abortingFlag = null; - for (RaceLog raceLog : raceLogs) { + for (RaceLog raceLog : raceLogs.keySet()) { abortingFlag = createAbortingFlagFinder(raceLog).analyze(); if (abortingFlag != null) { break; @@ -277,7 +309,7 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { public void visit(RaceLogRevokeEvent event) { if (markPassingUpdateListener != null) { RaceLogEvent revokedEvent = null; - for (RaceLog log : raceLogs) { + for (RaceLog log : raceLogs.keySet()) { try { log.lockForRead(); revokedEvent = log.getEventById(event.getRevokedEventId()); @@ -306,12 +338,12 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { @Override public void visit(RaceLogStartOfTrackingEvent event) { - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override public void visit(RaceLogEndOfTrackingEvent event) { - trackedRace.updateStartAndEndOfTracking(); + trackedRace.updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override @@ -320,4 +352,5 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { trackedRace.invalidateEndTime(); } } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 6beb0de7ab4..90062a62724 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -157,6 +157,7 @@ public class GPSFixTrackImpl extends TrackImpl } finally { unlockAfterWrite(); } + getDistanceCache().clear(); } protected MaxSpeedCache createMaxSpeedCache() { @@ -494,22 +495,26 @@ public class GPSFixTrackImpl extends TrackImpl if (!from.before(to)) { result = Distance.NULL; } else { + boolean perfectCacheHit = false; lockForRead(); try { Util.Pair> bestCacheEntry = getDistanceCache() .getEarliestFromAndDistanceAtOrAfterFrom(from, to); if (bestCacheEntry != null) { + perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below // compute the missing stretches between best cache entry's "from" and our "from" and the cache // entry's "to" and our "to" Distance distanceFromFromToBeginningOfCacheEntry = Distance.NULL; Distance distanceFromEndOfCacheEntryToTo = Distance.NULL; if (!bestCacheEntry.getB().getA().equals(from)) { assert bestCacheEntry.getB().getA().after(from); + perfectCacheHit = false; distanceFromFromToBeginningOfCacheEntry = getDistanceTraveledRecursively(from, bestCacheEntry .getB().getA(), recursionDepth + 1); } if (!bestCacheEntry.getA().equals(to)) { assert bestCacheEntry.getA().before(to); + perfectCacheHit = false; distanceFromEndOfCacheEntryToTo = getDistanceTraveledRecursively(bestCacheEntry.getA(), to, recursionDepth + 1); } @@ -544,7 +549,7 @@ public class GPSFixTrackImpl extends TrackImpl } finally { unlockAfterRead(); } - if (recursionDepth == 0) { + if (!perfectCacheHit && recursionDepth == 0) { getDistanceCache().cache(from, to, result); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index b54c6866af8..90984efdac2 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -620,6 +620,12 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { // race } + @Override + public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + // no action required; we're calculating based on each competitor's individual finishing time, not the finishing of + // the race + } + @Override public void startTimeReceivedChanged(TimePoint startTimeReceived) { } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index ac17a9a0a37..618faa14c8b 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -39,6 +39,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; @@ -46,6 +47,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogGateLineOpeningTimeEventImpl; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.RaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure; @@ -101,6 +103,7 @@ import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; @@ -206,7 +209,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl /** * The start and end of tracking inferred via RaceLog, the received timepoint (see above) and mapping intervals. - * For the precedence order see {@link #updateStartAndEndOfTracking()}. + * For the precedence order see {@link #updateStartAndEndOfTracking(boolean)}. */ private TimePoint startOfTracking; private TimePoint endOfTracking; @@ -233,6 +236,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl */ private TimePoint endTime; + /** + * The time set by race management ("Blue Flag Down" event) for when the race has finished. This field caches what + * today comes from the {@link RaceLog}s in the form of {@link RaceLogRaceStatusEvent}s setting the status to + * {@link RaceLogRaceStatus#FINISHED} and is computed by the {@link DynamicTrackedRaceLogListener#getFinishedTime()} + * method based on the {@link RaceState}s it manages for all the {@link RaceLog}s currently attached to this race. + */ + private TimePoint finishedTime; + /** * The first and last passing times of all course waypoints */ @@ -784,9 +795,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl *

  • start/end of race in Racelog +/- TRACKING_BUFFER_IN_MINUTES
  • * */ - public void updateStartAndEndOfTracking() { + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { final Pair trackingTimesFromRaceLog = this.getTrackingTimesFromRaceLogs(); - final Pair startAndFinishedTimesFromRaceLog = this.getStartAndFinishedTimeFromRaceLogs(); TimePoint oldStartOfTracking = getStartOfTracking(); TimePoint oldEndOfTracking = getEndOfTracking(); boolean startOfTrackingFound = false; @@ -815,19 +825,17 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } // check for start/finished times in race log and add a few minutes on the ends if (!startOfTrackingFound || !endOfTrackingFound) { - if (startAndFinishedTimesFromRaceLog != null) { - if (!startOfTrackingFound && startAndFinishedTimesFromRaceLog.getA() != null){ - startOfTracking = startAndFinishedTimesFromRaceLog.getA().minus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); + if (!startOfTrackingFound && getStartOfRace() != null) { + startOfTracking = getStartOfRace().minus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); startOfTrackingFound = true; } - if (!endOfTrackingFound && startAndFinishedTimesFromRaceLog.getB() != null){ - endOfTracking = startAndFinishedTimesFromRaceLog.getB().plus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); + if (!endOfTrackingFound && getFinishedTime() != null) { + endOfTracking = getFinishedTime().plus(Duration.ONE_MINUTE.times(TRACKING_BUFFER_IN_MINUTES)); endOfTrackingFound = true; } } - } - startOfTrackingChanged(oldStartOfTracking, false); - endOfTrackingChanged(oldEndOfTracking, false); + startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad); + endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad); } @Override @@ -836,21 +844,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } public void invalidateStartTime() { - startTime = null; - startTimeWithoutInferenceFromStartMarkPassings = null; - updateStartAndEndOfTracking(); + updateStartOfRaceCacheFields(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } public void invalidateEndTime() { endTime = null; - updateStartAndEndOfTracking(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } protected void invalidateMarkPassingTimes() { synchronized (markPassingsTimes) { markPassingsTimes.clear(); } - updateStartAndEndOfTracking(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } /** @@ -902,33 +909,43 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public TimePoint getStartOfRace(boolean inferred) { final TimePoint result; - // check if we have in our caching fields what is asked: - if (inferred && startTime != null) { + if (inferred) { result = startTime; - } else if (!inferred && startTimeWithoutInferenceFromStartMarkPassings != null) { - result = startTimeWithoutInferenceFromStartMarkPassings; } else { - // no cache match; re-calculate + result = startTimeWithoutInferenceFromStartMarkPassings; + } + return result; + } + + /** + * monitor for {@link #updateStartOfRaceCacheFields()}; has to be serializable, therefore {@link String} + * and not {@link Object}. + */ + private final String updateStartOfRaceCacheFieldsMonitor = ""; + private void updateStartOfRaceCacheFields() { + synchronized (updateStartOfRaceCacheFieldsMonitor) { + TimePoint newStartTime = null; + TimePoint newStartTimeWithoutInferenceFromStartMarkPassings = null; for (RaceLog raceLog : attachedRaceLogs.values()) { if (logger.isLoggable(Level.FINEST)) { logger.finest("Analyzing race log "+raceLog+" for race "+this.getRace().getName()); } - startTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime(); - if (startTime != null) { - startTimeWithoutInferenceFromStartMarkPassings = startTime; + newStartTime = new StartTimeFinder(raceLogResolver, raceLog).analyze().getStartTime(); + if (newStartTime != null) { + newStartTimeWithoutInferenceFromStartMarkPassings = newStartTime; if (logger.isLoggable(Level.FINEST)) { - logger.finest("Found start time "+startTime+" in race log "+raceLog+" for race "+this.getRace().getName()); + logger.finest("Found start time "+newStartTime+" in race log "+raceLog+" for race "+this.getRace().getName()); } break; } } - if (startTime == null) { + if (newStartTime == null) { if (logger.isLoggable(Level.FINEST)) { logger.finest("No start time found in race logs for race "+getRace().getName()); } - startTime = getStartTimeReceived(); - if (startTime != null) { - startTimeWithoutInferenceFromStartMarkPassings = startTime; + newStartTime = getStartTimeReceived(); + if (newStartTime != null) { + newStartTimeWithoutInferenceFromStartMarkPassings = newStartTime; } // If not null, check if the first mark passing for the start line is too much after the // startTimeReceived; if so, return an adjusted, later start time. @@ -946,38 +963,34 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassing.asMillis() - startTimeReceived.asMillis(); if (startTimeReceived2timeOfFirstMarkPassingFirstMark > MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS) { - startTime = new MillisecondsTimePoint(timeOfFirstMarkPassing.asMillis() + newStartTime = new MillisecondsTimePoint(timeOfFirstMarkPassing.asMillis() - MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS); if (logger.isLoggable(Level.FINEST)) { - logger.finest("Using start mark passings for start time of race "+this.getRace().getName()+": "+startTime); + logger.finest("Using start mark passings for start time of race "+this.getRace().getName()+": "+newStartTime); } } else { - startTime = startTimeReceived; + newStartTime = startTimeReceived; if (logger.isLoggable(Level.FINEST)) { - logger.finest("Using start mark received for race "+this.getRace().getName()+": "+startTime); + logger.finest("Using start mark received for race "+this.getRace().getName()+": "+newStartTime); } } } } else { final NavigableSet markPassingsForFirstWaypointInOrder = getMarkPassingsInOrderAsNavigableSet(firstWaypoint); if (markPassingsForFirstWaypointInOrder != null) { - startTime = calculateStartOfRaceFromMarkPassings(markPassingsForFirstWaypointInOrder, + newStartTime = calculateStartOfRaceFromMarkPassings(markPassingsForFirstWaypointInOrder, getRace().getCompetitors()); - if (startTime != null && logger.isLoggable(Level.FINEST)) { - logger.finest("Using start mark passings for start time of race "+this.getRace().getName()+": "+startTime); + if (newStartTime != null && logger.isLoggable(Level.FINEST)) { + logger.finest("Using start mark passings for start time of race "+this.getRace().getName()+": "+newStartTime); } } } } } - if (inferred) { - result = startTime; - } else { - result = startTimeWithoutInferenceFromStartMarkPassings; + startTime = newStartTime; + startTimeWithoutInferenceFromStartMarkPassings = newStartTimeWithoutInferenceFromStartMarkPassings; } } - return result; - } /** * Calculates the end time of the race from the mark passings of the last course waypoint @@ -990,6 +1003,15 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl return endTime; } + @Override + public TimePoint getFinishedTime() { + return finishedTime; + } + + protected void setFinishedTime(final TimePoint newFinishedTime) { + finishedTime = newFinishedTime; + } + private TimePoint getLastPassingOfFinishLine() { TimePoint passingTime = null; final Waypoint lastWaypoint = getRace().getCourse().getLastWaypoint(); @@ -1955,7 +1977,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } protected void setStartTimeReceived(TimePoint start) { - if ((start == null) != (startTimeReceived == null) || (start != null && !start.equals(startTimeReceived))) { + if (!Util.equalsWithNull(start, startTimeReceived)) { this.startTimeReceived = start; invalidateStartTime(); invalidateMarkPassingTimes(); @@ -1968,18 +1990,16 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } protected void setStartOfTrackingReceived(final TimePoint startOfTracking, final boolean waitForGPSFixesToLoad) { - final TimePoint oldStartOfTracking = getStartOfTracking(); this.startOfTrackingReceived = startOfTracking; - updateStartAndEndOfTracking(); - startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad); + updateStartAndEndOfTracking(waitForGPSFixesToLoad); } - private void startOfTrackingChanged(final TimePoint oldStartOfTracking, boolean waitForGPSFixesToLoad){ - TimePoint inferedStartOfTracking = getStartOfTracking(); - if (!Util.equalsWithNull(oldStartOfTracking, inferedStartOfTracking) && - (inferedStartOfTracking == null || (oldStartOfTracking != null && inferedStartOfTracking.before(oldStartOfTracking)))) { - logger.info("Loading fixes after start of tracking for "+getRace()+" has been extended from "+oldStartOfTracking+" to "+inferedStartOfTracking); - loadGPSFixesForExtendedTimeRange(inferedStartOfTracking, oldStartOfTracking, waitForGPSFixesToLoad); + protected void startOfTrackingChanged(final TimePoint oldStartOfTracking, boolean waitForGPSFixesToLoad) { + TimePoint inferredStartOfTracking = getStartOfTracking(); + if (!Util.equalsWithNull(oldStartOfTracking, inferredStartOfTracking) && + (inferredStartOfTracking == null || (oldStartOfTracking != null && inferredStartOfTracking.before(oldStartOfTracking)))) { + logger.info("Loading fixes after start of tracking for "+getRace()+" has been extended from "+oldStartOfTracking+" to "+inferredStartOfTracking); + loadGPSFixesForExtendedTimeRange(inferredStartOfTracking, oldStartOfTracking, waitForGPSFixesToLoad); } } @@ -1991,21 +2011,17 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } protected void setEndOfTrackingReceived(final TimePoint endOfTracking, final boolean waitForGPSFixesToLoad) { - final TimePoint oldEndOfTracking = this.endOfTrackingReceived; this.endOfTrackingReceived = endOfTracking; - updateStartAndEndOfTracking(); - endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad); + updateStartAndEndOfTracking(waitForGPSFixesToLoad); } - private void endOfTrackingChanged(final TimePoint oldEndOfTracking, boolean waitForGPSFixesToLoad){ - TimePoint inferedEndOfTracking = getEndOfTracking(); - - if (!Util.equalsWithNull(oldEndOfTracking, inferedEndOfTracking) && - (inferedEndOfTracking == null || (oldEndOfTracking != null && inferedEndOfTracking.after(oldEndOfTracking)))) { - logger.info("Loading fixes after end of tracking for "+getRace()+" has been extended from "+oldEndOfTracking+" to "+inferedEndOfTracking); - loadGPSFixesForExtendedTimeRange(oldEndOfTracking, inferedEndOfTracking, waitForGPSFixesToLoad); + protected void endOfTrackingChanged(final TimePoint oldEndOfTracking, boolean waitForGPSFixesToLoad) { + TimePoint inferredEndOfTracking = getEndOfTracking(); + if (!Util.equalsWithNull(oldEndOfTracking, inferredEndOfTracking) && + (inferredEndOfTracking == null || (oldEndOfTracking != null && inferredEndOfTracking.after(oldEndOfTracking)))) { + logger.info("Loading fixes after end of tracking for "+getRace()+" has been extended from "+oldEndOfTracking+" to "+inferredEndOfTracking); + loadGPSFixesForExtendedTimeRange(oldEndOfTracking, inferredEndOfTracking, waitForGPSFixesToLoad); } - } /** @@ -3481,7 +3497,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl synchronized (TrackedRaceImpl.this) { attachedRaceLogs.put(raceLog.getId(), raceLog); notifyAll(); - updateStartAndEndOfTracking(); + updateStartOfRaceCacheFields(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } } @@ -3537,7 +3554,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } catch (InterruptedException e) { logger.log(Level.WARNING, "Interrupted while waiting for race log being attached", e); } - updateStartAndEndOfTracking(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override @@ -3548,6 +3565,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public void detachRaceLog(Serializable identifier) { this.attachedRaceLogs.remove(identifier); + updateStartOfRaceCacheFields(); + updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java index 3b92f7ced2d..8fe1eeefdf2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java @@ -229,7 +229,7 @@ public class TrackedRegattaImpl implements TrackedRegatta { } @Override - public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { + public int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { int result = 0; lockTrackedRacesForRead(); try { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 8ce0c7194ed..cd2fee1d259 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -398,6 +398,10 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { public double getStatuteMilesPerHour() { return 0; } + @Override + public Distance travel(Duration duration) { + return null; + } } @Override diff --git a/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java b/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java index 0fb01362420..d6da5a01d62 100755 --- a/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java +++ b/java/com.sap.sailing.freg.resultimport.test/src/com/sap/sailing/freg/resultimport/test/SimpleHtmlParsingTest.java @@ -43,7 +43,7 @@ public class SimpleHtmlParsingTest { if (Util.contains(row.getNames(), "MARTIN Aline")) { assertEquals("FRA 8716", row.getSailID()); assertEquals(111, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(230.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(230.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); } } } @@ -59,7 +59,7 @@ public class SimpleHtmlParsingTest { if (Util.contains(row.getNames(), "NIEMINEN Jukka")) { assertEquals("FIN 8768", row.getSailID()); assertEquals(118, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(118.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(118.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertNull(Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getRank()); assertNull(Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getScore()); assertEquals(false, Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).isDiscarded()); @@ -69,7 +69,7 @@ public class SimpleHtmlParsingTest { } else if (Util.contains(row.getNames(), "HAGAN Douglas")) { assertEquals("AUS 8817", row.getSailID()); assertEquals(235.00, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(235.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(235.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertNull(Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getRank()); assertEquals("DNF", Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getMaxPointsReason()); assertEquals((double) 117.00, (double) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getScore(), 0.000000001); @@ -118,14 +118,14 @@ public class SimpleHtmlParsingTest { if (Util.contains(row.getNames(), "HOLZAPFEL Alexander")) { assertEquals("GER 8975", row.getSailID()); assertEquals(60.00, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(74.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(74.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertEquals((int) 11, (int) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getRank()); assertEquals((double) 11, (double) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getScore(), 0.000000001); assertEquals(false, Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).isDiscarded()); } else if (Util.contains(row.getNames(), "BART Cedric")) { assertEquals("SUI 8543", row.getSailID()); assertEquals(55.00, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(74.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(74.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertEquals((int) 2, (int) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 2).getRank()); assertEquals((double) 2, (double) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 2).getScore(), 0.000000001); assertEquals(false, Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 2).isDiscarded()); @@ -135,7 +135,7 @@ public class SimpleHtmlParsingTest { } else if (Util.contains(row.getNames(), "LEWNS Chris")) { assertEquals("GBR 9057", row.getSailID()); assertEquals(64.00, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(140.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(140.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertNull(Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 7).getRank()); assertEquals("DNF", Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 7).getMaxPointsReason()); assertEquals((double) 76, (double) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 7).getScore(), 0.000000001); @@ -155,7 +155,7 @@ public class SimpleHtmlParsingTest { if (Util.contains(row.getNames(), "HOLSTE Kim (M1993)")) { assertEquals("GER 1864", row.getSailID()); assertEquals(58.00, row.getScoreAfterDiscarding(), 0.000000001); - assertEquals(169.00, row.getNetPointsBeforeDiscarding(), 0.000000001); + assertEquals(169.00, row.getTotalPointsBeforeDiscarding(), 0.000000001); assertEquals((int) 25, (int) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getRank()); assertEquals((double) 25, (double) Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).getScore(), 0.000000001); assertEquals(false, Util.get(row.getRankAndMaxPointsReasonAndPointsAndDiscarded(), 0).isDiscarded()); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/client/GwtUrlHelperTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/client/GwtUrlHelperTest.java new file mode 100644 index 00000000000..5cd97cb9bc7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/client/GwtUrlHelperTest.java @@ -0,0 +1,51 @@ +package com.sap.sailing.gwt.ui.client; + +import org.junit.Test; + +import com.google.gwt.junit.client.GWTTestCase; +import com.sap.sse.common.util.UrlHelper; + +public class GwtUrlHelperTest extends GWTTestCase { + + private final UrlHelper urlHelper = GwtUrlHelper.INSTANCE; + + @Test + public void testEncodeQueryStringSimple() { + testCase("TestTest", "TestTest"); + } + + @Test + public void testEncodeQueryStringWithWhitespace() { + testCase("Test%20Test", "Test Test"); + } + + @Test + public void testEncodeQueryStringWithSlash() { + testCase("Test%2FTest", "Test/Test"); + } + + @Test + public void testEncodeQueryStringWithPlusSign() { + testCase("Test%2BTest", "Test+Test"); + } + + @Test + public void testEncodeQueryStringWithAmpersand() { + testCase("Test%26Test", "Test&Test"); + } + + @Test + public void testEncodeQueryStringComplex() { + testCase("Test%20%2B%20Test%20%2F%20Test%20%26%20Test", "Test + Test / Test & Test"); + } + + private void testCase(String expectedEncodedString, String decodedString) { + assertEquals(expectedEncodedString, urlHelper.encodeQueryString(decodedString)); + } + + @Override + public String getModuleName() { + return "com.sap.sailing.gwt.ui.test.TestConsole"; + } + +} diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index c6ef63bfb48..e6383c266c8 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -179,7 +179,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { ArrayList al = new ArrayList(); al.add(COLUMN1_NAME); service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* addOverallDetails */ false, /* previousLeaderboardId */ null, - /* fillUncorrectedNetPoints */ false, new AsyncCallback() { + /* fillUncorrectedTotalPoints */ false, new AsyncCallback() { @Override public void onFailure(Throwable caught) { fail("Failed to get leaderboard."); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index fd68bd89bff..6af394766a0 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -97,6 +97,11 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public TimePoint getFinishedTime() { + return null; + } + @Override public Iterable getTrackedLegs() { return null; @@ -299,7 +304,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { + public int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException { return 0; } @@ -803,11 +808,11 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void updateStartAndEndOfTracking() { + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { } @Override - public void onFinishedTimeChangedByRaceCommittee(TimePoint newFinishedTime) { + public void setFinishedTime(TimePoint newFinishedTime) { } @Override diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java index a6123d06096..e3a7483d13c 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java @@ -99,7 +99,7 @@ public class TestColumnSwapping { sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[0], true); sailingService.updateIsMedalRace(TEST_LEADERBOARD_NAME, races[2], false); lb = sailingService.getLeaderboardByName(TEST_LEADERBOARD_NAME, new Date(), /* races to load */ null, /* addOverallDetails */ true, /* previous leaderboard ID */ null, - /* fillNetPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null); + /* fillTotalPointsUncorrected */ false).getLeaderboardDTO(/* previousVersion */ null); } catch (Exception e) { // e.printStackTrace(); fail(e.getLocalizedMessage()); @@ -141,7 +141,7 @@ public class TestColumnSwapping { service.moveLeaderboardColumnUp(LEADERBOARDNAME, "Race3"); try { leaderboardDTO = service.getLeaderboardByName(LEADERBOARDNAME, leaderboardCreationDate, leglist, /* addOverallDetails */ - true, /* previous leaderboard ID */null, /* fillNetPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null); + true, /* previous leaderboard ID */null, /* fillTotalPointsUncorrected */false).getLeaderboardDTO(/* previousVersion */null); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java index e47bc79a458..d5680b45e05 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java @@ -77,6 +77,7 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata.LASER_4_7.getDisplayName(), imageResources.LaserIcon()); boatClassIconsMap.put(BoatClassMasterdata.LASER_RADIAL.getDisplayName(), imageResources.LaserIcon()); boatClassIconsMap.put(BoatClassMasterdata.LAGO_26.getDisplayName(), imageResources.Lago26Icon()); + boatClassIconsMap.put(BoatClassMasterdata.M32.getDisplayName(), imageResources.M32Icon()); boatClassIconsMap.put(BoatClassMasterdata.MELGES_20.getDisplayName(), imageResources.Melges20Icon()); boatClassIconsMap.put(BoatClassMasterdata.MELGES_24.getDisplayName(), imageResources.Melges24Icon()); boatClassIconsMap.put(BoatClassMasterdata.MINI_TRANSAT.getDisplayName(), imageResources.MiniTransatIcon()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java index a6e47667273..431bbd4163f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java @@ -130,6 +130,9 @@ public interface BoatClassImageResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/boatclass/LAGO_26.png") ImageResource Lago26Icon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/M32.png") + ImageResource M32Icon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/MELGES_20.png") ImageResource Melges20Icon(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ja.properties index c0d79a705e3..443561e87e0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ja.properties @@ -96,7 +96,7 @@ eventRegattaLeaderboardFinishedMessage=レースはすべて終了 eventRegattaLeaderboardLastScoreUpdate=前回スコア更新 eventRegattaLeaderboardName=名称 eventRegattaLeaderboardRegattaRank=レガッタランク -eventRegattaLeaderboardTotalPoints=合計ポイント +eventRegattaLeaderboardTotalPoints=総得点 eventRegattaRaceWatch=ライブ視聴 eventRegattaRaceAnalyse=レース分析 eventRegattaRaceNotTracked=未追跡 @@ -192,32 +192,32 @@ contentSapInSailing10=ドイツ代表セーリングチームである Audi Sail contentSapInSailing11=世界最大のレガッタである Kieler Woche (Kiel Week) のプレミアムパートナー contentSapInSailing12=SAP 5O5 World Championships のタイトルスポンサー -solutionsSAPText1=レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを平易かつ分かりやすく説明することをお手伝いしています。 +solutionsSAPText1=レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを簡略に分かりやすく説明することをお手伝いしています。 solutionsSAPText2=SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできるさまざまなフロントエンドでコンテンツを視覚化することによってセーリングの世界にインサイトと透明性を提供します。 -solutionsRaceText=SAP Race Committee アプリは、セーリングレガッタ運営中にレース組織者にその手動での作業および連絡業務を自動化および簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営および管理の業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 -solutionsPostText=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。
    SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 -solutionsTrainingText=STG Training Diary は、アスリートの体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 -solutionsSimulatorText=適切な戦略の選択は、レース中における大きな競争上の優位性をもたらすことができます。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが平易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 +solutionsRaceText=SAP Race Committee アプリは、セーリングレガッタ運営中にレース組織者が手動で行う作業や連絡業務を自動化/簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営/管理業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 +solutionsPostText=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。
    SAP Business Analytics ソリューションは、多くのレガッタおよびレースを横断して履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 +solutionsTrainingText=STG Training Diary は、選手の体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 +solutionsSimulatorText=適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 sapSailingAnalytics=SAP Sailing Analytics -contentSapSailingAnalytics1=レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することによって、SAP はセーリングという複雑なスポーツを平易かつ分かりやすく説明することに役立っています。 -contentSapSailingAnalytics2=SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできるさまざまなフロントエンドでコンテンツを視覚化することによってセーリングの世界にインサイトと透明性を提供します。 +contentSapSailingAnalytics1=レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを簡略に分かりやすく説明することをお手伝いしています。 +contentSapSailingAnalytics2=SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできる多様なフロントエンドでコンテンツを視覚化して、セーリングの世界に洞察力と透明性を提供します。 sapSailingAnalyticsReadMore=続きを読む sapSailingRaceManager=SAP Sailing Race Manager -contentSapSailingRaceManager=SAP Sailing Race Manager は、セーリングレガッタ運営中にレース組織者にその手動での作業および連絡業務を自動化および簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営および管理の業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 +contentSapSailingRaceManager=SAP Sailing Race Manager は、セーリングレガッタ運営中にレース組織者が手動で行う作業や連絡業務を自動化/簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営/管理業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 sapSailingRaceManagerReadMore=詳細情報 postRaceAnalytics=レース後分析 -contentPostRaceAnalytics1=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。 -contentPostRaceAnalytics2=SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 +contentPostRaceAnalytics1=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上に役立つ、セーラーがそのパフォーマンスを評価および分析する機会が提供されます。 +contentPostRaceAnalytics2=SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタで成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary -i18nDeSolutionsTrainingText=STG Training Diary は、アスリートの体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 +i18nDeSolutionsTrainingText=STG Training Diary は、選手の体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 strategySimulator=戦略シミュレータ -contentStrategySimulator=適切な戦略の選択は、レース中における大きな競争上の優位性をもたらすことができます。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが平易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 +contentStrategySimulator=適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 strategySimulatorReadMore=詳細情報 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties index 491cd1ee293..28a1722c963 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_ru.properties @@ -96,7 +96,7 @@ eventRegattaLeaderboardFinishedMessage=Больше нет гонок eventRegattaLeaderboardLastScoreUpdate=Последнее обновление оценок eventRegattaLeaderboardName=Название eventRegattaLeaderboardRegattaRank=Ранг в регате -eventRegattaLeaderboardTotalPoints=Всего баллов +eventRegattaLeaderboardTotalPoints=Общий балл eventRegattaRaceWatch=Смотреть в реальном времени eventRegattaRaceAnalyse=Анализировать гонку eventRegattaRaceNotTracked=Не отслеживается @@ -114,7 +114,7 @@ eventRegattaRacesLegendAudio=Аудио eventSponsorsTitle=Спонсорская поддержка событий footerJobs=Работа footerCopyright=© SAP Sailing Analytics, 2011–2016. -footerLanguage=SAP Sailing в +footerLanguage=Язык SAP Sailing footerLegal=Раскрытие юридической информации footerPrivacy=Конфиденциальность footerFeedback=Обратная связь diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties index 7471a4e1992..56f0ca2b022 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/TextMessages_zh.properties @@ -113,7 +113,7 @@ eventRegattaRacesLegendVideo=视频 eventRegattaRacesLegendAudio=音频 eventSponsorsTitle=活动赞助 footerJobs=招贤纳士 -footerCopyright=版权所有 \u00A9 2011-2016 SAP Sailing Analytics +footerCopyright=版权所有 © 2011-2016 SAP Sailing Analytics footerLanguage=SAP Sailing 语言 footerLegal=法律声明 footerPrivacy=隐私 @@ -222,10 +222,4 @@ contentStrategySimulator=选择正确的策略可在比赛期间极大地提高 strategySimulatorReadMore=了解更多 impressum=版本说明 -playstoreCampaign=utm_source=sapsailing&utm_medium=solutions&utm_content=2016.02&utm_campaign=solutions&pcampaignid=2016.sapsailing.solutions -playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap.sailing.racecommittee.app -playstoreRacecommitteeBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png - -appstoreStgTrainingstagebuch=https://itunes.apple.com/de/app/stg-trainingstagebuch/id779563635?l=en&mt=8 -appstoreBadgeSuffix=_zh diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages.properties.xlf b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages.properties.xlf index 0c8fc014323..4133836dcbe 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages.properties.xlf +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages.properties.xlf @@ -1,14 +1,14 @@ - +
    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 - + - +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ja.properties.xlf b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ja.properties.xlf index 3c315efa80f..03912f4e4f1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ja.properties.xlf +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ja.properties.xlf @@ -1,14 +1,14 @@ - +
    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 - + - +
    @@ -405,7 +405,7 @@ Total Points - 合計ポイント + 総得点 Watch live @@ -770,7 +770,7 @@ By delivering real time analytics around race rankings, speeds, maneuvers, bearings etc., SAP is helping to simplify and demystify the complex sport of sailing. - レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを平易かつ分かりやすく説明することをお手伝いしています。 + レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを簡略に分かりやすく説明することをお手伝いしています。 SAP Sailing Analytics provide insights and transparency to the world of sailing by utilizing Cloud and In-Memory Technology, processing GPS and wind measurement data in real time and visualizing contents in various frontends accessible from anywhere. @@ -778,19 +778,19 @@ The SAP Race Committee App delivers greater efficiency and control to race organizers by automating and simplifying their manual tasks and communication during running sailing regattas. The Race Committee App helps simplify the operation of running and managing race events to help race committees operate smarter. - SAP Race Committee アプリは、セーリングレガッタ運営中にレース組織者にその手動での作業および連絡業務を自動化および簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営および管理の業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 + SAP Race Committee アプリは、セーリングレガッタ運営中にレース組織者が手動で行う作業や連絡業務を自動化/簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営/管理業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 Access to data collected at sailing events gives sailors the opportunity to assess and analyse their performances to help develop strategies and sail better.<br> SAP Business Analytics solutions provide sailors, coaches, fans and media with tools to browse and analyse historical data across many regattas and races. SAP HANA allows them to learn about performance patterns, tendencies and sailor profiles in a simple, fast and efficient way in order to optimize their own performance and develop successful strategies for future regattas. - セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。<br>SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 + セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。<br>SAP Business Analytics ソリューションは、多くのレガッタおよびレースを横断して履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 The STG Training Diary supports the daily documentation and analysis of an athlete''s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel. - STG Training Diary は、アスリートの体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 + STG Training Diary は、選手の体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 Choosing the right strategy can provide great competitive advantage during a race. The Strategy Simulator simplifies determining the best sailing strategy for various wind conditions and water currents. Since wind and current can be changed in the simulation right away, the robustness of a strategy can be easily evaluated even for uncertain weather conditions. - 適切な戦略の選択は、レース中における大きな競争上の優位性をもたらすことができます。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが平易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 + 適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 SAP Sailing Analytics @@ -798,11 +798,11 @@ By delivering real time analytics around race rankings, speeds, maneuvers, bearings etc., SAP is helping to simplify and demystify the complex sport of sailing. - レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することによって、SAP はセーリングという複雑なスポーツを平易かつ分かりやすく説明することに役立っています。 + レースランキング、速力、操縦、ベアリングなどに関係するリアルタイム分析を提供することにより、SAP はセーリングという複雑なスポーツを簡略に分かりやすく説明することをお手伝いしています。 SAP Sailing Analytics provide insights and transparency to the world of sailing by utilizing Cloud and In-Memory Technology, processing GPS and wind measurement data in real time and visualizing contents in various frontends accessible from anywhere. - SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできるさまざまなフロントエンドでコンテンツを視覚化することによってセーリングの世界にインサイトと透明性を提供します。 + SAP Sailing Analytics は、クラウドおよびインメモリ技術を活用し、リアルタイムで GPS および風計測データを処理し、どこからでもアクセスできる多様なフロントエンドでコンテンツを視覚化して、セーリングの世界に洞察力と透明性を提供します。 Read More @@ -814,7 +814,7 @@ The SAP Sailing Race Manager delivers greater efficiency and control to race organizers by automating and simplifying their manual tasks and communication during running sailing regattas. The Race Committee App helps simplify the operation of running and managing race events to help race committees operate smarter. - SAP Sailing Race Manager は、セーリングレガッタ運営中にレース組織者にその手動での作業および連絡業務を自動化および簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営および管理の業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 + SAP Sailing Race Manager は、セーリングレガッタ運営中にレース組織者が手動で行う作業や連絡業務を自動化/簡略化することによって効率と統制を向上させます。Race Committee アプリは、レースイベントの運営/管理業務を簡略化するのに役立ち、レース委員会がより円滑に運営されるよう支援します。 Learn More @@ -826,11 +826,11 @@ Access to data collected at sailing events gives sailors the opportunity to assess and analyse their performances to help develop strategies and sail better. - セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上を支援するためにセーラーがそのパフォーマンスを評価および分析するための機会が提供されます。 + セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上に役立つ、セーラーがそのパフォーマンスを評価および分析する機会が提供されます。 SAP Business Analytics solutions provide sailors, coaches, fans and media with tools to browse and analyse historical data across many regattas and races. SAP HANA allows them to learn about performance patterns, tendencies and sailor profiles in a simple, fast and efficient way in order to optimize their own performance and develop successful strategies for future regattas. - SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタのために成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 + SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタで成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 SAP Sail Better – STG Training Diary @@ -838,7 +838,7 @@ The STG Training Diary supports the daily documentation and analysis of an athlete’s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel. - STG Training Diary は、アスリートの体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 + STG Training Diary は、選手の体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 Strategy Simulator @@ -846,7 +846,7 @@ Choosing the right strategy can provide great competitive advantage during a race. The Strategy Simulator simplifies determining the best sailing strategy for various wind conditions and water currents. Since wind and current can be changed in the simulation right away, the robustness of a strategy can be easily evaluated even for uncertain weather conditions. - 適切な戦略の選択は、レース中における大きな競争上の優位性をもたらすことができます。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが平易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 + 適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 Learn More diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ru.properties.xlf b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ru.properties.xlf index 619b00b9d85..e00ac0d16ac 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ru.properties.xlf +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_ru.properties.xlf @@ -1,14 +1,14 @@ - +
    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 - + - +
    @@ -405,7 +405,7 @@ Total Points - Всего баллов + Общий балл Watch live @@ -477,7 +477,7 @@ SAP Sailing in - SAP Sailing в + Язык SAP Sailing Legal Disclosure diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_zh_CN.properties.xlf b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_zh_CN.properties.xlf index 7ced312c930..ade246f8efc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_zh_CN.properties.xlf +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n/XLF/TextMessages_zh_CN.properties.xlf @@ -1,14 +1,14 @@ - +
    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 - + - +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/minileaderboard/MiniLeaderboardItemDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/minileaderboard/MiniLeaderboardItemDTO.java index 33ca8070b9b..12116590ad7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/minileaderboard/MiniLeaderboardItemDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/minileaderboard/MiniLeaderboardItemDTO.java @@ -6,18 +6,18 @@ import com.sap.sse.gwt.dispatch.shared.commands.DTO; public class MiniLeaderboardItemDTO implements DTO { private SimpleCompetitorDTO competitor; private int rank; - private Double points; + private Double netPoints; private int raceCount; @SuppressWarnings("unused") private MiniLeaderboardItemDTO() { } - public MiniLeaderboardItemDTO(SimpleCompetitorDTO competitor, int rank, Double points, int raceCount) { + public MiniLeaderboardItemDTO(SimpleCompetitorDTO competitor, int rank, Double netPoints, int raceCount) { super(); this.competitor = competitor; this.rank = rank; - this.points = points; + this.netPoints = netPoints; this.raceCount = raceCount; } @@ -25,8 +25,8 @@ public class MiniLeaderboardItemDTO implements DTO { return competitor; } - public Double getPoints() { - return points; + public Double getNetPoints() { + return netPoints; } public int getRank() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java index 57bcbbfa744..b21c3108dc9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java @@ -35,7 +35,7 @@ public class StandingsListCompetitor extends UIObject { rankUi.setInnerText(Integer.toString(item.getRank())); teamIdUi.setInnerText(competitor.getSailID()); teamNameUi.setInnerText(competitor.getName()); - String pointsString = i18n.pointsValue(item.getPoints()); + String pointsString = i18n.pointsValue(item.getNetPoints()); if(showRaceCounts) { pointsString += " (" + i18n.racesCount(item.getRaceCount()) + ")"; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java index 77876a41481..b0eaae18c6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java @@ -91,7 +91,7 @@ public class RegattaCompetitorAnalyticsTabView extends SharedLeaderboardRegattaT private List getAvailableDetailsTypes() { List availableDetailsTypes = new ArrayList(); availableDetailsTypes.add(DetailType.REGATTA_RANK); - availableDetailsTypes.add(DetailType.REGATTA_TOTAL_POINTS_SUM); + availableDetailsTypes.add(DetailType.REGATTA_NET_POINTS_SUM); return availableDetailsTypes; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java index 3789e28beb5..b65dd681c3a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java @@ -80,7 +80,7 @@ public class EventSeriesCompetitorAnalyticsTabView extends SharedLeaderboardEven private List getAvailableDetailsTypes() { List availableDetailsTypes = new ArrayList(); availableDetailsTypes.add(DetailType.OVERALL_RANK); - availableDetailsTypes.add(DetailType.REGATTA_TOTAL_POINTS_SUM); + availableDetailsTypes.add(DetailType.REGATTA_NET_POINTS_SUM); return availableDetailsTypes; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/minileaderboard/MinileaderboardBoxItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/minileaderboard/MinileaderboardBoxItem.java index 376a35f2044..ea9645bd916 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/minileaderboard/MinileaderboardBoxItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/minileaderboard/MinileaderboardBoxItem.java @@ -30,7 +30,7 @@ public class MinileaderboardBoxItem extends Widget { competitorNameUi.setInnerText(entry.getCompetitor().getName()); competitorCountryNameUi.setInnerText(String.valueOf(entry.getCompetitor().getSailID())); competitorRankUi.setInnerText(String.valueOf(entry.getRank()) + "."); - competitorPointsUi.setInnerText(StringMessages.INSTANCE.pointsValue(entry.getPoints())); + competitorPointsUi.setInnerText(StringMessages.INSTANCE.pointsValue(entry.getNetPoints())); if (showRaceCount) { competitorRacesUi.setInnerText("(" + entry.getRaceCount() + ")"); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java index f3b53e52326..27204254541 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java @@ -93,12 +93,12 @@ public class LeaderboardContext { int raceCount = 0; if(row.fieldsByRaceColumnName != null) { for(LeaderboardEntryDTO lbEntry : row.fieldsByRaceColumnName.values()) { - if(lbEntry.totalPoints != null) { + if(lbEntry.netPoints != null) { raceCount++; } } } - result.addItem(new MiniLeaderboardItemDTO(new SimpleCompetitorDTO(competitor), rank, row.totalPoints, raceCount)); + result.addItem(new MiniLeaderboardItemDTO(new SimpleCompetitorDTO(competitor), rank, row.netPoints, raceCount)); if (limit > 0 && rank >= limit) break; } result.setTotalCompetitorCount(leaderboardDTO.competitors.size()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java index 4fe52c27d2e..bbb3d31a004 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/RaceContext.java @@ -525,7 +525,7 @@ public class RaceContext { RaceTrackingState trackingState = RaceTrackingState.NOT_TRACKED; if (trackedRace != null) { trackingState = RaceTrackingState.TRACKED_NO_VALID_DATA; - if (trackedRace.hasWindData() && trackedRace.hasGPSData()) { + if (trackedRace.hasGPSData()) { trackingState = RaceTrackingState.TRACKED_VALID_DATA; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java index 27a996ff1c5..91ce4b1261a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/actions/GetLeaderboardByNameAction.java @@ -37,11 +37,11 @@ public class GetLeaderboardByNameAction implements AsyncAction { private final StringMessages stringMessages; private final ErrorReporter errorReporter; private final Timer timerToAdjustOffetIn; - private final boolean fillNetPointsUncorrected; + private final boolean fillTotalPointsUncorrected; public GetLeaderboardByNameAction(SailingServiceAsync sailingService, String leaderboardName, Date date, final Collection namesOfRacesForWhichToLoadLegDetails, boolean addOverallDetails, - LeaderboardDTO previousLeaderboard, boolean fillNetPointsUncorrected, Timer timerToAdjustOffsetIn, + LeaderboardDTO previousLeaderboard, boolean fillTotalPointsUncorrected, Timer timerToAdjustOffsetIn, ErrorReporter errorReporter, StringMessages stringMessages) { this.sailingService = sailingService; this.errorReporter = errorReporter; @@ -52,14 +52,14 @@ public class GetLeaderboardByNameAction implements AsyncAction { this.addOverallDetails = addOverallDetails; this.previousLeaderboard = previousLeaderboard; this.timerToAdjustOffetIn = timerToAdjustOffsetIn; - this.fillNetPointsUncorrected = fillNetPointsUncorrected; + this.fillTotalPointsUncorrected = fillTotalPointsUncorrected; } @Override public void execute(final AsyncCallback callback) { final long clientTimeWhenRequestWasSent = System.currentTimeMillis(); sailingService.getLeaderboardByName(leaderboardName, date, namesOfRacesForWhichToLoadLegDetails, addOverallDetails, - previousLeaderboard==null?null:previousLeaderboard.getId(), fillNetPointsUncorrected, + previousLeaderboard==null?null:previousLeaderboard.getId(), fillTotalPointsUncorrected, new AsyncCallback() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java index 89793730d46..22f61ee64c9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractFileImportWidget.java @@ -9,6 +9,7 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; @@ -19,6 +20,7 @@ import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.TrackFileImportDeviceIdentifierDTO; import com.sap.sse.gwt.client.ErrorReporter; @@ -37,16 +39,18 @@ public abstract class AbstractFileImportWidget extends Composite { @UiField FormPanel formPanelUi; private final TrackFileImportDeviceIdentifierTableWrapper table; protected final SailingServiceAsync sailingService; + private final StringMessages stringMessages; private final ErrorReporter errorReporter; - protected AbstractFileImportWidget(String formActionUrl, String importButtonText, + protected AbstractFileImportWidget(String formActionUrl, StringMessages stringMessages, TrackFileImportDeviceIdentifierTableWrapper table, SailingServiceAsync sailingService, final ErrorReporter errorReporter) { this.table = table; this.sailingService = sailingService; + this.stringMessages = stringMessages; this.errorReporter = errorReporter; initWidget(uiBinder.createAndBindUi(this)); - importButtonUi.setText(importButtonText); + importButtonUi.setText(stringMessages.importFixes()); formPanelUi.setAction(formActionUrl); getImporterTypes(new AsyncCallback>() { @Override @@ -69,20 +73,27 @@ public abstract class AbstractFileImportWidget extends Composite { @UiHandler("formPanelUi") void onFileImportComplete(SubmitCompleteEvent event) { - List uuids = Arrays.asList(event.getResults().split("\\n")); - sailingService.getTrackFileImportDeviceIds(uuids, new AsyncCallback>() { - @Override - public void onSuccess(List result) { - table.getDataProvider().getList().addAll(result); - showLoadingImage(false); - } - - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Could not load TrackFileImportDeviceIds: " + caught.getMessage()); - showLoadingImage(false); - } - }); + table.getDataProvider().getList().clear(); + String[] uuids = event.getResults().split("\\n"); + if (uuids.length != 1 || (uuids.length > 0 && !uuids[0].isEmpty())) { + sailingService.getTrackFileImportDeviceIds(Arrays.asList(uuids), + new AsyncCallback>() { + @Override + public void onSuccess(List result) { + table.getDataProvider().getList().addAll(result); + showLoadingImage(false); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Could not load TrackFileImportDeviceIds: " + caught.getMessage()); + showLoadingImage(false); + } + }); + } else { + showLoadingImage(false); + Window.alert(stringMessages.noTracksFound()); + } } protected void setMultipleFileUploadEnabled(boolean enableMultipleFileUpload) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java index 045fbcaf4f7..8945a126a06 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java @@ -266,19 +266,14 @@ public class RegattaConfigurationDialog extends DataEntryDialog() { - @Override - public void onValueChange(ValueChangeEvent event) { - } - }); + gateStartPathfinderBox = createCheckbox(stringMessages.activatePathfinder()); + if (originalConfiguration.gateStartConfiguration != null) { + gateStartPathfinderBox.setValue(originalConfiguration.gateStartConfiguration.hasPathfinder); + } gateStartGolfDownBox = createCheckbox(stringMessages.hasAdditionalGolfDownTime()); - gateStartGolfDownBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - } - }); - + if (originalConfiguration.gateStartConfiguration != null) { + gateStartGolfDownBox.setValue(originalConfiguration.gateStartConfiguration.hasAdditionalGolfDownTime); + } grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); grid.setWidget(0, 1, gateStartClassFlagListBox); grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("Gate Start"))); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java index c2ee844b550..c6ff1885e74 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SensorDataImportWidget.java @@ -12,7 +12,7 @@ public class SensorDataImportWidget extends AbstractFileImportWidget implements public SensorDataImportWidget(TrackFileImportDeviceIdentifierTableWrapper table, StringMessages stringMessages, final SailingServiceAsync sailingService, final ErrorReporter errorReporter) { - super("/sailingserver/sensordata/import", stringMessages.importFixes(), table, sailingService, errorReporter); + super("/sailingserver/sensordata/import", stringMessages, table, sailingService, errorReporter); setMultipleFileUploadEnabled(true); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 45a42de6f6c..6aef44e8c76 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -44,6 +45,7 @@ import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; @@ -226,6 +228,13 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat @Override public void onSuccess(List events) { existingEvents = events; + Collections.sort(existingEvents, new Comparator() { + private final NaturalComparator comp = new NaturalComparator(); + @Override + public int compare(EventDTO o1, EventDTO o2) { + return comp.compare(o1.getName(), o2.getName()); + } + }); sailingEventsListBox.addItem(stringMessages.selectSailingEvent()); for (EventDTO event : existingEvents) { sailingEventsListBox.addItem(event.getName()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 32c17dd5f83..7382d60646f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -515,7 +515,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { }); for (TracTracConfigurationDTO config : result) { TracTracEventManagementPanel.this.previousConfigurations.put(config.name, config); - TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name); + TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name, config.name); } if (!result.isEmpty()) { @@ -575,7 +575,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { liveDataURI, storedDataURI, courseDesignUpdateURI, tractracUsername, tractracPassword); if (TracTracEventManagementPanel.this.previousConfigurations.put(config.name, config) == null) { - TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name); + TracTracEventManagementPanel.this.connectionsHistoryListBox.addItem(config.name, config.name); } } })); @@ -626,19 +626,16 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { private void updatePanelFromSelectedStoredConfiguration() { int index = connectionsHistoryListBox.getSelectedIndex(); - - if (index == -1) - return; - - String configurationKey = connectionsHistoryListBox.getItemText(index); - TracTracConfigurationDTO config = previousConfigurations.get(configurationKey); - - jsonURLTextBox.setValue(config.jsonURL); - liveURITextBox.setValue(config.liveDataURI); - storedURITextBox.setValue(config.storedDataURI); - tracTracUpdateURITextBox.setValue(config.courseDesignUpdateURI); - tractracUsernameTextBox.setValue(config.tractracUsername); - tractracPasswordTextBox.setValue(config.tractracPassword); + if (index != -1) { + String configurationKey = connectionsHistoryListBox.getValue(index); + TracTracConfigurationDTO config = previousConfigurations.get(configurationKey); + jsonURLTextBox.setValue(config.jsonURL); + liveURITextBox.setValue(config.liveDataURI); + storedURITextBox.setValue(config.storedDataURI); + tracTracUpdateURITextBox.setValue(config.courseDesignUpdateURI); + tractracUsernameTextBox.setValue(config.tractracUsername); + tractracPasswordTextBox.setValue(config.tractracPassword); + } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java index 731803b8351..385413b1ecd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackFileImportWidget.java @@ -13,7 +13,7 @@ public class TrackFileImportWidget extends AbstractFileImportWidget implements I public TrackFileImportWidget(TrackFileImportDeviceIdentifierTableWrapper table, StringMessages stringMessages, final SailingServiceAsync sailingService, final ErrorReporter errorReporter) { - super("/sailingserver/trackfiles/import", stringMessages.importFixes(), table, sailingService, errorReporter); + super("/sailingserver/trackfiles/import", stringMessages, table, sailingService, errorReporter); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java index f1107a7b121..06b113daf50 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/DetailTypeFormatter.java @@ -26,12 +26,12 @@ public class DetailTypeFormatter { return stringMessages.regattaRank(); case OVERALL_RANK: return stringMessages.overallRank(); - case RACE_TOTAL_POINTS: - return stringMessages.totalPoints(); - case REGATTA_TOTAL_POINTS: - return stringMessages.totalPoints(); - case REGATTA_TOTAL_POINTS_SUM: - return stringMessages.totalPoints(); + case RACE_NET_POINTS: + return stringMessages.netPoints(); + case REGATTA_NET_POINTS: + return stringMessages.netPoints(); + case REGATTA_NET_POINTS_SUM: + return stringMessages.netPoints(); case NUMBER_OF_MANEUVERS: return stringMessages.numberOfManeuvers(); case CURRENT_SPEED_OVER_GROUND_IN_KNOTS: @@ -231,9 +231,9 @@ public class DetailTypeFormatter { case RACE_RANK: case REGATTA_RANK: case OVERALL_RANK: - case RACE_TOTAL_POINTS: - case REGATTA_TOTAL_POINTS: - case REGATTA_TOTAL_POINTS_SUM: + case RACE_NET_POINTS: + case REGATTA_NET_POINTS: + case REGATTA_NET_POINTS_SUM: case CURRENT_LEG: case TACK: case START_TACK: @@ -346,12 +346,12 @@ public class DetailTypeFormatter { return stringMessages.regattaRankTooltip(); case OVERALL_RANK: return stringMessages.overallRankTooltip(); - case RACE_TOTAL_POINTS: - return stringMessages.raceTotalPointsTooltip(); - case REGATTA_TOTAL_POINTS: - return stringMessages.regattaTotalPointsTooltip(); - case REGATTA_TOTAL_POINTS_SUM: - return stringMessages.regattaTotalPointsTooltip(); + case RACE_NET_POINTS: + return stringMessages.raceNetPointsTooltip(); + case REGATTA_NET_POINTS: + return stringMessages.regattaNetPointsTooltip(); + case REGATTA_NET_POINTS_SUM: + return stringMessages.regattaNetPointsTooltip(); case RANK_GAIN: return stringMessages.rankGainTooltip(); case TACK: diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java index e60d01853a3..249bf6eeb76 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/GwtUrlHelper.java @@ -14,14 +14,15 @@ import com.sap.sse.common.util.UrlHelper; public enum GwtUrlHelper implements UrlHelper { INSTANCE; - @Override - public String encodeUrl(String decodedUrlString) { - return URL.encode(decodedUrlString); - } - @Override public String encodeQueryString(String queryString) { - return URL.encodeQueryString(queryString); + // TODO See bug3664 => https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3664 + // Because of changes in the query string encoding, the Sail InSight-App for iOS will now have problems + // while parsing the registration URLs, which are sent to invited competitors, if e.g. the leaderboard name + // contains whitespaces. To avoid this problems, we use this temporary fix until a new version of the Sail + // InSight-App for iOS is rolled out. Afterwards this fix must be replaced by the line below. + return URL.encodePathSegment(queryString); + // return URL.encodeQueryString(queryString); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index db662990ad8..61640d30833 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -183,7 +183,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS IncrementalOrFullLeaderboardDTO getLeaderboardByName(String leaderboardName, Date date, Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - String previousLeaderboardId, boolean fillNetPointsUncorrected) throws Exception; + String previousLeaderboardId, boolean fillTotalPointsUncorrected) throws Exception; List getLeaderboards(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index d3cc144105a..1a6cd0ee4d2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -222,7 +222,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void getLeaderboardByName(String leaderboardName, Date date, Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails, - String previousLeaderboardId, boolean fillNetPointsUncorrected, + String previousLeaderboardId, boolean fillTotalPointsUncorrected, AsyncCallback callback); void getLeaderboardNames(AsyncCallback> callback); @@ -521,7 +521,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan /** * @param detailType - * supports {@link DetailType#REGATTA_RANK}, {@link DetailType#REGATTA_TOTAL_POINTS_SUM} and + * supports {@link DetailType#REGATTA_RANK}, {@link DetailType#REGATTA_NET_POINTS_SUM} and * {@link DetailType#OVERALL_RANK}. * * @return the first triple element is the race column name; then follows the list of competitors, and finally the diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 9a08004d190..f8c72f8ec72 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -499,9 +499,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String competitorRegattaDataAfterRaceN(String detailType, String raceColumnName); String showRankChart(); String rankChart(); - String totalPoints(); - String raceTotalPointsTooltip(); - String regattaTotalPointsTooltip(); + String netPoints(); + String raceNetPointsTooltip(); + String regattaNetPointsTooltip(); String overallLeaderboardSelection(); String errorTryingToObtainOverallLeaderboards(String message); String finished(); @@ -587,7 +587,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String sailIdColumnTooltip(); String rankColumnTooltip(); String carryColumnTooltip(); - String totalsColumnTooltip(); + String totalNetPointsColumnTooltip(); String windData(); String gpsData(); String status(); @@ -621,7 +621,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String correctScore(); String correctScoreFor(String competitorName, String raceColumnName); String penaltyOrRedress(); - String netScore(); + String totalScore(); String timePointMustBeAfterStartOfTracking(); String timePointMustBeBeforeEndOfTracking(); String regattaUsedForTheTrackedRace(); @@ -1139,7 +1139,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String racesScored(); String racesScoredTooltip(); String averageNumberOfOperationsPerMessage(); - String showUncorrectedNetPoints(); + String showUncorrectedTotalPoints(); String setStartTimeReceived(); String setStartTimeReceivedDescription(); String lastScoreCorrectionsTime(); @@ -1592,4 +1592,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String current(); String simulationLegendAlgorithmTimedOutText(); String simulationLegendMixedLegText(); + String noTracksFound(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 14b0210b26f..4157e4dba78 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -523,9 +523,9 @@ errorFetchingChartData=Error fetching chart data: {0} loadingCompetitorData=Loading competitor data... competitorRegattaDataAfterRaceN={0} after race {1} rankChart=Ranking -totalPoints=Total points -raceTotalPointsTooltip=Competitor''s total points in the race -regattaTotalPointsTooltip=Competitor''s total points in the regatta +netPoints=Net points +raceNetPointsTooltip=Competitor''s net points in the race +regattaNetPointsTooltip=Competitor''s net points in the regatta showRankChart=Show rank chart overallLeaderboardSelection=Overall Leaderboard errorTryingToObtainOverallLeaderboards=Error trying to obtain overall leaderboards: {0} @@ -580,7 +580,7 @@ competitorColumnTooltip=Names of the members of this competitor. sailIdColumnTooltip=Shows the flag of the country and the sail number of the competitor. rankColumnTooltip=The overall rank of this competitor in the regatta. carryColumnTooltip=Points carried over from previous races. -totalsColumnTooltip=The total of all points of a competitor in the regatta.\nSorting based on rank, not points. +totalNetPointsColumnTooltip=The total net points of a competitor in the regatta.\nSorting based on rank, not points. windData=Wind data gpsData=GPS data status=Status @@ -604,7 +604,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=This is a regatta leaderboard correctScore=Correct Score correctScoreFor=Correct score for competitor {0} in race {1} penaltyOrRedress=Penalty/Redress -netScore=Net points +totalScore=Total points timePointMustBeAfterStartOfTracking=Time point must be after start of tracking. timePointMustBeBeforeEndOfTracking=Time point must be before end of tracking. regattaUsedForTheTrackedRace=Regatta used for the tracked race @@ -819,7 +819,6 @@ importUrlInvalid=Not a valid URL for fetching leaderboardgroups: {0} importGetLeaderboardsFailed=GET leaderboardgroups request failed for host {0} with error {1} importNoDataReturned=No data was returned by the server importServerError=GET leaderboardgroups request failed with Server error. Please see server logs for more information. -importLeaderboardGroups=Leaderboard Groups: importOverrideSwitchLabel=Override existing data if names and ids match importSelectedLeaderboardGroups=Import selected leaderboard groups importSelectAtLeastOne=You have to select at least one leaderboard group to import @@ -1111,7 +1110,7 @@ simulationLegendTooltip=Time Comparison:\nRace is the leg duration measured from racesScored=Races scored racesScoredTooltip=Number of races the competitor has completed or gotten a score for in this leaderboard. averageNumberOfOperationsPerMessage=Average number of operations per message -showUncorrectedNetPoints=Show uncorrected net points +showUncorrectedTotalPoints=Show uncorrected total points setStartTimeReceived=Set start time received setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted. lastScoreCorrectionsTime=Last score correction time @@ -1562,6 +1561,7 @@ oneTurnerRight=1-Turner Right current=Current simulationLegendAlgorithmTimedOutText = out-of-bounds simulationLegendMixedLegText = ambiguous wind +noTracksFound=No tracks found currentRideHeightInMeters=Ride Height currentRideHeightInMetersTooltip=The current Foiling ride height currentOrAverageRideHeightInMeters=Ride Height (\u2205 at end) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 9c0cc3fbbb4..b16bd5d5660 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -517,9 +517,9 @@ errorFetchingChartData=Fehler beim Laden der Chartdaten: {0} loadingCompetitorData=Lade Teilnehmer-Daten competitorRegattaDataAfterRaceN={0} nach Rennen {1} rankChart=Ranking -totalPoints=Gesamtpunktzahl -raceTotalPointsTooltip=Punktzahl des Teilnehmers in dem Rennen -regattaTotalPointsTooltip=Punktzahl des Teilnehmers in der Regatta +netPoints=Gesamtpunktzahl +raceNetPointsTooltip=Punktzahl des Teilnehmers in dem Rennen +regattaNetPointsTooltip=Punktzahl des Teilnehmers in der Regatta showRankChart=Rang-Diagramm anzeigen overallLeaderboardSelection=Gesamtergebnisse errorTryingToObtainOverallLeaderboards=Fehler beim Laden der Gesamtergebnisse: {0} @@ -576,7 +576,7 @@ competitorColumnTooltip=Namen der Segler dieses Teilnehmers sailIdColumnTooltip=Zeigt die Länderflagge und die Segelnummer des Teilnehmers rankColumnTooltip=Der Gesamtplatz des Teilnehmers in der Regatta carryColumnTooltip=Übertrag vergangener Rennen -totalsColumnTooltip=Summe aller Punkte eines Teilnehmers in der Regatta.\nSortierung nach Rang, nicht Punkten. +totalNetPointsColumnTooltip=Summe aller Punkte eines Teilnehmers in der Regatta abzüglich gestrichene Wertungen.\nSortierung nach Rang, nicht Punkten. windData=Winddaten gpsData=GPS-Daten status=Status @@ -598,7 +598,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=Dies ist eine Regatta-Ergebni correctScore=Ergebnis bearbeiten correctScoreFor=Ergebnis für Teilnehmer {0} in Rennen {1} bearbeiten penaltyOrRedress=Strafe/Gutschrift -netScore=Nettoergebnis +totalScore=Punkte vor Streichern timePointMustBeAfterStartOfTracking=Der Zeitpunkt muß nach dem Trackingbeginn sein. timePointMustBeBeforeEndOfTracking=Der Zeitpunkt muß vor dem Trackingende sein. regattaUsedForTheTrackedRace=Regatta, zu der das Rennen gehört @@ -673,7 +673,7 @@ topNCompetitorsByRaceRank=Wettfahrt Top {0} topNCompetitorsByTotalRank=Gesamt Top {0} showHiddenRaces=Rennen im Status HIDDEN anzeigen simulator=Simulator -optionsBar=Optionsbar +optionsBar=Auswahlleiste simulateButton=Prognose setup=Einstellungen strength=Stärke (in kn) @@ -691,26 +691,26 @@ downWind=Vorwind strategies=Strategien omniscient=Allwissender opportunistic=Opportunist -mixed=Mixed -maps=Maps -startEnd=Start-End -racecourse=Course -summary=Summary -replay=Replay -display=Display +mixed=Gemischt +maps=Karten +startEnd=Start-Ende +racecourse=Kurs +summary=Zusammenfassung +replay=Wiedergabe +display=Anzeige simulatorPolarHeader = Polardiagramm show = zeigen hide = verbergen -leftWind=Left wind -rightWind=Right wind -strategyOption1=strategyOption1 -strategyOption2=strategyOption2 -strategyOption3=strategyOption3 +leftWind=Linker Wind +rightWind=Rechter Wind +strategyOption1=Strategie Option 1 +strategyOption2=Strategie Option 2 +strategyOption3=Strategie Option 3 legLabel=Leg -raceLabel=Race -competitorLabel=Competitor +raceLabel=Rennen +competitorLabel=Wettkampfteilnehmer freemode=Winkel erhalten -whatIfCourse=What-If Course +whatIfCourse=What-If Kurs editRegatta=Regatta bearbeiten... selectSailingEvent=Bitte Event auswählen... selectCourseArea=Bitte Bahn auswählen... @@ -812,7 +812,6 @@ importUrlInvalid=Keine gültige URL um Leaderboard Groups abzurufen: {0} importGetLeaderboardsFailed=GET leaderboardgroups Request scheiterte für den Host {0} mit dem Fehler: {1} importNoDataReturned=Es kamen keine Daten vom Server zurück importServerError=Der GET leaderboardgroups Request scheiterte an einem Server Error. Bitte schauen Sie in die Server logs für mehr Informationen. -importLeaderboardGroups=Leaderboard Groups: importOverrideSwitchLabel=Bei übereinstimmenden IDs und Namen die Daten überschreiben importSelectedLeaderboardGroups=Ausgewählte Leaderboard Groups selektieren importSelectAtLeastOne=Es muss mindestens eine Leaderboard Group ausgewählt werden @@ -1101,7 +1100,7 @@ showNumberOfRacesScored=Anzahl bepunkteter Rennen racesScored=Bepunktete Rennen racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er Punkte bekommen hat averageNumberOfOperationsPerMessage=Durchschnittliche Anzahl Operationen pro Nachricht -showUncorrectedNetPoints=Unkorrigierte Netto-Punkte anzeigen +showUncorrectedTotalPoints=Unkorrigierte Punkte anzeigen setStartTimeReceived=Setze die erhaltene Startzeit setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace, that isn''t persistent. This means, that the new value will be forgotten after the server has been restarted. setStartTimeReceivedDescription=Dies setzt die startTimeReceived des selektierten TrackedRaces, welches nicht persistent ist. Das beudeutet, dass der neue Wert vergessen wird, sobald der Server neu gestartet wurde. @@ -1547,6 +1546,7 @@ oneTurnerRight=1-Wenden-Kurs rechts current=Strömung simulationLegendAlgorithmTimedOutText=außerhalb des zul. Bereichs simulationLegendMixedLegText=mehrdeutiger Wind +noTracksFound=Keine Tracks gefunden currentRideHeightInMeters=Fahrhöhe currentRideHeightInMetersTooltip=Die aktuelle Fahrhöhe (Foiling). currentOrAverageRideHeightInMeters=Fahrhöhe (\u2205 nach Abschluss) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 2e85c231022..38e486f8db9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -356,6 +356,7 @@ series=シリーズ splitFleet=フリート分割 color=色 medalSeries=メダルシリーズ +canFleetsRunInParallel=フリートを並列に実行可能 noColor=色なし pleaseSelectARegatta=レガッタを選択してください。 addFleet=フリート追加 @@ -520,9 +521,9 @@ errorFetchingChartData=チャートデータの取込でエラー発生: {0} loadingCompetitorData=競技者データをロードしています... competitorRegattaDataAfterRaceN=レース {1} 後の {0} rankChart=ランキング -totalPoints=総得点 -raceTotalPointsTooltip=当該レースの競技者総得点 -regattaTotalPointsTooltip=当該レガッタの競技者総得点 +netPoints=総合得点 +raceNetPointsTooltip=当該レースの競技者総合得点 +regattaNetPointsTooltip=当該レガッタの競技者総合得点 showRankChart=ランクチャート表示 overallLeaderboardSelection=全体リーダーボード errorTryingToObtainOverallLeaderboards=全体リーダーボードを取得しようとしてエラーが発生: {0} @@ -577,7 +578,7 @@ competitorColumnTooltip=この競技者の乗員の名前です。 sailIdColumnTooltip=競技者の国の国旗と競技者のセールナンバーを表示します。 rankColumnTooltip=レガッタにおけるこの競技者の全体ランクです。 carryColumnTooltip=以前のレースから持ち越された得点です。 -totalsColumnTooltip=レガッタにおける競技者のすべての得点の合計です。\nソートは得点ではなくランクに基づきます。 +totalNetPointsColumnTooltip=レガッタにおける競技者の総合得点を合計したものです。\nソートは得点ではなくランクに基づきます。 windData=風データ gpsData=GPS データ status=ステータス @@ -601,7 +602,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=これはレガッタのリ correctScore=スコア修正 correctScoreFor=レース {1} での競技者 {0} のスコアを修正 penaltyOrRedress=ペナルティー/救済 -netScore=正味得点 +totalScore=総得点 timePointMustBeAfterStartOfTracking=時点は追跡の開始後でなければなりません。 timePointMustBeBeforeEndOfTracking=時点は追跡の終了前でなければなりません。 regattaUsedForTheTrackedRace=追跡レースに使用されるレガッタ @@ -680,8 +681,8 @@ setup=設定 strength=風力 (kn) oscillation=振動 oscillating=振動中 -amplitude=振幅 (勾配) -frequency=周期 (1/h) +amplitude=振幅 +frequency=周期 noise=雑音 sailing=帆走 optimality=最適性 @@ -721,7 +722,7 @@ regattaOverview=レガッタ概要 additionalSettings=追加設定 showFollowingCourseAreas=以下のコース領域を表示 showFollowingRegattas=以下のレガッタを表示 -showOnlyRacesOfSameDay=同一のレースのみ表示 +showOnlyRacesOfSameDay=同日のレースのみ表示 showOnlyCurrentlyRunningRaces=現在競技中のレースのみ表示 seriesDefinesResultDiscardingRule=シリーズにより結果取消ルールを定義 addSeries=シリーズ追加 @@ -816,7 +817,6 @@ importUrlInvalid=リーダーボードグループ取込に有効な URL であ importGetLeaderboardsFailed=リーダーボードグループ取得の要求がホスト {0} に対してエラー {1} で失敗しました importNoDataReturned=データがサーバから戻されませんでした importServerError=リーダーボード取得の要求がサーバエラーで失敗しました。詳細については、サーバログを参照してください。 -importLeaderboardGroups=リーダーボードグループ: importOverrideSwitchLabel=名称と ID が一致する場合に既存データを上書き importSelectedLeaderboardGroups=選択したリーダーボードグループのインポート importSelectAtLeastOne=インポートするリーダーボードグループを少なくとも 1 つ選択する必要があります @@ -885,7 +885,7 @@ sheetNameTooltip=テキスト項目を使用してポーラーシートに名称 timeDownwind=ダウンウィンド時間 timeReaching=リーチング時間 timeUpwind=アップウィンド時間 -raceTimeTooltip=競技者がスタート変針店を通過したときに計測を開始した、このレースで移動した合計時間\n(レースのスタート時ではなく) +raceTimeTooltip=競技者がスタート変針点を通過したときに計測を開始した、このレースで移動した合計時間\n(レースのスタート時ではなく) raceTimeDownwindTooltip=このレースでダウンウィンドで移動した合計時間 raceTimeReachingTooltip=このレースでリーチングで移動した合計時間 raceTimeUpwindTooltip=このレースでアップウィンドで移動した合計時間 @@ -907,7 +907,7 @@ line=ライン offset=オフセット doYouReallyWantToRemoveCompetitors=選択した競技者を削除しますか doYouReallyWantToRemoveCompetitor=競技者 {0} を削除しますか -filterCompetitors=競技者をフィルタリングする名称、艇種、セールナンバー、または ID +filterCompetitors=競技者をフィルタリングする名称、艇種、セールナンバー、検索タグ、または ID editCompetitor=競技者編集 pleaseEnterASailNumber=セールナンバーを入力してください successfullyAllowedCompetitorReset=競技者 {0} のリロードが許可されました。 @@ -1108,7 +1108,7 @@ simulationLegendTooltip=時間比較:\nレースは GPS から計測されたレ racesScored=スコアが記録されたレース racesScoredTooltip=このリーダーボードにおいて競技者が完了したか、得点を獲得したレースの数 averageNumberOfOperationsPerMessage=メッセージごとの操作の平均数 -showUncorrectedNetPoints=未修正正味得点表示 +showUncorrectedTotalPoints=未修正総得点表示 setStartTimeReceived=設定済スタート時刻を受信 setStartTimeReceivedDescription=これにより、永続的でない選択した TrackedRace の startTimeReceived が設定されます。したがって、サーバが再起動された後は新規の値は忘れられます。 lastScoreCorrectionsTime=最終得点修正時刻 @@ -1514,3 +1514,48 @@ windFixes=風フィックス twoXMark=2x マーク pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} +init=初期化しています... +connectionSetup=接続の設定 +connectionEstablish=接続しています... +transferStarted=データ読込 +transferCompleted=データ転送が完了、インポート処理を初期化中 +importInit=インポート初期化中 +importWait=他のデータインポート処理が終了するのを待機中 +importLeaderboardGroups=リーダーボードグループのインポート +importWindTracks=風追跡のインポート +updateEventLeaderboardGroupLinks=イベント/リーダーボードグループのリンク更新 +strategySimulatorTitle=戦略シミュレータ +controlSettings=管理設定 +windDisplay=風表示 +windSetup=風設定 +sailingSetup=帆走設定 +chooseAWindPattern=風パターン選択 +oscillations=振れ +oscillationWithGusts=振れ (突風あり) +gusts=突風 +measured=計測 +baseBearing=基本方位 +raceCourseDiff=レースコース差異 +baseSpeed=基本風速 +probability=確率 +gustSize=突風規模 +averageSpeed=平均風速 +speedVariance=風速偏差 +averageDirection=平均方向 +speedLeftSide=風速 (左側) +speedMiddle=風速 (中央) +speedRightSide=風速 (右側) +currentSpeed=現風速 +currentBearing=現方位 +percentUnit=% +perHours=/hr +pleaseSelectAValidWindPattern=有効な風パターンを選択してください。 +downwindLegsNotSupported=ダウンウィンドレグはまだサポートされていません (申し訳ありません)。 +raceLeader=レース首位艇 +opportunistLeft=オポチュニスト左 +opportunistRight=オポチュニスト右 +oneTurnerLeft=ワンターナー左 +oneTurnerRight=ワンターナー右 +current=現在 +simulationLegendAlgorithmTimedOutText=禁止区域 +simulationLegendMixedLegText=不明瞭な風 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 67ac24c3d2c..e99c9bf3921 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -356,7 +356,7 @@ series=Серия splitFleet=Разделить флот color=Цвет medalSeries=Серия на медали -canFleetsRunInParallel=Fleets can run in parallel +canFleetsRunInParallel=Флоты могут идти параллельно noColor=Без цвета pleaseSelectARegatta=Выберите регату. addFleet=Добавить флот @@ -430,7 +430,7 @@ scoringSchemeWinnerGetsEightAndInterpolation=Система высоких ба scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07=Низкий балл с турами на выбывание, победитель получает 0,7 балла scoringSchemeLowPointWithEliminationsAndRoundsWinnerGets07Description=Вариант схемы оценки с низкими баллами, использующий туры на выбывание.\nКаждый тур моделируется как серия в регате, и суда ранжируются в обратном порядке,\nначиная с наибольшего числа за первый раунд.\nВ финале и при определении 3-го места используйте ранги 1 и 2. Для полуфинального тура начните с 3. scoringSchemeLowPointForLeagueOverallLeaderboard=Низкий балл, равенство разрешается по сумме актов, затем по последнему акту -scoringSchemeLowPointForLeagueOverallLeaderboardDescription=Вместо обычной системы обратного отчета, используемой для разрешения равенства в схемах низких баллов, участники с равными суммами баллов сравниваются по сумме баллов, полученных за все акты. Если равенство сохраняется, оно разрешается по последнему акту. +scoringSchemeLowPointForLeagueOverallLeaderboardDescription=Вместо обычной системы обратного отсчета, используемой для разрешения равенства в схемах низких баллов, участники с равными суммами баллов сравниваются по сумме баллов, полученных за все акты. Если равенство сохраняется, оно разрешается по последнему акту. scoringSchemeHighPointFirstGetsOne=Высокий балл, победитель получает 1 балл scoringSchemeHighPointFirstGetsTen=Высокий балл, победитель получает 10 баллов scoringSchemeHighPointFirstGetsTenOrEight=Высокий балл, победитель получает 10 баллов (или 8) @@ -521,9 +521,9 @@ errorFetchingChartData=Ошибка при извлечении данных д loadingCompetitorData=Загрузка данных участника... competitorRegattaDataAfterRaceN={0} после гонки {1} rankChart=Ранг -totalPoints=Общий балл -raceTotalPointsTooltip=Общий балл участника в данной гонке -regattaTotalPointsTooltip=Общий балл участника в данной регате +netPoints=Чистый балл +raceNetPointsTooltip=Чистый балл участника в данной гонке +regattaNetPointsTooltip=Чистый балл участника в данной регате showRankChart=Показать диаграмму ранга overallLeaderboardSelection=Итоговая таблица лидеров errorTryingToObtainOverallLeaderboards=Ошибка при попытке получить итоговые таблицы лидеров: {0} @@ -578,7 +578,7 @@ competitorColumnTooltip=Имена членов данного участник sailIdColumnTooltip=Показывает флаг страны и хвостовой номер участника. rankColumnTooltip=Общий ранг данного участника в регате. carryColumnTooltip=Баллы, перенесенные из других гонок. -totalsColumnTooltip=Общая сумма всех баллов участника в регате.\nСортировка по рангу, не по баллам. +totalNetPointsColumnTooltip=Общая чистая сумма баллов участника в данной регате.\nСортировка по рангу, не по баллам. windData=Данные о ветре gpsData=Данные GPS status=Статус @@ -602,7 +602,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=Это таблица лид correctScore=Правильная оценка correctScoreFor=Правильная оценка для участника {0} в гонке {1} penaltyOrRedress=Взыскание/компенсация -netScore=Точки сети +totalScore=Общий балл timePointMustBeAfterStartOfTracking=Отметка времени должна быть после начала отслеживания. timePointMustBeBeforeEndOfTracking=Отметка времени должна быть раньше окончания отслеживания. regattaUsedForTheTrackedRace=Регата, используемая для отслеживаемой гонки @@ -817,7 +817,6 @@ importUrlInvalid=Недействительный URL-адрес для извл importGetLeaderboardsFailed=Сбой GET-запроса групп таблиц лидеров для хоста {0} с ошибкой {1} importNoDataReturned=Сервер не вернул никаких данных importServerError=Сбой GET-запроса групп таблиц лидеров с ошибкой сервера. Дополнительные сведения см. в журналах сервера. -importLeaderboardGroups=Группы таблиц лидеров: importOverrideSwitchLabel=Переопределять существующие данные при совпадении названий и идентификаторов importSelectedLeaderboardGroups=Импорт выбранных групп таблиц лидеров importSelectAtLeastOne=Выберите не менее одной группы таблиц лидеров для импорта @@ -908,7 +907,7 @@ line=Линия offset=Сдвиг doYouReallyWantToRemoveCompetitors=Действительно удалить выбранных участников? doYouReallyWantToRemoveCompetitor=Действительно удалить участника {0}? -filterCompetitors=Фильтрация участников по названию, классу лодки, номеру на парусе или идентификатору +filterCompetitors=Фильтрация участников по названию, классу лодки, номеру на парусе, тегу поиска или идентификатору editCompetitor=Изменить участника pleaseEnterASailNumber=Введите номер на парусе successfullyAllowedCompetitorReset=Успешно разрешена перезагрузка участников: {0}. @@ -1105,11 +1104,11 @@ errorFetchingWindStreamletData=Ошибка при извлечении данн showWindStreamletOverlay=Показать наложение потоков ветра showWindStreamletColors=Цвет потока соответствует скорости ветра showSimulationOverlay=Показать наложение симуляции -simulationLegendTooltip=Сравнение времени:\nгонка — это продолжительность отрезка, измеренная по данным GPS, "Всезнайка" — курс при наличии всех сведений о ветре, "Авантюрист" — курсы при принятии решения сделать поворот для измерения дистанции до следующей отметки, "1 поворот" — курсы с одним поворотом при наличии всех сведений о ветре +simulationLegendTooltip=Сравнение времени:\nгонка — это продолжительность отрезка, измеренная по данным GPS, "Информированный" — курс при наличии всех сведений о ветре, "Ситуативный" — курсы при принятии решения сделать поворот для измерения дистанции до следующей отметки, "1 поворот" — курсы с одним поворотом при наличии всех сведений о ветре racesScored=Оцененные гонки racesScoredTooltip=Число гонок, которые участник завершил или за которые имеет оценки в данной таблице лидеров. averageNumberOfOperationsPerMessage=Среднее число операций на сообщение -showUncorrectedNetPoints=Показать неисправленные точки сети +showUncorrectedTotalPoints=Показать неисправленный общий балл setStartTimeReceived=Установить полученное время старта setStartTimeReceivedDescription=Устанавливает полученное время старта для выбранной отслеживаемой гонки, которое не хранится постоянно. Это значит, что после перезагрузки сервера новое значение будет забыто. lastScoreCorrectionsTime=Время последнего исправления оценки @@ -1515,3 +1514,48 @@ windFixes=Замеры ветра twoXMark=2-отмет. pagerStateInfo={0,number,#,###}–{1,number,#,###} из {2,number,#,###} pagerStateInfo[false]={0,number,#,###}–{1,number,#,###} из {2,number,#,###} +init=Инициализация... +connectionSetup=Установка соединения +connectionEstablish=Соединение... +transferStarted=Чтение данных +transferCompleted=Передача данных завершена, инициализация операции импорта +importInit=Инициализация импорта +importWait=Ожидание завершения других операций импорта данных +importLeaderboardGroups=Импорт групп таблиц лидеров +importWindTracks=Импорт данных отслеживания ветра +updateEventLeaderboardGroupLinks=Обновление ссылок на группы таблиц лидеров событий +strategySimulatorTitle=Симулятор стратегии +controlSettings=Настройки управления +windDisplay=Отображение ветра +windSetup=Настройка ветра +sailingSetup=Настройка плавания +chooseAWindPattern=Выберите ветровой режим +oscillations=Колебания +oscillationWithGusts=Колебания с порывами +gusts=Порывы +measured=Измеренная +baseBearing=Базовый ветер +raceCourseDiff=Отклонение от курса гонки +baseSpeed=Базовая скорость +probability=Вероятность +gustSize=Величина порыва +averageSpeed=Средняя скорость +speedVariance=Отклонение скорости +averageDirection=Среднее направление +speedLeftSide=Скорость слева +speedMiddle=Скорость в середине +speedRightSide=Скорость справа +currentSpeed=Текущая скорость +currentBearing=Текущее направление +percentUnit=% +perHours=в ч. +pleaseSelectAValidWindPattern=Выберите допустимый режим ветра. +downwindLegsNotSupported=Подветренные галсы пока что НЕ поддерживаются. Извините :) +raceLeader=Лидер гонки +opportunistLeft=Ситуативный левый +opportunistRight=Ситуативный правый +oneTurnerLeft=1 поворот левый +oneTurnerRight=1 поворот правый +current=Текущий +simulationLegendAlgorithmTimedOutText=вне ограничений +simulationLegendMixedLegText=неопределенный ветер diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 4754a13add1..69ce2937868 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -356,7 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 -canFleetsRunInParallel=Fleets can run in parallel +canFleetsRunInParallel=船队可以并行航行 noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 @@ -521,9 +521,9 @@ errorFetchingChartData=获取记录数据出错: {0} loadingCompetitorData=正在加载参赛队数据... competitorRegattaDataAfterRaceN={0} 比赛轮次后 {1} rankChart=排名 -totalPoints=总分 -raceTotalPointsTooltip=参赛队在本轮次比赛中的总分 -regattaTotalPointsTooltip=参赛队在比赛中的总分 +netPoints=净分 +raceNetPointsTooltip=参赛队在本轮次比赛中的净分 +regattaNetPointsTooltip=参赛队在比赛中的净分 showRankChart=显示排名记录 overallLeaderboardSelection=总积分榜 errorTryingToObtainOverallLeaderboards=尝试获取总积分榜出错: {0} @@ -578,7 +578,7 @@ competitorColumnTooltip=此参赛队的成员姓名。 sailIdColumnTooltip=显示参赛队所代表的国家国旗和帆号。 rankColumnTooltip=参赛队在比赛中的总名次。 carryColumnTooltip=前几个轮次的比赛中的得分。 -totalsColumnTooltip=参赛队在比赛中的总得分。\n按名次(而不是得分)排序。 +totalNetPointsColumnTooltip=参赛队在比赛中的总净分。\n按名次(而不是得分)排序。 windData=风力数据 gpsData=GPS 数据 status=状态 @@ -602,7 +602,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=有比赛积分榜。您只 correctScore=更正得分 correctScoreFor=更正参赛队{0}在比赛轮次{1}中的得分 penaltyOrRedress=惩罚/补偿 -netScore=净分 +totalScore=总分 timePointMustBeAfterStartOfTracking=时间点必须在跟踪开始之后。 timePointMustBeBeforeEndOfTracking=时间点必须在跟踪结束之前。 regattaUsedForTheTrackedRace=用于跟踪的比赛轮次的比赛 @@ -817,7 +817,6 @@ importUrlInvalid=获取积分榜分组的 URL 无效: {0} importGetLeaderboardsFailed=主机{0}获取积分榜分组请求失败,错误为{1} importNoDataReturned=服务器未返回数据 importServerError=获取积分榜请求失败,服务器出错。请查看服务器日志,了解更多信息。 -importLeaderboardGroups=积分榜分组: importOverrideSwitchLabel=如果名称和编号匹配,则覆盖现有数据 importSelectedLeaderboardGroups=导入选择的积分榜分组 importSelectAtLeastOne=必须至少选择一个要导入的积分榜分组 @@ -908,7 +907,7 @@ line=线 offset=分支 doYouReallyWantToRemoveCompetitors=是否确定移除所选的参赛队? doYouReallyWantToRemoveCompetitor=是否确定移除参赛队 {0}? -filterCompetitors=按名称、船只级别或帆号或编号筛选参赛队 +filterCompetitors=按名称、船只级别或帆号、搜索标记或编号筛选参赛队 editCompetitor=编辑参赛队 pleaseEnterASailNumber=请输入帆号 successfullyAllowedCompetitorReset=已成功允许重新加载参赛队{0}。 @@ -1109,7 +1108,7 @@ simulationLegendTooltip=时间比较:\n比赛轮次指根据 GPS 计算的航 racesScored=记分的比赛轮次 racesScoredTooltip=参赛队完成并记录在此积分榜上的比赛轮次数。 averageNumberOfOperationsPerMessage=各消息操作的平均数 -showUncorrectedNetPoints=显示未更正的净分 +showUncorrectedTotalPoints=显示未更正的总分 setStartTimeReceived=设置接收的开始时间 setStartTimeReceivedDescription=设置非持久的所选 TrackedRace 的 startTimeReceived。 这意味着,重启服务器后,将忘记新值。 lastScoreCorrectionsTime=上次得分更正时间 @@ -1515,3 +1514,48 @@ windFixes=风力修复 twoXMark=2 个标志 pagerStateInfo={0,number,#,###}-{1,number,#,###}/{2,number,#,###} pagerStateInfo[false]={0,number,#,###}-{1,number,#,###}/{2,number,#,###} 以上 +init=正在初始化... +connectionSetup=正在设置连接 +connectionEstablish=正在连接... +transferStarted=正在读取数据 +transferCompleted=数据传输完成,正在初始化导入操作 +importInit=正在初始化导入 +importWait=正在等待其他数据导入操作完成 +importLeaderboardGroups=正在导入积分榜分组 +importWindTracks=正在导入风力跟踪 +updateEventLeaderboardGroupLinks=正在更新活动积分榜分组链接 +strategySimulatorTitle=策略模拟器 +controlSettings=控制设置 +windDisplay=风力显示 +windSetup=风力设置 +sailingSetup=航行设置 +chooseAWindPattern=选择风力模式 +oscillations=振荡 +oscillationWithGusts=振荡和阵风 +gusts=阵风 +measured=测量 +baseBearing=基本航向角 +raceCourseDiff=比赛轮次场地差异 +baseSpeed=基本速度 +probability=概率 +gustSize=阵风大小 +averageSpeed=平均速度 +speedVariance=速度偏差 +averageDirection=平均方向 +speedLeftSide=速度 - 左侧 +speedMiddle=速度 - 中间 +speedRightSide=速度 - 右侧 +currentSpeed=当前速度 +currentBearing=当前航向角 +percentUnit=% +perHours=每小时 +pleaseSelectAValidWindPattern=请输入有效的风力模式。 +downwindLegsNotSupported=暂不支持顺风航段。抱歉 :) +raceLeader=比赛轮次第一名 +opportunistLeft=投机级 - 左 +opportunistRight=投机级 - 右 +oneTurnerLeft=一圈即成级 - 左 +oneTurnerRight=一圈即成级 - 右 +current=水流 +simulationLegendAlgorithmTimedOutText=越界 +simulationLegendMixedLegText=方向不定的风 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties index 4754a13add1..69ce2937868 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties @@ -356,7 +356,7 @@ series=系列 splitFleet=船队区分 color=颜色 medalSeries=奖牌轮系列 -canFleetsRunInParallel=Fleets can run in parallel +canFleetsRunInParallel=船队可以并行航行 noColor=没有颜色 pleaseSelectARegatta=请选择比赛。 addFleet=添加船队 @@ -521,9 +521,9 @@ errorFetchingChartData=获取记录数据出错: {0} loadingCompetitorData=正在加载参赛队数据... competitorRegattaDataAfterRaceN={0} 比赛轮次后 {1} rankChart=排名 -totalPoints=总分 -raceTotalPointsTooltip=参赛队在本轮次比赛中的总分 -regattaTotalPointsTooltip=参赛队在比赛中的总分 +netPoints=净分 +raceNetPointsTooltip=参赛队在本轮次比赛中的净分 +regattaNetPointsTooltip=参赛队在比赛中的净分 showRankChart=显示排名记录 overallLeaderboardSelection=总积分榜 errorTryingToObtainOverallLeaderboards=尝试获取总积分榜出错: {0} @@ -578,7 +578,7 @@ competitorColumnTooltip=此参赛队的成员姓名。 sailIdColumnTooltip=显示参赛队所代表的国家国旗和帆号。 rankColumnTooltip=参赛队在比赛中的总名次。 carryColumnTooltip=前几个轮次的比赛中的得分。 -totalsColumnTooltip=参赛队在比赛中的总得分。\n按名次(而不是得分)排序。 +totalNetPointsColumnTooltip=参赛队在比赛中的总净分。\n按名次(而不是得分)排序。 windData=风力数据 gpsData=GPS 数据 status=状态 @@ -602,7 +602,7 @@ cannotAddRacesToRegattaLeaderboardButOnlyToRegatta=有比赛积分榜。您只 correctScore=更正得分 correctScoreFor=更正参赛队{0}在比赛轮次{1}中的得分 penaltyOrRedress=惩罚/补偿 -netScore=净分 +totalScore=总分 timePointMustBeAfterStartOfTracking=时间点必须在跟踪开始之后。 timePointMustBeBeforeEndOfTracking=时间点必须在跟踪结束之前。 regattaUsedForTheTrackedRace=用于跟踪的比赛轮次的比赛 @@ -817,7 +817,6 @@ importUrlInvalid=获取积分榜分组的 URL 无效: {0} importGetLeaderboardsFailed=主机{0}获取积分榜分组请求失败,错误为{1} importNoDataReturned=服务器未返回数据 importServerError=获取积分榜请求失败,服务器出错。请查看服务器日志,了解更多信息。 -importLeaderboardGroups=积分榜分组: importOverrideSwitchLabel=如果名称和编号匹配,则覆盖现有数据 importSelectedLeaderboardGroups=导入选择的积分榜分组 importSelectAtLeastOne=必须至少选择一个要导入的积分榜分组 @@ -908,7 +907,7 @@ line=线 offset=分支 doYouReallyWantToRemoveCompetitors=是否确定移除所选的参赛队? doYouReallyWantToRemoveCompetitor=是否确定移除参赛队 {0}? -filterCompetitors=按名称、船只级别或帆号或编号筛选参赛队 +filterCompetitors=按名称、船只级别或帆号、搜索标记或编号筛选参赛队 editCompetitor=编辑参赛队 pleaseEnterASailNumber=请输入帆号 successfullyAllowedCompetitorReset=已成功允许重新加载参赛队{0}。 @@ -1109,7 +1108,7 @@ simulationLegendTooltip=时间比较:\n比赛轮次指根据 GPS 计算的航 racesScored=记分的比赛轮次 racesScoredTooltip=参赛队完成并记录在此积分榜上的比赛轮次数。 averageNumberOfOperationsPerMessage=各消息操作的平均数 -showUncorrectedNetPoints=显示未更正的净分 +showUncorrectedTotalPoints=显示未更正的总分 setStartTimeReceived=设置接收的开始时间 setStartTimeReceivedDescription=设置非持久的所选 TrackedRace 的 startTimeReceived。 这意味着,重启服务器后,将忘记新值。 lastScoreCorrectionsTime=上次得分更正时间 @@ -1515,3 +1514,48 @@ windFixes=风力修复 twoXMark=2 个标志 pagerStateInfo={0,number,#,###}-{1,number,#,###}/{2,number,#,###} pagerStateInfo[false]={0,number,#,###}-{1,number,#,###}/{2,number,#,###} 以上 +init=正在初始化... +connectionSetup=正在设置连接 +connectionEstablish=正在连接... +transferStarted=正在读取数据 +transferCompleted=数据传输完成,正在初始化导入操作 +importInit=正在初始化导入 +importWait=正在等待其他数据导入操作完成 +importLeaderboardGroups=正在导入积分榜分组 +importWindTracks=正在导入风力跟踪 +updateEventLeaderboardGroupLinks=正在更新活动积分榜分组链接 +strategySimulatorTitle=策略模拟器 +controlSettings=控制设置 +windDisplay=风力显示 +windSetup=风力设置 +sailingSetup=航行设置 +chooseAWindPattern=选择风力模式 +oscillations=振荡 +oscillationWithGusts=振荡和阵风 +gusts=阵风 +measured=测量 +baseBearing=基本航向角 +raceCourseDiff=比赛轮次场地差异 +baseSpeed=基本速度 +probability=概率 +gustSize=阵风大小 +averageSpeed=平均速度 +speedVariance=速度偏差 +averageDirection=平均方向 +speedLeftSide=速度 - 左侧 +speedMiddle=速度 - 中间 +speedRightSide=速度 - 右侧 +currentSpeed=当前速度 +currentBearing=当前航向角 +percentUnit=% +perHours=每小时 +pleaseSelectAValidWindPattern=请输入有效的风力模式。 +downwindLegsNotSupported=暂不支持顺风航段。抱歉 :) +raceLeader=比赛轮次第一名 +opportunistLeft=投机级 - 左 +opportunistRight=投机级 - 右 +oneTurnerLeft=一圈即成级 - 左 +oneTurnerRight=一圈即成级 - 右 +current=水流 +simulationLegendAlgorithmTimedOutText=越界 +simulationLegendMixedLegText=方向不定的风 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index 0a262e4a19d..51c44328066 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java @@ -4,6 +4,8 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.MediaElement; import com.google.gwt.dom.client.Style; import com.google.gwt.event.dom.client.ChangeEvent; @@ -170,40 +172,38 @@ public class NewMediaDialog extends DataEntryDialog { @Override public void onChange(ChangeEvent event) { - updateFromUrl(); + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + updateFromUrl(); + } + }); } - }); return result; } protected void updateFromUrl() { String url = urlBox.getValue(); - String youtubeId = YoutubeApi.getIdByUrl(url); - if (youtubeId != null) { mediaTrack.url = youtubeId; mediaTrack.mimeType = MimeType.youtube; loadYoutubeMetadata(youtubeId); - setUiEnabled(false); } else { mediaTrack.url = url; loadMediaDuration(); - String lastPathSegment = mediaTrack.url.substring(mediaTrack.url.lastIndexOf('/') + 1); int dotPos = lastPathSegment.lastIndexOf('.'); if (dotPos >= 0) { mediaTrack.title = lastPathSegment.substring(0, dotPos); String fileEnding = lastPathSegment.substring(dotPos + 1).toLowerCase(); - mediaTrack.mimeType = MimeType.byName(fileEnding); } else { mediaTrack.title = mediaTrack.url; mediaTrack.mimeType = null; } } - refreshUI(); } @@ -246,9 +246,12 @@ public class NewMediaDialog extends DataEntryDialog { setTimeout( function() { //Remove temporary script element. - document.body + if (window != null && window.youtubeMetadataCallbackScript != null) { + document.body .removeChild(window.youtubeMetadataCallbackScript); - delete window.youtubeMetadataCallbackScript; + + delete window.youtubeMetadataCallbackScript; + } that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::setUiEnabled(Z)(true); }, 2000); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java index 5dfc3baa56e..c8e820693bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorLeaderboardChart.java @@ -154,7 +154,7 @@ public abstract class AbstractCompetitorLeaderboardChart(); availableDetailsTypes.add(DetailType.REGATTA_RANK); - availableDetailsTypes.add(DetailType.REGATTA_TOTAL_POINTS_SUM); + availableDetailsTypes.add(DetailType.REGATTA_NET_POINTS_SUM); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java index a725d3cca94..854a080fb04 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/filter/CompetitorRaceRankFilterUIFactory.java @@ -15,8 +15,8 @@ public class CompetitorRaceRankFilterUIFactory extends AbstractCompetitorNumberF this(new CompetitorRaceRankFilter()); } - public CompetitorRaceRankFilterUIFactory(CompetitorRaceRankFilter competitorRaceNetPointsFilter) { - super(competitorRaceNetPointsFilter, BinaryOperator.Operators.LessThanEquals); + public CompetitorRaceRankFilterUIFactory(CompetitorRaceRankFilter competitorRaceTotalPointsFilter) { + super(competitorRaceTotalPointsFilter, BinaryOperator.Operators.LessThanEquals); supportedOperators.add(BinaryOperator.Operators.LessThanEquals); supportedOperators.add(BinaryOperator.Operators.GreaterThanEquals); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java index dc295973248..f6abc0c1530 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java @@ -33,7 +33,7 @@ public class BoatClassVectorGraphicsResolver { BoatClassMasterdata._29ER, BoatClassMasterdata._18Footer); BoatClassVectorGraphics extreme40 = new Extreme40VectorGraphics(BoatClassMasterdata.EXTREME_40, BoatClassMasterdata.D_35); - BoatClassVectorGraphics gc32 = new GC32VectorGraphics(BoatClassMasterdata.GC_32); + BoatClassVectorGraphics gc32 = new GC32VectorGraphics(BoatClassMasterdata.GC_32, BoatClassMasterdata.M32); BoatClassVectorGraphics smallMultihull = new SmallMultihullVectorGraphics(BoatClassMasterdata.NACRA_17, BoatClassMasterdata.F_16, BoatClassMasterdata.F_18, BoatClassMasterdata.HOBIE_WILD_CAT, BoatClassMasterdata.HOBIE_TIGER, BoatClassMasterdata.A_CAT, BoatClassMasterdata.TORNADO, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java index 5ec1cca6b9e..4f0bd829032 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/common/client/YoutubeApi.java @@ -14,7 +14,7 @@ import com.google.gwt.regexp.shared.RegExp; public class YoutubeApi { private static final RegExp YOUTUBE_ID_REGEX = RegExp.compile("^.*(youtu.be/|v/|u/\\w/|embed/|watch\\?v=|\\&v=)([^#\\&\\?]*).*"); - private static final RegExp HTTP_FTP_REGEX = RegExp.compile("^(http|ftp).*"); // starting with http, https or ftp + private static final RegExp HTTP_FTP_FILE_REGEX = RegExp.compile("^(http|ftp|file).*"); // starting with http, https or ftp /** * Extracts the youtube id of the passed youtube video URL. @@ -27,7 +27,7 @@ public class YoutubeApi { MatchResult match = YOUTUBE_ID_REGEX.exec(url); if ((match != null) && (match.getGroupCount() == 3)) { return match.getGroup(2); - } else if (HTTP_FTP_REGEX.exec(url) == null) { //--> doesn't start with either http, https or ftp --> supposed to be a naked youtube id + } else if (HTTP_FTP_FILE_REGEX.exec(url) == null) { //--> doesn't start with either http, https or ftp --> supposed to be a naked youtube id return url.trim(); } else { return null; // --> plain http, https or ftp URL --> no youtube diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 79f24c91d28..602c1ee6b30 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -160,7 +160,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { String chartDetailParam = GwtHttpRequestUtils.getStringParameter(LeaderboardUrlSettings.PARAM_CHART_DETAIL, null); final DetailType chartDetailType; if (chartDetailParam != null && (DetailType.REGATTA_RANK.name().equals(chartDetailParam) || DetailType.OVERALL_RANK.name().equals(chartDetailParam) || - DetailType.REGATTA_TOTAL_POINTS_SUM.name().equals(chartDetailParam))) { + DetailType.REGATTA_NET_POINTS_SUM.name().equals(chartDetailParam))) { chartDetailType = DetailType.valueOf(chartDetailParam); } else { chartDetailType = leaderboardType.isMetaLeaderboard() ? DetailType.OVERALL_RANK : DetailType.REGATTA_RANK; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 9392d7df64a..eac5f79fdeb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -526,7 +526,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS getSailingService().getLeaderboardByName(getLeaderboardName(), timer.getPlayMode() == PlayModes.Live ? null : getLeaderboardDisplayDate(), /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), - shallAddOverallDetails(), previousLeaderboard.getId(), /* fillNetPointsUncorrected */ false, + shallAddOverallDetails(), previousLeaderboard.getId(), /* fillTotalPointsUncorrected */ false, new MarkedAsyncCallback( new AsyncCallback() { @Override @@ -768,8 +768,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS double addedScores = 0; for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) { LeaderboardEntryDTO entryBefore = object.fieldsByRaceColumnName.get(raceColumn.getName()); - if (entryBefore.totalPoints != null) { - addedScores += entryBefore.totalPoints; + if (entryBefore.netPoints != null) { + addedScores += entryBefore.netPoints; } // else "add 0" if (raceColumn.getName().equals(getRaceColumnName())) { @@ -780,11 +780,11 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } /** - * Displays a combination of total points and maxPointsReason in bold, transparent, strike-through, depending on + * Displays a combination of net points and maxPointsReason in bold, transparent, strike-through, depending on * various criteria. Here's how: * *
    -         *                                  total points                |    maxPointsReason
    +         *                                  net points                |    maxPointsReason
              * -------------------------------+-----------------------------+-----------------------
              *  not discarded, no maxPoints   | bold                        | none
              *  not discarded, maxPoints      | bold                        | transparent
    @@ -799,10 +799,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                 	boolean isLive = isLive(entry.fleet);
                     final String textColor = isLive ? IS_LIVE_TEXT_COLOR : DEFAULT_TEXT_COLOR;
                     final String addedScores = isShowAddedScores() ? scoreFormat.format(computeAddedScores(object)) : "";
    -                String totalOrAddedPointsAsText = isShowAddedScores() ? addedScores
    -                        : entry.totalPoints == null ? "" : scoreFormat.format(entry.totalPoints);
                     String netOrAddedPointsAsText = isShowAddedScores() ? addedScores
                             : entry.netPoints == null ? "" : scoreFormat.format(entry.netPoints);
    +                String totalOrAddedPointsAsText = isShowAddedScores() ? addedScores
    +                        : entry.totalPoints == null ? "" : scoreFormat.format(entry.totalPoints);
                     if (entry.fleet != null && entry.fleet.getColor() != null) {
                         html.append(raceColumnTemplate.cellFrameWithTextColorAndFleetBorder(textColor, entry.fleet.getColor().getAsHtml()));
                     } else {
    @@ -812,15 +812,15 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                     if (entry.reasonForMaxPoints == null || entry.reasonForMaxPoints == MaxPointsReason.NONE) {
                         if (!entry.discarded) {
                             html.appendHtmlConstant("");
    -                        html.appendHtmlConstant(totalOrAddedPointsAsText);
    +                        html.appendHtmlConstant(netOrAddedPointsAsText);
                             html.appendHtmlConstant("");
                         } else {
                             html.appendHtmlConstant(" ");
    -                        html.appendHtmlConstant(netOrAddedPointsAsText);
    +                        html.appendHtmlConstant(totalOrAddedPointsAsText);
                             html.appendHtmlConstant("");
                         }
                     } else {
    -                    html.appendHtmlConstant(" ");
                         if (entry.discarded) {
                             html.appendHtmlConstant("");
    @@ -924,8 +924,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
             public String getValue(LeaderboardRowDTO object) {
                 // The following code exists only for robustness. This method should never be called because
                 // RaceColumn implements its own render(...) method which doesn't make use of getValue(...)
    -            final Double totalPoints = object.fieldsByRaceColumnName.get(getRaceColumnName()).totalPoints;
    -            return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints));
    +            final Double netPoints = object.fieldsByRaceColumnName.get(getRaceColumnName()).netPoints;
    +            return "" + (netPoints == null ? "" : scoreFormat.format(netPoints));
             }
     
             @Override
    @@ -1479,7 +1479,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                 int racesSailedInRow = 0;
                 for (RaceColumnDTO raceColumn : getLeaderboard().getRaceList()) {
                     LeaderboardEntryDTO entryBefore = object.fieldsByRaceColumnName.get(raceColumn.getName());
    -                if (entryBefore.totalPoints != null) {
    +                if (entryBefore.netPoints != null) {
                         if (entryBefore.reasonForMaxPoints.equals(MaxPointsReason.NONE) ||
                                 !Util.contains(Arrays.asList(MAX_POINTS_REASONS_THAT_IDENTIFY_NON_FINISHED_RACES), entryBefore.reasonForMaxPoints)) {
                             racesSailedInRow++;
    @@ -1528,15 +1528,15 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
         }
     
         /**
    -     * Displays the totals for a competitor for the entire leaderboard.
    +     * Displays the net points totals for a competitor for the entire leaderboard.
          * 
          * @author Axel Uhl (D043530)
          * 
          */
    -    private class TotalsColumn extends LeaderboardSortableColumnWithMinMax {
    +    private class TotalNetPointssColumn extends LeaderboardSortableColumnWithMinMax {
             private final String columnStyle;
     
    -        protected TotalsColumn(String columnStyle) {
    +        protected TotalNetPointssColumn(String columnStyle) {
                 super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this);
                 this.columnStyle = columnStyle;
                 setHorizontalAlignment(ALIGN_CENTER);
    @@ -1544,8 +1544,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
     
             @Override
             public String getValue(LeaderboardRowDTO object) {
    -            Double totalPoints = object.totalPoints;
    -            return "" + (totalPoints == null ? "" : scoreFormat.format(totalPoints));
    +            Double netPoints = object.netPoints;
    +            return "" + (netPoints == null ? "" : scoreFormat.format(netPoints));
             }
     
             @Override
    @@ -1575,7 +1575,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
     
             @Override
             public SafeHtmlHeader getHeader() {
    -            return new SafeHtmlHeaderWithTooltip(SafeHtmlUtils.fromString(stringMessages.total()), stringMessages.totalsColumnTooltip());
    +            return new SafeHtmlHeaderWithTooltip(SafeHtmlUtils.fromString(stringMessages.total()), stringMessages.totalNetPointsColumnTooltip());
             }
         }
     
    @@ -2262,7 +2262,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
                 GetLeaderboardByNameAction getLeaderboardByNameAction = new GetLeaderboardByNameAction(sailingService,
                         getLeaderboardName(), useNullAsTimePoint() ? null : date,
                         /* namesOfRacesForWhichToLoadLegDetails */getNamesOfExpandedRaces(), shallAddOverallDetails(), /* previousLeaderboard */
    -                    getLeaderboard(), isFillNetPointsUncorrected(), timer, errorReporter, stringMessages);
    +                    getLeaderboard(), isFillTotalPointsUncorrected(), timer, errorReporter, stringMessages);
                 this.asyncActionsExecutor.execute(getLeaderboardByNameAction, LOAD_LEADERBOARD_DATA_CATEGORY,
                         new AsyncCallback() {
                             @Override
    @@ -2284,7 +2284,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
             }
         }
     
    -    protected boolean isFillNetPointsUncorrected() {
    +    protected boolean isFillTotalPointsUncorrected() {
             return false;
         }
     
    @@ -2845,7 +2845,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
          * If the last column is the totals column, remove it. Add the race column as the last column.
          */
         private void addRaceColumn(RaceColumn raceColumn) {
    -        if (getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn) {
    +        if (getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalNetPointssColumn) {
                 removeColumn(getLeaderboardTable().getColumnCount() - 1);
             }
             addColumn(raceColumn);
    @@ -2962,8 +2962,8 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS
         private void ensureTotalsColumn() {
             // add a totals column on the right
             if (getLeaderboardTable().getColumnCount() == 0
    -                || !(getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalsColumn)) {
    -            addColumn(new TotalsColumn(TOTAL_COLUMN_STYLE));
    +                || !(getLeaderboardTable().getColumn(getLeaderboardTable().getColumnCount() - 1) instanceof TotalNetPointssColumn)) {
    +            addColumn(new TotalNetPointssColumn(TOTAL_COLUMN_STYLE));
             }
         }
         
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java
    index dec01ccef93..42d5159e8da 100755
    --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java
    +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardUrlConfigurationDialog.java
    @@ -178,10 +178,10 @@ public class LeaderboardUrlConfigurationDialog extends SettingsDialog> {
         private final ListBox maxPointsBox;
    -    private final DoubleBox netPointsBox;
    +    private final DoubleBox totalPointsBox;
         private final StringMessages stringMessages;
         
         public EditScoreDialog(StringMessages stringMessages, String competitorName, String raceColumnName,
    -            MaxPointsReason oldMaxPointsReason, Double oldNetPoints, DialogCallback> callback) {
    +            MaxPointsReason oldMaxPointsReason, Double oldTotalPoints, DialogCallback> callback) {
             super(stringMessages.correctScore(), stringMessages.correctScoreFor(competitorName, raceColumnName),
                     stringMessages.ok(), stringMessages.cancel(), /* validator */ null, /* animationEnabled */ true,
                     callback);
    @@ -34,9 +34,9 @@ public class EditScoreDialog extends DataEntryDialog(maxPointsReason, netScore);
    +        final Double totalScore = totalPointsBox.getValue();
    +        return new Util.Pair(maxPointsReason, totalScore);
         }
     
         @Override
    @@ -57,19 +57,19 @@ public class EditScoreDialog extends DataEntryDialog suppressedCompetitorsTable;
         final private ListDataProvider suppressedCompetitorsShown;
     
    -    private CheckBox showUncorrectedNetPointsCheckbox;
    +    private CheckBox showUncorrectedTotalPointsCheckbox;
         
         private class SettingsClickHandler implements ClickHandler {
             private final StringMessages stringMessages;
    @@ -334,13 +334,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                             }
     
                             @Override
    -                        public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) {
    +                        public void onSuccess(Util.Triple newTotalAndNetPointsAndIsCorrected) {
                                 setBusyState(false);
                                 row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints = value == null
                                         || value.length() == 0 ? null : MaxPointsReason.valueOf(value.trim());
    -                            row.fieldsByRaceColumnName.get(raceColumnName).netPoints = newNetAndTotalPointsAndIsCorrected.getA();
    -                            row.fieldsByRaceColumnName.get(raceColumnName).totalPoints = newNetAndTotalPointsAndIsCorrected.getB();
    -                            row.fieldsByRaceColumnName.get(raceColumnName).netPointsCorrected = newNetAndTotalPointsAndIsCorrected.getC();
    +                            row.fieldsByRaceColumnName.get(raceColumnName).totalPoints = newTotalAndNetPointsAndIsCorrected.getA();
    +                            row.fieldsByRaceColumnName.get(raceColumnName).netPoints = newTotalAndNetPointsAndIsCorrected.getB();
    +                            row.fieldsByRaceColumnName.get(raceColumnName).totalPointsCorrected = newTotalAndNetPointsAndIsCorrected.getC();
                                 getCell().setViewData(row, null); // ensure that getValue() is called again
                                 whiteboard.setObjectWithWhichToUpdateRow(row);
                             }
    @@ -360,10 +360,10 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
             }
         }
         
    -    private class UncorrectedNetPointsViewProvider extends AbstractRowUpdateWhiteboardProducerThatHasCell {
    +    private class UncorrectedTotalPointsViewProvider extends AbstractRowUpdateWhiteboardProducerThatHasCell {
             private final String raceColumnName;
             
    -        protected UncorrectedNetPointsViewProvider(String raceColumnName) {
    +        protected UncorrectedTotalPointsViewProvider(String raceColumnName) {
                 this.raceColumnName = raceColumnName;
             }
     
    @@ -382,59 +382,59 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
             public String getValue(LeaderboardRowDTO object) {
                 LeaderboardEntryDTO leaderboardEntryDTO = object.fieldsByRaceColumnName.get(raceColumnName);
                 String result = "";
    -            if (leaderboardEntryDTO != null && leaderboardEntryDTO.netPointsUncorrected != null) {
    -                result="("+scoreFormat.format(leaderboardEntryDTO.netPointsUncorrected)+")";
    +            if (leaderboardEntryDTO != null && leaderboardEntryDTO.totalPointsUncorrected != null) {
    +                result="("+scoreFormat.format(leaderboardEntryDTO.totalPointsUncorrected)+")";
                 }
                 return result;
             }
         }
     
    -    private class NetPointsEditCellProvider extends AbstractRowUpdateWhiteboardProducerThatHasCell {
    -        private final EditTextCell netPointsEditCell;
    +    private class TotalPointsEditCellProvider extends AbstractRowUpdateWhiteboardProducerThatHasCell {
    +        private final EditTextCell totalPointsEditCell;
             private final String raceColumnName;
     
    -        protected NetPointsEditCellProvider(String raceColumnName) {
    +        protected TotalPointsEditCellProvider(String raceColumnName) {
                 this.raceColumnName = raceColumnName;
    -            netPointsEditCell = new EditTextCell(new SafeHtmlRenderer() {
    +            totalPointsEditCell = new EditTextCell(new SafeHtmlRenderer() {
                     @Override
    -                public void render(String netPointsAsString, SafeHtmlBuilder html) {
    -                    if (netPointsAsString != null) {
    -                        final boolean netPointsCorrected = isNetPointsCorrected();
    -                        if (netPointsCorrected) {
    +                public void render(String totalPointsAsString, SafeHtmlBuilder html) {
    +                    if (totalPointsAsString != null) {
    +                        final boolean totalPointsCorrected = isTotalPointsCorrected();
    +                        if (totalPointsCorrected) {
                                 html.appendHtmlConstant("");
                             }
    -                        html.appendEscaped(netPointsAsString);
    -                        if (netPointsCorrected) {
    +                        html.appendEscaped(totalPointsAsString);
    +                        if (totalPointsCorrected) {
                                 html.appendHtmlConstant("");
                             }
                         }
                     }
     
                     @Override
    -                public SafeHtml render(String netPointsAsString) {
    -                    final boolean netPointsCorrected = isNetPointsCorrected();
    +                public SafeHtml render(String totalPointsAsString) {
    +                    final boolean totalPointsCorrected = isTotalPointsCorrected();
                         String prefix;
                         String suffix;
    -                    if (netPointsCorrected) {
    +                    if (totalPointsCorrected) {
                             prefix = "";
                             suffix = "";
                         } else {
                             prefix = "";
                             suffix = "";
                         }
    -                    return SafeHtmlUtils.fromSafeConstant(prefix+SafeHtmlUtils.fromString(netPointsAsString).asString()+suffix);
    +                    return SafeHtmlUtils.fromSafeConstant(prefix+SafeHtmlUtils.fromString(totalPointsAsString).asString()+suffix);
                     }
                 });
             }
     
    -        private boolean isNetPointsCorrected() {
    +        private boolean isTotalPointsCorrected() {
                 LeaderboardEntryDTO leaderboardEntryDTO = getCurrentlyRendering().fieldsByRaceColumnName.get(raceColumnName);
    -            return leaderboardEntryDTO != null && leaderboardEntryDTO.netPointsCorrected;
    +            return leaderboardEntryDTO != null && leaderboardEntryDTO.totalPointsCorrected;
             }
     
             @Override
             public EditTextCell getCell() {
    -            return netPointsEditCell;
    +            return totalPointsEditCell;
             }
     
             @Override
    @@ -460,13 +460,13 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                             }
     
                             @Override
    -                        public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) {
    +                        public void onSuccess(Util.Triple newTotalAndTotalPointsAndIsCorrected) {
                                 setBusyState(false);
                                 final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
    -                            leaderboardEntryDTO.netPoints = value == null || value.length() == 0 ? newNetAndTotalPointsAndIsCorrected
    +                            leaderboardEntryDTO.totalPoints = value == null || value.length() == 0 ? newTotalAndTotalPointsAndIsCorrected
                                         .getA() : Double.valueOf(value.trim());
    -                            leaderboardEntryDTO.totalPoints = newNetAndTotalPointsAndIsCorrected.getB();
    -                            leaderboardEntryDTO.netPointsCorrected = newNetAndTotalPointsAndIsCorrected.getC();
    +                            leaderboardEntryDTO.netPoints = newTotalAndTotalPointsAndIsCorrected.getB();
    +                            leaderboardEntryDTO.totalPointsCorrected = newTotalAndTotalPointsAndIsCorrected.getC();
                                 getCell().setViewData(row, null); // ensure that getValue() is called again
                                 whiteboard.setObjectWithWhichToUpdateRow(row);
                             }
    @@ -479,31 +479,31 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
             public String getValue(LeaderboardRowDTO object) {
                 LeaderboardEntryDTO leaderboardEntryDTO = object.fieldsByRaceColumnName.get(raceColumnName);
                 String result = "n/a";
    -            if (leaderboardEntryDTO != null && leaderboardEntryDTO.netPoints != null) {
    -                result = scoreFormat.format(leaderboardEntryDTO.netPoints);
    +            if (leaderboardEntryDTO != null && leaderboardEntryDTO.totalPoints != null) {
    +                result = scoreFormat.format(leaderboardEntryDTO.totalPoints);
                 }
                 return result;
             }
         }
         
    -    private class MaxPointsReasonAndNetPointsEditButtonCell extends AbstractRowUpdateWhiteboardProducerThatHasCell {
    +    private class MaxPointsReasonAndTotalPointsEditButtonCell extends AbstractRowUpdateWhiteboardProducerThatHasCell {
             private final ButtonCell cell = new ButtonCell();
             private final String raceColumnName;
             private final StringMessages stringMessages;
             private final MaxPointsDropDownCellProvider maxPointsDropDownCellProvider;
    -        private final NetPointsEditCellProvider netPointsEditCellProvider;
    +        private final TotalPointsEditCellProvider totalPointsEditCellProvider;
             
             /**
              * @param maxPointsDropDownCellProvider will have its cell's view data reset after a successful update to force call to getValue
    -         * @param netPointsEditCellProvider will have its cell's view data reset after a successful update to force call to getValue
    +         * @param totalPointsEditCellProvider will have its cell's view data reset after a successful update to force call to getValue
              */
    -        public MaxPointsReasonAndNetPointsEditButtonCell(StringMessages stringMessages, String raceColumnName,
    +        public MaxPointsReasonAndTotalPointsEditButtonCell(StringMessages stringMessages, String raceColumnName,
                     MaxPointsDropDownCellProvider maxPointsDropDownCellProvider,
    -                NetPointsEditCellProvider netPointsEditCellProvider) {
    +                TotalPointsEditCellProvider totalPointsEditCellProvider) {
                 this.stringMessages = stringMessages;
                 this.raceColumnName = raceColumnName;
                 this.maxPointsDropDownCellProvider = maxPointsDropDownCellProvider;
    -            this.netPointsEditCellProvider = netPointsEditCellProvider;
    +            this.totalPointsEditCellProvider = totalPointsEditCellProvider;
             }
     
             @Override
    @@ -521,7 +521,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                         getWhiteboardOwner().whiteboardProduced(whiteboard);
                         new EditScoreDialog(stringMessages, row.competitor.getName(), raceColumnName,
                                 row.fieldsByRaceColumnName.get(raceColumnName).reasonForMaxPoints,
    -                            row.fieldsByRaceColumnName.get(raceColumnName).netPoints, new DialogCallback>() {
    +                            row.fieldsByRaceColumnName.get(raceColumnName).totalPoints, new DialogCallback>() {
                             @Override
                             public void ok(final Util.Pair editedObject) {
                                 setBusyState(true);
    @@ -538,7 +538,7 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                                     }
     
                                     @Override
    -                                public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) {
    +                                public void onSuccess(Util.Triple newTotalAndTotalPointsAndIsCorrected) {
                                         getSailingService().updateLeaderboardMaxPointsReason(getLeaderboardName(), row.competitor.getIdAsString(), raceColumnName,
                                                 editedObject.getA(), getLeaderboardDisplayDate(),
                                                         new AsyncCallback>() {
    @@ -552,15 +552,15 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                                             }
     
                                             @Override
    -                                        public void onSuccess(Util.Triple newNetAndTotalPointsAndIsCorrected) {
    +                                        public void onSuccess(Util.Triple newTotalAndNetPointsAndIsCorrected) {
                                                 setBusyState(false);
                                                 final LeaderboardEntryDTO leaderboardEntryDTO = row.fieldsByRaceColumnName.get(raceColumnName);
                                                 leaderboardEntryDTO.reasonForMaxPoints = editedObject.getA();
    -                                            leaderboardEntryDTO.netPoints = newNetAndTotalPointsAndIsCorrected.getA();
    -                                            leaderboardEntryDTO.totalPoints = newNetAndTotalPointsAndIsCorrected.getB();
    -                                            leaderboardEntryDTO.netPointsCorrected = newNetAndTotalPointsAndIsCorrected.getC();
    +                                            leaderboardEntryDTO.totalPoints = newTotalAndNetPointsAndIsCorrected.getA();
    +                                            leaderboardEntryDTO.netPoints = newTotalAndNetPointsAndIsCorrected.getB();
    +                                            leaderboardEntryDTO.totalPointsCorrected = newTotalAndNetPointsAndIsCorrected.getC();
                                                 maxPointsDropDownCellProvider.getCell().setViewData(row, null);
    -                                            netPointsEditCellProvider.getCell().setViewData(row, null);
    +                                            totalPointsEditCellProvider.getCell().setViewData(row, null);
                                                 whiteboard.setObjectWithWhichToUpdateRow(row);
                                             }
                                         });
    @@ -652,14 +652,14 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
             final Button setScoreCorrectionDefaultTimeBtn = new Button(stringMessages.setTimeToNow());
             setScoreCorrectionDefaultTimeBtn.addStyleName("inlineButton");
             scoreCorrectionInfoGrid.setWidget(0, 2, setScoreCorrectionDefaultTimeBtn);
    -        showUncorrectedNetPointsCheckbox = new CheckBox(stringMessages.showUncorrectedNetPoints());
    -        showUncorrectedNetPointsCheckbox.addValueChangeHandler(new ValueChangeHandler() {
    +        showUncorrectedTotalPointsCheckbox = new CheckBox(stringMessages.showUncorrectedTotalPoints());
    +        showUncorrectedTotalPointsCheckbox.addValueChangeHandler(new ValueChangeHandler() {
                 @Override
                 public void onValueChange(ValueChangeEvent event) {
                     timeChanged(getTimer().getTime(), getTimer().getTime());
                 }
             });
    -        scoreCorrectionInfoGrid.setWidget(0, 3, showUncorrectedNetPointsCheckbox);
    +        scoreCorrectionInfoGrid.setWidget(0, 3, showUncorrectedTotalPointsCheckbox);
     
             setScoreCorrectionDefaultTimeBtn.addClickHandler(new ClickHandler() {
                 @Override
    @@ -994,12 +994,12 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
                     new ArrayList>();
             final MaxPointsDropDownCellProvider maxPointsDropDownCellProvider = new MaxPointsDropDownCellProvider(race.getRaceColumnName());
             list.add(maxPointsDropDownCellProvider);
    -        final NetPointsEditCellProvider netPointsEditCellProvider = new NetPointsEditCellProvider(race.getRaceColumnName());
    -        list.add(netPointsEditCellProvider);
    -        final UncorrectedNetPointsViewProvider uncorrectedViewProvider = new UncorrectedNetPointsViewProvider(race.getRaceColumnName());
    +        final TotalPointsEditCellProvider totalPointsEditCellProvider = new TotalPointsEditCellProvider(race.getRaceColumnName());
    +        list.add(totalPointsEditCellProvider);
    +        final UncorrectedTotalPointsViewProvider uncorrectedViewProvider = new UncorrectedTotalPointsViewProvider(race.getRaceColumnName());
             list.add(uncorrectedViewProvider);
    -        list.add(new MaxPointsReasonAndNetPointsEditButtonCell(getStringMessages(), race.getRaceColumnName(),
    -                maxPointsDropDownCellProvider, netPointsEditCellProvider));
    +        list.add(new MaxPointsReasonAndTotalPointsEditButtonCell(getStringMessages(), race.getRaceColumnName(),
    +                maxPointsDropDownCellProvider, totalPointsEditCellProvider));
             return list;
         }
         
    @@ -1007,8 +1007,8 @@ public class EditableLeaderboardPanel extends LeaderboardPanel {
          * Computing the uncorrected scores can be expensive for large leaderboards; disable by default but allow user to enable
          */
         @Override
    -    protected boolean isFillNetPointsUncorrected() {
    -        return showUncorrectedNetPointsCheckbox==null?false:showUncorrectedNetPointsCheckbox.getValue();
    +    protected boolean isFillTotalPointsUncorrected() {
    +        return showUncorrectedTotalPointsCheckbox==null?false:showUncorrectedTotalPointsCheckbox.getValue();
         }
     
         @Override
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java
    index 066d0212239..1fa05e1b726 100755
    --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java
    +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java
    @@ -259,9 +259,9 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog");
                         }
    -                    if (officialNetPoints == null) {
    -                        sb.appendEscaped("null");
    -                    } else {
    -                        sb.append(officialNetPoints);
    -                    }
    -                    sb.appendEscaped("/");
                         if (officialTotalPoints == null) {
                             sb.appendEscaped("null");
                         } else {
                             sb.append(officialTotalPoints);
                         }
                         sb.appendEscaped("/");
    +                    if (officialTotalPointsWithFactorApplied == null) {
    +                        sb.appendEscaped("null");
    +                    } else {
    +                        sb.append(officialTotalPointsWithFactorApplied);
    +                    }
    +                    sb.appendEscaped("/");
                         if (officialMaxPointsReason != null) {
                             sb.appendEscaped(officialMaxPointsReason.name());
                         } else {
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java
    index efb15971469..ea9f4f37866 100755
    --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java
    +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/ResultSelectionAndApplyDialog.java
    @@ -45,7 +45,7 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog scoreCorrectionProviderNames, 
                 SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter) {
    -        super(stringMessages.importOfficialResults(), null, stringMessages.ok(), stringMessages.cancel(), new Validator(),
    +        super(stringMessages.importOfficialResults(), null, stringMessages.ok(), stringMessages.cancel(), new Validator(stringMessages),
                     new Callback(sailingService, leaderboardPanel, errorReporter, stringMessages));
             this.sailingService = sailingService;
             this.stringMessages = stringMessages;
    @@ -180,11 +180,17 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog>> {
    +        private final StringMessages stringMessages;
    +
    +        public Validator(StringMessages stringMessages) {
    +            this.stringMessages = stringMessages;
    +        }
    +
             @Override
             public String getErrorMessage(Util.Triple> valueToValidate) {
                 String errorMessage = null;
    -            if(valueToValidate == null) {
    -                errorMessage = "";
    +            if (valueToValidate == null) {
    +                errorMessage = stringMessages.pleaseSelectAScoringResult();
                 }
                 return errorMessage;
             }
    @@ -257,7 +263,7 @@ public class ResultSelectionAndApplyDialog extends DataEntryDialog 0) {
                  String selectedProviderName = scoreCorrectionProviderListBox.getItemText(selectedProviderIndex);
                  int selectedScoreCorrectionIndex = scoreCorrectionListBox.getSelectedIndex();
    -             if(selectedScoreCorrectionIndex > 0) {
    +             if (selectedScoreCorrectionIndex > 0) {
                      Util.Pair> pair = scoreCorrections.get(scoreCorrectionListBox.getValue(selectedScoreCorrectionIndex));
                      result = new Util.Triple>(selectedProviderName, pair.getA(), pair.getB());
                  }
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java
    index 64ea6a42a76..ec91acc62f1 100644
    --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java
    +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/masterdataimport/MasterDataImportPanel.java
    @@ -316,7 +316,7 @@ public class MasterDataImportPanel extends VerticalPanel {
         }
     
         private void addContentToLeftPanel(VerticalPanel contentPanel) {
    -        contentPanel.add(new Label(stringMessages.importLeaderboardGroups()));
    +        contentPanel.add(new Label(stringMessages.availableLeaderboardGroups()));
             
             HorizontalPanel filterPanel = new HorizontalPanel();
             filterPanel.add(new Label(stringMessages.filterName() + ":"));
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
    index 4e93cbf257a..8106e832b61 100644
    --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
    +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java
    @@ -703,7 +703,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
         @Override
         public IncrementalOrFullLeaderboardDTO getLeaderboardByName(final String leaderboardName, final Date date,
                 final Collection namesOfRaceColumnsForWhichToLoadLegDetails, boolean addOverallDetails,
    -            String previousLeaderboardId, boolean fillNetPointsUncorrected) throws NoWindException, InterruptedException, ExecutionException,
    +            String previousLeaderboardId, boolean fillTotalPointsUncorrected) throws NoWindException, InterruptedException, ExecutionException,
                 IllegalArgumentException {
             try {
                 long startOfRequestHandling = System.currentTimeMillis();
    @@ -717,7 +717,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
                         timePoint = new MillisecondsTimePoint(date);
                     }
                     LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO(timePoint,
    -                        namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory, fillNetPointsUncorrected);
    +                        namesOfRaceColumnsForWhichToLoadLegDetails, addOverallDetails, getService(), baseDomainFactory, fillTotalPointsUncorrected);
                     LeaderboardDTO previousLeaderboardDTO = null;
                     synchronized (leaderboardByNameResultsCacheById) {
                         leaderboardByNameResultsCacheById.put(leaderboardDTO.getId(), leaderboardDTO);
    @@ -3053,8 +3053,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
                 TimePoint effectiveTimePoint = timePoint == null ? leaderboard.getNowMinusDelay() : timePoint;
                 if (detailType != null) {
                     switch (detailType) {
    -                case REGATTA_TOTAL_POINTS_SUM:
    -                    for (Entry> e : leaderboard.getTotalPointsSumAfterRaceColumn(effectiveTimePoint).entrySet()) {
    +                case REGATTA_NET_POINTS_SUM:
    +                    for (Entry> e : leaderboard.getNetPointsSumAfterRaceColumn(effectiveTimePoint).entrySet()) {
                             List competitorDTOs = new ArrayList<>();
                             List pointSums = new ArrayList<>();
                             for (Entry e2 : e.getValue().entrySet()) {
    @@ -4933,7 +4933,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
         @Override
         public void setTrackingTimes(RaceLogSetTrackingTimesDTO dto) throws NotFoundException {
             RaceLog raceLog = getRaceLog(dto.leaderboardName, dto.raceColumnName, dto.fleetName);
    -        // TODO If new is null and current is not, current should be revoked.
    +        // the tracking start/end time events are not revoked; updates with null as TimePoint may be added instead
             if (!Util.equalsWithNull(dto.newStartOfTracking, dto.currentStartOfTracking)) {
                 raceLog.add(new RaceLogStartOfTrackingEventImpl(
                         dto.newStartOfTracking, new LogEventAuthorImpl(dto.authorName, dto.authorPriority),
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html
    index 1519e553966..a5350cf3384 100644
    --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html
    +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html
    @@ -4,6 +4,19 @@
     

    What's New - SAP Sailing Race Manager

    +
    May 2016
    + +
      +
    • A bug related to entering the passing instructions for a waypoint in the "by-marks" + course designer has been fixed. At the same time, for now the passing instructions + "Offset" and "Fixed Bearing" are no longer offered until the application really offers + support for them, particularly during mark passing calculation.
    • +
    • Swiping the start time button to the left to reach the other buttons for entering + the racing procedure, start mode flag, etc. now works reliably.
    • +
    • Regatta-specific preferences now properly take precedence over the device configuration. + They are updated from the server when the regatta content is refreshed.
    • +
    +
    April 2016
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index 48dfa3eafcc..23a869cebd0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -2,10 +2,24 @@
      -

      What's New - SAP Sailing Analytics

      -
      -
      February 2016
      +

      What's New - SAP Sailing Analytics

      +
      +
      May 2016
      +
        +
      • It is now possible to follow races on the RaceBoard, even if there's no wind data available.
      • +
      +
      April 2016
      +
        +
      • The /gwt/RaceBoard.html entry point now accepts an optional parameter "t" in the form + [[hh:]mm:]ss, for example t=12:34 to place the timer so much after the race start + if the viewer comes up in Replay mode. You may also use just seconds, as in t=1234 which + makes specifying a time before the start somewhat easier (t=-123 instead of + t=-02:-03). +
      • +
      + +
      February 2016
      • The website now features a user registration and authentication interface. User accounts are currently used to grant special permissions to administrators and moderators. @@ -21,29 +35,19 @@ the same CSS animation for the marks that is already used for the boats.
      - -
      April 2016
      -
        -
      • The /gwt/RaceBoard.html entry point now accepts an optional parameter "t" in the form - [[hh:]mm:]ss, for example t=12:34 to place the timer so much after the race start - if the viewer comes up in Replay mode. You may also use just seconds, as in t=1234 which - makes specifying a time before the start somewhat easier (t=-123 instead of - t=-02:-03). -
      • -
      - -
      January 2016
      -
        -
      • Show maneuver markers on the race board by default - (tacks, jibes and penalty circles) with a smaller icon, which - shows an info bubble when the marker is clicked. - The icon turns green if the direction change is to starboard - and red if to port. -
      • -
      + +
      January 2016
      +
        +
      • Show maneuver markers on the race board by default + (tacks, jibes and penalty circles) with a smaller icon, which + shows an info bubble when the marker is clicked. + The icon turns green if the direction change is to starboard + and red if to port. +
      • +
      December 2015
      -
        +
        • The advantage line is now animated and moves smoothly with the boats.
        • New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent, diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/M32.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/M32.png new file mode 100644 index 00000000000..761df22fdf3 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/M32.png differ diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java index 54ad4d5c3c1..bfa1136c2a9 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboardGroups.java @@ -161,10 +161,10 @@ public class TestStoringAndRetrievingLeaderboardGroups extends AbstractMongoDBTe Leaderboard loadedOverallLeaderboard = loadedLeaderboardGroup.getOverallLeaderboard(); final RaceColumn leaderboard3Column = loadedOverallLeaderboard.getRaceColumnByName("Leaderboard 3"); assertTrue(loadedOverallLeaderboard.getScoreCorrection().hasCorrectionFor(leaderboard3Column)); - final Double totalPoints = loadedOverallLeaderboard.getTotalPoints(wolfgang, leaderboard3Column, + final Double netPoints = loadedOverallLeaderboard.getNetPoints(wolfgang, leaderboard3Column, MillisecondsTimePoint.now()); - assertNotNull(totalPoints); - assertEquals(99.9, totalPoints, 0.00000000001); + assertNotNull(netPoints); + assertEquals(99.9, netPoints, 0.00000000001); } @Test diff --git a/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_en.properties b/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties similarity index 100% rename from java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_en.properties rename to java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages.properties diff --git a/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_de.properties b/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_de.properties index c2ba45e114d..fcfc7c08a85 100644 --- a/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_de.properties +++ b/java/com.sap.sailing.polars.datamining/resources/stringmessages/Polars_StringMessages_de.properties @@ -1,5 +1,5 @@ PolarChain1=Polare -PolarChain1=Backend-Polare +PolarChain2=Backend-Polare RaceColumn=Rennen LegIndex=Schenkel Index PolarData=Polardaten diff --git a/java/com.sap.sailing.polars.datamining/resources/stringmessages/XLF/Polars_StringMessages.properties.xlf b/java/com.sap.sailing.polars.datamining/resources/stringmessages/XLF/Polars_StringMessages.properties.xlf new file mode 100644 index 00000000000..f7941ae30ce --- /dev/null +++ b/java/com.sap.sailing.polars.datamining/resources/stringmessages/XLF/Polars_StringMessages.properties.xlf @@ -0,0 +1,49 @@ + + + +
          + + H4sIAAAAAAAAAF2PTQrCQAyF93OKXEH32TiiFBSGunAdOrEObTPQaaHHd/6s4Cbv4yW8JMaPNOs3OTmgSRyU2a0jnqgbWCzUVksdaz+uk+AP1Y37RixvGAEylYwzLVRCIWExw3fN1TwubsMoEFU9ndiWpGdMBBmz2QTt5eUsy4I7QR66M4V15ika8TQZMBVVb67b/j74ALSew+fwAAAA + + + + + +
          + + + Polars + + + Backend Polars + + + RaceColumn + + + Leg Index + + + Polar Data + + + Polars + + + GPS Fix + + + Wind Range + + + Confident Wind Measurement + + + Rank + + + Backend Polars + + +
          +
          \ No newline at end of file diff --git a/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/CompetitorRow.java b/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/CompetitorRow.java index db4cf05447d..bb5bcded235 100755 --- a/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/CompetitorRow.java +++ b/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/CompetitorRow.java @@ -4,7 +4,7 @@ package com.sap.sailing.resultimport; public interface CompetitorRow { Iterable getRankAndMaxPointsReasonAndPointsAndDiscarded(); - Double getNetPointsBeforeDiscarding(); + Double getTotalPointsBeforeDiscarding(); Double getScoreAfterDiscarding(); diff --git a/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/impl/CompetitorRowImpl.java b/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/impl/CompetitorRowImpl.java index 54d904eeb12..8a702d9df4e 100755 --- a/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/impl/CompetitorRowImpl.java +++ b/java/com.sap.sailing.resultimport/src/com/sap/sailing/resultimport/impl/CompetitorRowImpl.java @@ -8,17 +8,17 @@ public class CompetitorRowImpl implements CompetitorRow { private final String sailID; private final Iterable names; private final Double scoreAfterDiscarding; - private final Double netPointsBeforeDiscarding; + private final Double totalPointsBeforeDiscarding; private final Iterable rankAndMaxPointsReasonAndPointsAndDiscarded; public CompetitorRowImpl(Integer totalRank, String sailID, Iterable names, Double scoreAfterDiscarding, - Double netPointsBeforeDiscarding, Iterable rankAndMaxPointsReasonAndPointsAndDiscarded) { + Double totalPointsBeforeDiscarding, Iterable rankAndMaxPointsReasonAndPointsAndDiscarded) { super(); this.totalRank = totalRank; this.sailID = sailID; this.names = names; this.scoreAfterDiscarding = scoreAfterDiscarding; - this.netPointsBeforeDiscarding = netPointsBeforeDiscarding; + this.totalPointsBeforeDiscarding = totalPointsBeforeDiscarding; this.rankAndMaxPointsReasonAndPointsAndDiscarded = rankAndMaxPointsReasonAndPointsAndDiscarded; } @@ -43,8 +43,8 @@ public class CompetitorRowImpl implements CompetitorRow { } @Override - public Double getNetPointsBeforeDiscarding() { - return netPointsBeforeDiscarding; + public Double getTotalPointsBeforeDiscarding() { + return totalPointsBeforeDiscarding; } @Override diff --git a/java/com.sap.sailing.sailwave.resultimport.test/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.sailwave.resultimport.test/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..a99e012660b --- /dev/null +++ b/java/com.sap.sailing.sailwave.resultimport.test/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//src/com/sap/sailing/sailwave/resultimport/test/ParserTest.java=UTF-8 diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/GateStartConfigurationJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/GateStartConfigurationJsonDeserializer.java index f78514329cf..f603921fb6b 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/GateStartConfigurationJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/GateStartConfigurationJsonDeserializer.java @@ -34,7 +34,7 @@ public class GateStartConfigurationJsonDeserializer extends RacingProcedureConfi result.setHasIndividualRecall(inidividualRecall); result.setResultEntryEnabled(resultEntryEnabled); if (object.containsKey(GateStartConfigurationJsonSerializer.FIELD_HAS_PATHFINDER)) { - result.setHasIndividualRecall((Boolean)object.get(GateStartConfigurationJsonSerializer.FIELD_HAS_PATHFINDER)); + result.setHasPathfinder((Boolean)object.get(GateStartConfigurationJsonSerializer.FIELD_HAS_PATHFINDER)); } if (object.containsKey(GateStartConfigurationJsonSerializer.FIELD_HAS_ADDITIONAL_GOLF_DOWN_TIME)) { result.setHasAdditionalGolfDownTime((Boolean)object.get(GateStartConfigurationJsonSerializer.FIELD_HAS_ADDITIONAL_GOLF_DOWN_TIME)); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index 1e04efb451c..8c334f270d8 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -154,7 +154,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { LeaderboardDTO leaderboardDTO = leaderboard.getLeaderboardDTO( timePointAndResultStateAndMaxCompetitorsCount.getA(), Collections. emptyList(), /* addOverallDetails */ false, getService(), getService().getBaseDomainFactory(), - /* fillNetPointsUncorrected */false); + /* fillTotalPointsUncorrected */false); TimePoint resultTimePoint = timePointAndResultStateAndMaxCompetitorsCount.getA(); ResultStates resultState = timePointAndResultStateAndMaxCompetitorsCount.getB(); @@ -183,7 +183,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { jsonCompetitor.put("rank", counter); jsonCompetitor.put("carriedPoints", leaderboardRowDTO.carriedPoints); - jsonCompetitor.put("totalPoints", leaderboardRowDTO.totalPoints); + jsonCompetitor.put("netPoints", leaderboardRowDTO.netPoints); jsonCompetitorEntries.add(jsonCompetitor); JSONObject jsonRaceColumns = new JSONObject(); jsonCompetitor.put("raceScores", jsonRaceColumns); @@ -197,9 +197,9 @@ public class LeaderboardsResource extends AbstractSailingServerResource { final FleetDTO fleetOfCompetitor = leaderboardEntry.fleet; jsonEntry.put("fleet", fleetOfCompetitor == null ? "" : fleetOfCompetitor.getName()); - jsonEntry.put("netPoints", leaderboardEntry.netPointsCorrected); - jsonEntry.put("uncorrectedNetPoints", leaderboardEntry.netPoints); - jsonEntry.put("totalPoints", leaderboardEntry.totalPoints); + jsonEntry.put("totalPoints", leaderboardEntry.totalPointsCorrected); + jsonEntry.put("uncorrectedTotalPoints", leaderboardEntry.totalPoints); + jsonEntry.put("netPoints", leaderboardEntry.netPoints); MaxPointsReason maxPointsReason = leaderboardEntry.reasonForMaxPoints; jsonEntry.put("maxPointsReason", maxPointsReason != null ? maxPointsReason.toString() : null); jsonEntry.put("rank", regattaRankedCompetitorsForColumn.indexOf(competitor) + 1); @@ -236,7 +236,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { jsonCompetitor.put("rank", 0); jsonCompetitor.put("carriedPoints", null); - jsonCompetitor.put("totalPoints", null); + jsonCompetitor.put("netPoints", null); jsonCompetitorEntries.add(jsonCompetitor); JSONObject jsonRaceColumns = new JSONObject(); jsonCompetitor.put("raceScores", jsonRaceColumns); @@ -245,8 +245,8 @@ public class LeaderboardsResource extends AbstractSailingServerResource { jsonRaceColumns.put(raceColumn.getName(), jsonEntry); final Fleet fleetOfCompetitor = raceColumn.getFleetOfCompetitor(competitor); jsonEntry.put("fleet", fleetOfCompetitor == null ? "" : fleetOfCompetitor.getName()); - jsonEntry.put("netPoints", null); jsonEntry.put("totalPoints", null); + jsonEntry.put("netPoints", null); jsonEntry.put("maxPointsReason", ""); jsonEntry.put("rank", 0); jsonEntry.put("isDiscarded", false); @@ -380,6 +380,7 @@ public class LeaderboardsResource extends AbstractSailingServerResource { } final TimePoint now = MillisecondsTimePoint.now(); String competitorId = (String) requestObject.get(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING); + // TODO alternatively, check for a markId here and then produce a RegattaLogDeviceMarkMappingEventImpl String deviceUuid = (String) requestObject.get(DeviceMappingConstants.JSON_DEVICE_UUID); Long fromMillis = (Long) requestObject.get(DeviceMappingConstants.JSON_FROM_MILLIS); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java index 88765bd6284..ab0fcc60042 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java @@ -183,6 +183,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest { assertEquals(masterTrackedRace.getEndOfTracking(), replicaTrackedRace.getEndOfTracking()); TimePoint now = MillisecondsTimePoint.now(); assertFalse(now.equals(replicaTrackedRace.getStartOfRace())); + Thread.sleep(1000); ((DynamicTrackedRace) masterTrackedRace).setStartTimeReceived(now); Thread.sleep(1000); assertEquals(now, replicaTrackedRace.getStartOfRace()); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java index ef295836baa..a26a4200d04 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/LeaderboardDiscardingRulesTest.java @@ -86,10 +86,10 @@ public class LeaderboardDiscardingRulesTest { TimePoint now = MillisecondsTimePoint.now(); // Hunger scores better than Plattner in this case because Hunger's two discards are 123/124, keeping 122/122 // whereas Plattner is to discard two of the four 123 results, keeping 123/123, being two points worse than Hunger: - double totalPointsHasso = leaderboard.getTotalPoints(hasso, now); - double totalPointsWolfgang = leaderboard.getTotalPoints(wolfgang, now); - assertEquals(122.+122., totalPointsWolfgang, 0.0000000001); - assertEquals(123.+123., totalPointsHasso, 0.000000001); + double netPointsHasso = leaderboard.getNetPoints(hasso, now); + double netPointsWolfgang = leaderboard.getNetPoints(wolfgang, now); + assertEquals(122.+122., netPointsWolfgang, 0.0000000001); + assertEquals(123.+123., netPointsHasso, 0.000000001); } /** @@ -129,11 +129,11 @@ public class LeaderboardDiscardingRulesTest { TimePoint now = MillisecondsTimePoint.now(); // Jes scores better than Rasmus in this case because Jes's two discards are 1.0/2.0, keeping 3.0/3.0 // whereas Rasmus is to discard two of the four 2.0 results, keeping 2.0/2.0, being two points worse than Hunger: - double totalPointsRasmus = leaderboard.getTotalPoints(rasmus, now); - double totalPointsJes = leaderboard.getTotalPoints(jes, now); + double netPointsRasmus = leaderboard.getNetPoints(rasmus, now); + double netPointsJes = leaderboard.getNetPoints(jes, now); assertEquals(3, Util.size(leaderboard.getCompetitors())); - assertEquals(3.+3., totalPointsJes, 0.0000000001); - assertEquals(2.+2., totalPointsRasmus, 0.000000001); + assertEquals(3.+3., netPointsJes, 0.0000000001); + assertEquals(2.+2., netPointsRasmus, 0.000000001); } /** @@ -171,9 +171,9 @@ public class LeaderboardDiscardingRulesTest { TimePoint now = MillisecondsTimePoint.now(); // Hunger scores better than Plattner in this case because Hunger's two discards are 123/124, keeping 122/122 // whereas Plattner is to discard two of the four 123 results, keeping 123/123, being two points worse than Hunger: - double totalPointsHasso = leaderboard.getTotalPoints(hasso, now); - double totalPointsWolfgang = leaderboard.getTotalPoints(wolfgang, now); - assertEquals(122.+122., totalPointsWolfgang, 0.0000000001); - assertEquals(123.+123., totalPointsHasso, 0.000000001); + double netPointsHasso = leaderboard.getNetPoints(hasso, now); + double netPointsWolfgang = leaderboard.getNetPoints(wolfgang, now); + assertEquals(122.+122., netPointsWolfgang, 0.0000000001); + assertEquals(123.+123., netPointsHasso, 0.000000001); } } diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 52de168aeb5..f14bce336b7 100755 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -12,6 +12,8 @@ + + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 92bca70c482..4e61b163b5d 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -592,7 +592,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes Executor simulatorExecutor = new ThreadPoolExecutor(/* corePoolSize */SIMULATION_THREAD_POOL_SIZE, /* maximumPoolSize */SIMULATION_THREAD_POOL_SIZE, /* keepAliveTime */60, TimeUnit.SECONDS, - /* workQueue */new LinkedBlockingQueue()); + /* workQueue */new LinkedBlockingQueue(), new ThreadFactoryWithPriority( + Thread.NORM_PRIORITY, /* daemon */true)); // TODO: initialize smart-future-cache for simulation-results and add to simulation-service simulationService = SimulationServiceFactory.INSTANCE.getService(simulatorExecutor, this); this.raceLogReplicator = new RaceLogReplicator(this); @@ -1648,6 +1649,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes // see raceTimesChanged(TimePoint, TimePoint, TimePoint). } + @Override + public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + // no action required; the update signaled by this call is implicit; the race log + // updates that led to this change are replicated separately + } + @Override public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { // no-op; the course change is replicated by the separate CourseChangeReplicator diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 2574f2831bd..bad9e471302 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -108,6 +108,11 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { return null; } + @Override + public TimePoint getFinishedTime() { + return null; + } + @Override public TimePoint getEndOfRace() { return null; @@ -633,7 +638,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { } @Override - public void updateStartAndEndOfTracking() { + public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { } @Override diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java index f4c26059041..c5d84f5697d 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/ImportMasterDataOperation.java @@ -291,18 +291,18 @@ public class ImportMasterDataOperation extends RaceLogIdentifier identifier = raceColumn.getRaceLogIdentifier(fleet); RaceLog currentPersistedLog = mongoRaceLogStore.getRaceLog(identifier, true); if (currentPersistedLog.isEmpty()) { - addAllmportedEvents(mongoObjectFactory, mongoRaceLogStore, log, identifier); + addAllImportedEvents(mongoObjectFactory, mongoRaceLogStore, log, identifier); } else if (override) { // Clear existing race log mongoRaceLogStore.removeRaceLog(identifier); - addAllmportedEvents(mongoObjectFactory, mongoRaceLogStore, log, identifier); + addAllImportedEvents(mongoObjectFactory, mongoRaceLogStore, log, identifier); } } } } } - private void addAllmportedEvents(MongoObjectFactory mongoObjectFactory, RaceLogStore mongoRaceLogStore, + private void addAllImportedEvents(MongoObjectFactory mongoObjectFactory, RaceLogStore mongoRaceLogStore, RaceLog log, RaceLogIdentifier identifier) { RaceLogEventVisitor storeVisitor = MongoRaceLogStoreFactory.INSTANCE .getMongoRaceLogStoreVisitor(identifier, mongoObjectFactory); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardMaxPointsReason.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardMaxPointsReason.java index ecb91f059ce..fbfb0196bf5 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardMaxPointsReason.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardMaxPointsReason.java @@ -51,7 +51,7 @@ public class UpdateLeaderboardMaxPointsReason extends AbstractLeaderboardColumnO updateStoredLeaderboard(toState, leaderboard); Entry updatedEntry = leaderboard.getEntry(competitor, raceColumn, timePoint); boolean isScoreCorrected = leaderboard.getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint); - return new Util.Triple(updatedEntry.getNetPoints(), updatedEntry.getTotalPoints(), isScoreCorrected); + return new Util.Triple(updatedEntry.getTotalPoints(), updatedEntry.getNetPoints(), isScoreCorrected); } else { throw new IllegalArgumentException("Didn't find competitor with ID "+competitorIdAsString+" in leaderboard "+getLeaderboardName()); } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardScoreCorrection.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardScoreCorrection.java index ac34e5c1123..effb79dc65e 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardScoreCorrection.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateLeaderboardScoreCorrection.java @@ -41,8 +41,8 @@ public class UpdateLeaderboardScoreCorrection extends AbstractLeaderboardColumnO @Override public Util.Triple internalApplyTo(RacingEventService toState) throws NoWindException { Leaderboard leaderboard = toState.getLeaderboardByName(getLeaderboardName()); - Double newNetPoints; Double newTotalPoints; + Double newNetPoints; boolean isScoreCorrected; if (leaderboard != null) { Competitor competitor = leaderboard.getCompetitorByIdAsString(competitorIdAsString); @@ -50,12 +50,12 @@ public class UpdateLeaderboardScoreCorrection extends AbstractLeaderboardColumnO RaceColumn raceColumn = leaderboard.getRaceColumnByName(getColumnName()); if (correctedScore == null) { leaderboard.getScoreCorrection().uncorrectScore(competitor, raceColumn); - newNetPoints = leaderboard.getNetPoints(competitor, raceColumn, timePoint); + newTotalPoints = leaderboard.getTotalPoints(competitor, raceColumn, timePoint); } else { leaderboard.getScoreCorrection().correctScore(competitor, raceColumn, correctedScore); - newNetPoints = correctedScore; + newTotalPoints = correctedScore; } - newTotalPoints = leaderboard.getEntry(competitor, raceColumn, timePoint).getTotalPoints(); + newNetPoints = leaderboard.getEntry(competitor, raceColumn, timePoint).getNetPoints(); isScoreCorrected = leaderboard.getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint); } else { throw new IllegalArgumentException("Didn't find competitor with ID "+competitorIdAsString+" in leaderboard "+getLeaderboardName()); @@ -64,7 +64,7 @@ public class UpdateLeaderboardScoreCorrection extends AbstractLeaderboardColumnO throw new IllegalArgumentException("Didn't find leaderboard "+getLeaderboardName()); } updateStoredLeaderboard(toState, leaderboard); - return new Util.Triple(newNetPoints, newTotalPoints, isScoreCorrected); + return new Util.Triple(newTotalPoints, newNetPoints, isScoreCorrected); } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java index 161dfbd418c..d9ff523efbb 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/simulation/SimulationServiceImpl.java @@ -176,6 +176,12 @@ public class SimulationServiceImpl implements SimulationService { // TODO: update leg 1 } + @Override + public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { + // relevant for simulation? for last leg? + // TODO: update last leg? + } + @Override public void windSourcesToExcludeChanged(Iterable windSourcesToExclude) { // relevant for simulation: update all legs when wind changes overall diff --git a/java/com.sap.sailing.velum.resultimport.test/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.velum.resultimport.test/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..bb332813b38 --- /dev/null +++ b/java/com.sap.sailing.velum.resultimport.test/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding//src/com/sap/sailing/velum/resultimport/test/ParserTest.java=UTF-8 diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index ec5c175b5e8..c7a7ced7467 100644 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -23,6 +23,19 @@

          Release Notes - Administration Console

          +

          May 2016

          +
            +
          • Smartphone tracking support has been improved. When starting to track a race that has no + start-of-tracking time set yet, the start-of-tracking time will be set to the time point + when tracking has been started. Similarly, when stopping tracking and no end-of-tracking + time is set for the race yet, the current time is set as the end-of-tracking time for + that race. +
          • +
          • Regatta structure import from Manage2Sail now takes the boat class name from the + regatta, not the division element, giving better results. +
          • +
          +

          April 2016

          • In the "Race Manager App" regatta configuration settings, a new preference diff --git a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java index a2a3b173b4f..73bae519d8e 100644 --- a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java +++ b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java @@ -243,7 +243,7 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(competitorRaceDataElement, "max_points_reason", maxPointsReason.toString()); boolean isDiscardedForCompetitor = leaderboard.isDiscarded(competitorInLeaderboard, column, timepointToBeUsed); addNamedElementWithValue(competitorRaceDataElement, "is_discarded", isDiscardedForCompetitor == true ? "true" : "false"); - Double finalRaceScore = leaderboard.getTotalPoints(competitorInLeaderboard, column, timepointToBeUsed); + Double finalRaceScore = leaderboard.getNetPoints(competitorInLeaderboard, column, timepointToBeUsed); addNamedElementWithValue(competitorRaceDataElement, "final_race_score", finalRaceScore); competitorElement.addContent(competitorRaceDataElement); raceElement.addContent(competitorElement); @@ -404,7 +404,7 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(competitorRaceDataElement, "max_points_reason", maxPointsReason.toString()); boolean isDiscardedForCompetitor = leaderboard.isDiscarded(competitorInLeaderboard, column, race.getEndOfRace()); addNamedElementWithValue(competitorRaceDataElement, "is_discarded", isDiscardedForCompetitor == true ? "true" : "false"); - Double finalRaceScore = leaderboard.getTotalPoints(competitorInLeaderboard, column, race.getEndOfRace()); + Double finalRaceScore = leaderboard.getNetPoints(competitorInLeaderboard, column, race.getEndOfRace()); addNamedElementWithValue(competitorRaceDataElement, "final_race_score", finalRaceScore); competitorElement.addContent(competitorRaceDataElement); raceElement.addContent(competitorElement); @@ -428,7 +428,7 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(competitorRaceDataElement, "max_points_reason", maxPointsReason.toString()); boolean isDiscardedForCompetitor = leaderboard.isDiscarded(competitor, column, race.getEndOfRace()); addNamedElementWithValue(competitorRaceDataElement, "is_discarded", isDiscardedForCompetitor == true ? "true" : "false"); - Double finalRaceScore = leaderboard.getTotalPoints(competitor, column, race.getEndOfRace()); + Double finalRaceScore = leaderboard.getNetPoints(competitor, column, race.getEndOfRace()); addNamedElementWithValue(competitorRaceDataElement, "final_race_score", finalRaceScore); if (maxPointsReason.equals(MaxPointsReason.DNS) || race.getMarkPassings(competitor).isEmpty()) { // we do not want to include competitors that did not start the race @@ -701,7 +701,7 @@ public class LeaderboardData extends ExportAction { Speed averageSpeedOverGroundIncludingNonCompletedRaces = getAverageSpeedOverGround(leaderboard, competitor, timePointOfLatestModification, true); addNamedElementWithValue(competitorElement, "average_speed_over_ground_including_non_finished_races_in_knots", averageSpeedOverGroundIncludingNonCompletedRaces == null ? 0 : averageSpeedOverGroundIncludingNonCompletedRaces.getKnots()); addNamedElementWithValue(competitorElement, "overall_rank", leaderboard.getTotalRankOfCompetitor(competitor, timePointOfLatestModification)); - addNamedElementWithValue(competitorElement, "overall_score", leaderboard.getTotalPoints(competitor, timePointOfLatestModification)); + addNamedElementWithValue(competitorElement, "overall_score", leaderboard.getNetPoints(competitor, timePointOfLatestModification)); competitorElement.addContent(createDataConfidenceXML(competitorConfidenceAndErrorMessages)); TimePoint elapsedTime = MillisecondsTimePoint.now().minus(timeSpent.asMillis()); addNamedElementWithValue(competitorElement, "generation_time_in_milliseconds", elapsedTime.asMillis()); diff --git a/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/ParserImpl.java b/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/ParserImpl.java index 643f7f3682a..e8cf151c43e 100755 --- a/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/ParserImpl.java +++ b/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/ParserImpl.java @@ -57,11 +57,7 @@ public class ParserImpl implements Parser { @Override public String getBoatClassName(Division division) { - String result = division.getIFClassID(); - if (result == null || result.isEmpty()) { - result = division.getTitle(); - } - return result; + return XRRParserUtil.getBoatClassName(division); } @Override diff --git a/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/XRRParserUtil.java b/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/XRRParserUtil.java index 8f523430116..3a950106c4c 100755 --- a/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/XRRParserUtil.java +++ b/java/com.sap.sailing.xrr.resultimport/src/com/sap/sailing/xrr/resultimport/impl/XRRParserUtil.java @@ -3,6 +3,7 @@ package com.sap.sailing.xrr.resultimport.impl; import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.XMLGregorianCalendar; +import com.sap.sailing.xrr.schema.Division; import com.sap.sailing.xrr.schema.RegattaResults; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -32,4 +33,11 @@ public class XRRParserUtil { return timePoint; } + public static String getBoatClassName(Division division) { + String result = division.getIFClassID(); + if (result == null || result.isEmpty()) { + result = division.getTitle(); + } + return result; + } } diff --git a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java index 486b9a5b82f..e796a45ee11 100755 --- a/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java +++ b/java/com.sap.sailing.xrr.structureimport/src/com/sap/sailing/xrr/structureimport/StructureImporter.java @@ -35,6 +35,7 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.xrr.resultimport.ParserFactory; +import com.sap.sailing.xrr.resultimport.impl.XRRParserUtil; import com.sap.sailing.xrr.schema.Boat; import com.sap.sailing.xrr.schema.Crew; import com.sap.sailing.xrr.schema.Division; @@ -52,6 +53,7 @@ import com.sap.sailing.xrr.structureimport.buildstructure.Series; import com.sap.sailing.xrr.structureimport.buildstructure.SetRacenumberStrategy; import com.sap.sse.common.Color; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.AbstractColor; import com.sapsailing.xrr.structureimport.eventimport.EventImport; import com.sapsailing.xrr.structureimport.eventimport.RegattaJSON; @@ -81,12 +83,12 @@ public class StructureImporter { } public Iterable getRegattas(Iterable regattas) { - Iterable parsedRegattas = parseRegattas(regattas); + Iterable> parsedRegattas = parseRegattas(regattas); Set addSpecificRegattas = new HashSet(); - for (RegattaResults result : parsedRegattas) { + for (Pair result : parsedRegattas) { List races = new ArrayList(); // assuming that the last element in getPersonOrBoatOrTeam is an event - Event event = (Event) result.getPersonOrBoatOrTeam().get(result.getPersonOrBoatOrTeam().size() - 1); + Event event = (Event) result.getB().getPersonOrBoatOrTeam().get(result.getB().getPersonOrBoatOrTeam().size() - 1); Iterable raceOrDivisionOrRegattaSeriesResults = event.getRaceOrDivisionOrRegattaSeriesResult(); for (Object raceOrDivisionOrRegattaSeriesResult : raceOrDivisionOrRegattaSeriesResults) { if (raceOrDivisionOrRegattaSeriesResult instanceof Race) { @@ -98,23 +100,22 @@ public class StructureImporter { final TimePoint endDate = null; // TODO can regatta end time be inferred from XRR document? RegattaImpl regatta = new RegattaImpl(RegattaImpl.getDefaultName(event.getTitle(), ((Division) event .getRaceOrDivisionOrRegattaSeriesResult().get(0)).getTitle()), - baseDomainFactory.getOrCreateBoatClass(((Division) event.getRaceOrDivisionOrRegattaSeriesResult() - .get(0)).getTitle()), startDate, endDate, getSeries(buildStructure), false, + baseDomainFactory.getOrCreateBoatClass(result.getA().getBoatClass()), startDate, endDate, getSeries(buildStructure), false, this.baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), event.getEventID(), null, OneDesignRankingMetric::new); addSpecificRegattas.add(regatta); } return addSpecificRegattas; } - private Iterable parseRegattas(final Iterable selectedRegattas) { - final Set result = Collections.synchronizedSet(new HashSet()); + private Iterable> parseRegattas(final Iterable selectedRegattas) { + final Set> result = Collections.synchronizedSet(new HashSet>()); Set threads = new HashSet(); for (final RegattaJSON selectedRegatta : selectedRegattas) { Thread thread = new Thread("XRR Importer " + selectedRegatta.getName()) { @Override public void run() { try { - result.add(parseRegattaXML(selectedRegatta.getXrrEntriesUrl())); + result.add(new Pair<>(selectedRegatta, parseRegattaXML(selectedRegatta.getXrrEntriesUrl()))); } catch (JAXBException | IOException e) { logger.info("Parse error during XRR import. Ignoring document " + selectedRegatta.getName()); } diff --git a/java/com.sap.sse.common.test/src/com/sap/sse/common/test/TestUrlEncoding.java b/java/com.sap.sse.common.test/src/com/sap/sse/common/test/TestUrlEncoding.java deleted file mode 100644 index 7cd3199e79c..00000000000 --- a/java/com.sap.sse.common.test/src/com/sap/sse/common/test/TestUrlEncoding.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.sap.sse.common.test; - -import static org.junit.Assert.assertEquals; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; - -import org.junit.Test; - -public class TestUrlEncoding { - @Test - public void testUrlQueryStringEncoding() throws UnsupportedEncodingException { - final String s = "Test & Test"; - assertEquals("Test+%26+Test", URLEncoder.encode(s, "UTF-8")); - assertEquals(s, URLDecoder.decode(URLEncoder.encode(s, "UTF-8"), "UTF-8")); - } -} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java index c419aff1d72..e32777f9172 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java @@ -52,6 +52,10 @@ public interface TimeRange extends Comparable, Serializable { /** * Merges the two ranges, only possible if {@code other} {@link #intersects()} this range. + * + * @return the union of this and the {@code other} time range if they {@link #intersects(TimeRange) intersect} which + * means that the result {@link #includes(TimeRange) includes} both this and the {@code other} time range; + * {@code null} otherwise */ TimeRange union(TimeRange other); diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/impl/MillisecondsDurationImpl.java b/java/com.sap.sse.common/src/com/sap/sse/common/impl/MillisecondsDurationImpl.java index b892cb67e13..96a4af91483 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/impl/MillisecondsDurationImpl.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/impl/MillisecondsDurationImpl.java @@ -60,7 +60,7 @@ public class MillisecondsDurationImpl implements Duration { @Override public double asHours() { - return asMillis() / Duration.ONE_HOUR.asMillis(); + return ((double) asMillis()) / (double) Duration.ONE_HOUR.asMillis(); } @Override diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java b/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java index ac8b02f0d2c..99ee927d7be 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java @@ -12,6 +12,16 @@ public class TimeRangeImpl extends Util.Pair implements Ti return new TimeRangeImpl(new MillisecondsTimePoint(fromMillis), new MillisecondsTimePoint(toMillis)); } + /** + * @param from + * if {@code null}, the time range is considered open on its "left" end, and all {@link TimePoint}s at or + * after {@link TimePoint#BeginningOfTime} and at or before {@code to} are considered {@link #includes + * included} in this time range. + * @param to + * if {@code null}, the time range is considered open on its "right" end, and all {@link TimePoint}s at + * or before {@link TimePoint#EndOfTime} and at or after {@code from} are considered {@link #includes + * included} in this time range. + */ public TimeRangeImpl(TimePoint from, TimePoint to) { super(from == null ? TimePoint.BeginningOfTime : from, to == null ? TimePoint.EndOfTime : to); if (from().after(to())) { diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java index 5dc875a3e8f..24b76beae54 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/UrlHelper.java @@ -5,7 +5,6 @@ package com.sap.sse.common.util; * GWT context does not offer full implementations of URL/URI. * */ public interface UrlHelper { - String encodeUrl(String decodedUrlString); - + String encodeQueryString(String queryString); } diff --git a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages.properties similarity index 64% rename from java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties rename to java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages.properties index a27c517814a..93761cc079d 100644 --- a/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages_en.properties +++ b/java/com.sap.sse.datamining.test/resources/stringmessages/Test_StringMessages.properties @@ -11,4 +11,6 @@ SailID=Sail ID TestCluster=Test Cluster English increment=Increment illegalDimension=Illegal Dimension -DistanceTraveled=Distance Traveled \ No newline at end of file +DistanceTraveled=Distance Traveled +# This message is only contains in the Test_StringMessages.properties for default locale to test the fallback behavior +DefaultTestMessage=Default \ No newline at end of file diff --git a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java index 9c09befa97a..6a6ef866454 100644 --- a/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java +++ b/java/com.sap.sse.datamining.test/src/com/sap/sse/datamining/impl/i18n/TestI18N.java @@ -5,10 +5,8 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.util.Date; -import java.util.HashSet; import java.util.Locale; import java.util.MissingResourceException; -import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -20,7 +18,9 @@ import com.sap.sse.i18n.ResourceBundleStringMessages; public class TestI18N { + private static final Locale DEFAULT_LOCALE = new Locale("default"); private static final String SIMPLE_TEST_MESSAGE_KEY = "SimpleTestMessage"; + private static final String DEFAULT_TEST_MESSAGE_KEY = "DefaultTestMessage"; private static final String TEST_MESSAGE_WITH_PARAMETERS = "TestMessageWithParameters"; private ResourceBundleStringMessages testStringMessages; @@ -32,12 +32,24 @@ public class TestI18N { @Test public void testGettingASimpleMessage() { + assertThat(testStringMessages.get(DEFAULT_LOCALE, SIMPLE_TEST_MESSAGE_KEY), is("English")); + assertThat(testStringMessages.get(Locale.ROOT, SIMPLE_TEST_MESSAGE_KEY), is("English")); assertThat(testStringMessages.get(Locale.ENGLISH, SIMPLE_TEST_MESSAGE_KEY), is("English")); assertThat(testStringMessages.get(Locale.GERMAN, SIMPLE_TEST_MESSAGE_KEY), is("Deutsch")); } + @Test + public void testGettingADefaultMessage() { + assertThat(testStringMessages.get(DEFAULT_LOCALE, DEFAULT_TEST_MESSAGE_KEY), is("Default")); + assertThat(testStringMessages.get(Locale.ROOT, DEFAULT_TEST_MESSAGE_KEY), is("Default")); + assertThat(testStringMessages.get(Locale.ENGLISH, DEFAULT_TEST_MESSAGE_KEY), is("Default")); + assertThat(testStringMessages.get(Locale.GERMAN, DEFAULT_TEST_MESSAGE_KEY), is("Default")); + } + @Test public void testGettingAMessageWithParameters() { + assertThat(testStringMessages.get(DEFAULT_LOCALE, TEST_MESSAGE_WITH_PARAMETERS, "Param0", "Param1"), is("English Param0 - Param1")); + assertThat(testStringMessages.get(Locale.ROOT, TEST_MESSAGE_WITH_PARAMETERS, "Param0", "Param1"), is("English Param0 - Param1")); assertThat(testStringMessages.get(Locale.ENGLISH, TEST_MESSAGE_WITH_PARAMETERS, "Param0", "Param1"), is("English Param0 - Param1")); assertThat(testStringMessages.get(Locale.GERMAN, TEST_MESSAGE_WITH_PARAMETERS, "Param0", "Param1"), is("Deutsch Param0 - Param1")); } @@ -81,25 +93,13 @@ public class TestI18N { @Test public void testGetLocaleForLocaleInfoName() { - assertThat(ResourceBundleStringMessages.Util.getLocaleFor("default"), is(Locale.ENGLISH)); + assertThat(ResourceBundleStringMessages.Util.getLocaleFor("default"), is(DEFAULT_LOCALE)); assertThat(ResourceBundleStringMessages.Util.getLocaleFor("en"), is(Locale.ENGLISH)); assertThat(ResourceBundleStringMessages.Util.getLocaleFor("de"), is(Locale.GERMAN)); + assertThat(ResourceBundleStringMessages.Util.getLocaleFor("zh"), is(Locale.CHINESE)); + assertThat(ResourceBundleStringMessages.Util.getLocaleFor("ja"), is(Locale.JAPANESE)); - assertThat(ResourceBundleStringMessages.Util.getLocaleFor("Unsupported locale info name"), is(Locale.ENGLISH)); - } - - @Test - public void testGetSupportedLocales() { - Set supportedLocales = new HashSet<>(); - for (Locale locale : ResourceBundleStringMessages.Util.getSupportedLocales()) { - supportedLocales.add(locale); - } - - Set expectedSupportedLocales = new HashSet<>(); - expectedSupportedLocales.add(Locale.GERMAN); - expectedSupportedLocales.add(Locale.ENGLISH); - - assertThat(supportedLocales, is(expectedSupportedLocales)); + assertThat(ResourceBundleStringMessages.Util.getLocaleFor("Unsupported locale info name"), is(Locale.ROOT)); } @Test(expected=MissingResourceException.class) diff --git a/java/com.sap.sse.datamining/resources/stringmessages/StringMessages_en.properties b/java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties similarity index 100% rename from java/com.sap.sse.datamining/resources/stringmessages/StringMessages_en.properties rename to java/com.sap.sse.datamining/resources/stringmessages/StringMessages.properties diff --git a/java/com.sap.sse.datamining/resources/stringmessages/XLF/StringMessages.properties.xlf b/java/com.sap.sse.datamining/resources/stringmessages/XLF/StringMessages.properties.xlf new file mode 100644 index 00000000000..b7546abb012 --- /dev/null +++ b/java/com.sap.sse.datamining/resources/stringmessages/XLF/StringMessages.properties.xlf @@ -0,0 +1,49 @@ + + + +
            + + H4sIAAAAAAAAAD2MQQoCMQxF9znFLHWnB8hicCXSjYMHKBpLsE2hTQdhmLsbpuoieT958MeZig+EYyc4erAX7ICpJbQB59+cLH8JjqXfnXDKTRS3bQ1KpWKCi2St+BKzMdJdWQL+Yha4Um1RJw7CT6aCy2Eddstx3cP/d5absG6GZTAHH6Y6WeOvAAAA + + + + + +
            + + + Average + + + Median + + + Sum + + + Maximum + + + Minimum + + + Count + + + m + + + kn + + + Collection + + + {0} ({1}) + + + {0} in {1} + + +
            +
            \ No newline at end of file diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties b/java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties similarity index 100% rename from java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages_en.properties rename to java/com.sap.sse.filestorage/resources/stringmessages/FileStorageStringMessages.properties diff --git a/java/com.sap.sse.filestorage/resources/stringmessages/XLF/FileStorageStringMessages.properties.xlf b/java/com.sap.sse.filestorage/resources/stringmessages/XLF/FileStorageStringMessages.properties.xlf new file mode 100644 index 00000000000..e2ab027cc7d --- /dev/null +++ b/java/com.sap.sse.filestorage/resources/stringmessages/XLF/FileStorageStringMessages.properties.xlf @@ -0,0 +1,37 @@ + + + +
            + + H4sIAAAAAAAAAJ1SwW7bMAy95yt4bNHBPeTcQ4tiQLGiGNrmAxiZTojSkiHKSb3Dvn2klGTddSdL1OPj4/PT9SNpuHsrKRMMLKSQItyP+Ms+b+sO3vcEmXSWwnFXERBxJGAFhIyxTyNsNk+PMMmsUAydMu84ojQwxd4au5Wu70Mg1ae+DmwXsL4rITwQbAXjB5QEsxL8vu3wqLchU0+xMIoCRy2E/fWF6ActTToZrMCJ0Kr/wfgwhw8qL7ZXpfQDpAFa+UxxtUf1M0q2tgXok7V8A7PAnzNEol5B52HgwDYEJsojq3KKer2SFFBOgvOBg7mdMqj5fvbVFVUDK7S5p4tpHDv4Xt93JsY7qHdohf3Esr9xaNXsWgJG2Nqx+mFILLBFpU2Wv7iXVMhG2ZPPCykW1yuLSzHmEx7GWSsi0+DxuLDyVjwUo7HI4hGxMPhY34g+cZyEKnbPdDAF2wXGNMcaIDz1fV3vax8M2QKlBQuHTnFSZPH8BKumZk+mSTi0f3HxoGsGPzTl1Wc/w+b1GW7gsvhdLRx9JZgnSdibvirlyCL/btgYn88TKme9gV/PqXDtbRf7Mbij1R9TuhdHUgMAAA== + + + + + +
            + + + Store files on Amazon S3. The resulting file name is a random UUID plus the original file ending. + + + Access ID (leave blank to use ~/.aws/credentials instead) + + + Secret Access Key (leave blank to use ~/.aws/credentials instead) + + + Name of Bucket to use (has to already exist, and user needs sufficient permissions) + + + Service for storing files in the local file system. Files get stored in localPath+fileName and can be accessed at baseUrl+fileName Note that the content lying in baseUrl must therefore be accessible remotely. This can for examplebe achieved by mounting a remote file system for example from static.sapsailing.com on the replicas to localPath. + + + Base URL + fileName = URL where uploaded file will be accessible + + + Local Path to use for file storage + + +
            +
            \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages.properties.xlf b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages.properties.xlf index f91b4f291cd..fc507785c98 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages.properties.xlf +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages.properties.xlf @@ -1,14 +1,14 @@ - +
            H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA== - + - +
            diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ja.properties.xlf b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ja.properties.xlf index abd201c84fe..154a50eb54c 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ja.properties.xlf +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ja.properties.xlf @@ -1,14 +1,14 @@ - +
            H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA== - + - +
            diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ru.properties.xlf b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ru.properties.xlf index 3fc65e1cf81..2b2cea0d2f7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ru.properties.xlf +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_ru.properties.xlf @@ -1,14 +1,14 @@ - +
            H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA== - + - +
            diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_zh_CN.properties.xlf b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_zh_CN.properties.xlf index 81cbe1dffc3..bed478bad3f 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_zh_CN.properties.xlf +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage/XLF/StringMessages_zh_CN.properties.xlf @@ -1,14 +1,14 @@ - +
            H4sIAAAAAAAAAH1SzU4jMQy+5yn8BIgXyKEa2MuuAFEEZ5O4naiZZOR4WiG0746TtLNod+Hi/2/8eb4UPJLdqjFMU9b4sTmTD/b+p3GYHEU7NGfQe7vx3pAPYm/VmEJ8JL5lzmw3CagGkJ1bmMnDaQyRAEVomiWkPUgGl5OgE5CRoKOv4CESFgI3kjvAW14YEskp86FOJ3IScgJMHoTfAPcY0pVxMReyQ7WNudADZ0d+YRowxnaGEKxFqNUz30eaY6Bity0D7qlp7Ic8TUsKDivhlyBjH7LtRuXzqQsnbZ+PMHpT0L+3aU73yKb0WLcIYIE+YFxeor/L8kRFlNxMLJXKUMuQsv4YbcC8dlaAfua/87rqi/G+v/IPjv5CdDaNvDZX1K+M/itY1N63uCOGiK/xAi3/YC8DF3hR5XZMZVS1mjcJJ7J3aswR40L2uVrjqTgOc30G9uZP3AXryiiTtAv7hbFNDZ+zqkbVqwreg7Xyowk+50RJ1i7sVOn3699mbu9yS1GfoD0/0tIy8wFPN7vhNwMAAA== - + - +
            diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/servlets/ProxiedRemoteServiceServlet.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/servlets/ProxiedRemoteServiceServlet.java index c8582bdaae4..a5eab29b318 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/servlets/ProxiedRemoteServiceServlet.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/servlets/ProxiedRemoteServiceServlet.java @@ -1,7 +1,13 @@ package com.sap.sse.gwt.dispatch.servlets; -import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.rpc.RPCServletUtils; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.google.gwt.user.server.rpc.SerializationPolicy; @@ -29,6 +35,36 @@ public abstract class ProxiedRemoteServiceServlet extends RemoteServiceServlet { } return super.doGetSerializationPolicy(request, moduleBaseURL, strongName); + } + + @Override + protected void doUnexpectedFailure(Throwable e) { + if ((e.getCause() != null && e.getCause().getClass().getName().matches("org\\.apache\\.shiro\\.authz\\..*[Aa]uth.*Exception")) || + (e instanceof SerializationException && e.getMessage().matches("Type 'org\\.apache\\.shiro\\.authz\\..*[Aa]uth.*Exception' was not included in the set of types which can be serialized.*"))) { + final HttpServletResponse servletResponse = getThreadLocalResponse(); + try { + servletResponse.reset(); + } catch (IllegalStateException ex) { + throw new RuntimeException("Unable to report failure", e); + } + ServletContext servletContext = getServletContext(); + RPCServletUtils.writeResponseForUnexpectedFailure(servletContext, servletResponse, e); + servletContext.log("Exception while dispatching incoming RPC call", e.getCause()==null?e:e.getCause()); + try { + servletResponse.setContentType("text/plain"); + servletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + try { + servletResponse.getOutputStream().write((e.getCause()==null?e:e.getCause()).getLocalizedMessage().getBytes("UTF-8")); + } catch (IllegalStateException ex) { + // Handle the (unexpected) case where getWriter() was previously used + servletResponse.getWriter().write("Couldn't write exception"); + } + } catch (IOException ex) { + servletContext.log( + "respondWithUnexpectedFailure failed while sending the previous failure to the client", ex); + } + } else { + super.doUnexpectedFailure(e); + } } - } diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages.properties.xlf b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages.properties.xlf index f91763df506..463a5ef3188 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages.properties.xlf +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages.properties.xlf @@ -1,14 +1,14 @@ - +
            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 - + - +
            diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ja.properties.xlf b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ja.properties.xlf index 8dc7a273a95..27ebb23e72e 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ja.properties.xlf +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ja.properties.xlf @@ -1,14 +1,14 @@ - +
            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 - + - +
            diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ru.properties.xlf b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ru.properties.xlf index bba0cbfc83a..ac32683480e 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ru.properties.xlf +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_ru.properties.xlf @@ -1,14 +1,14 @@ - +
            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 - + - +
            diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_zh_CN.properties.xlf b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_zh_CN.properties.xlf index 0a637913d25..2df5889fcae 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_zh_CN.properties.xlf +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/XLF/StringMessages_zh_CN.properties.xlf @@ -1,14 +1,14 @@ - +
            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 - + - +
            diff --git a/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java index b67b7f04b0a..10a9f5f5d95 100644 --- a/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java +++ b/java/com.sap.sse.shared.android/src/com/sap/sse/util/impl/NonGwtUrlHelper.java @@ -1,10 +1,6 @@ package com.sap.sse.util.impl; import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; import java.net.URLEncoder; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,22 +12,16 @@ public enum NonGwtUrlHelper implements UrlHelper { private static final Logger logger = Logger.getLogger(NonGwtUrlHelper.class.getName()); - public String encodeUrl(String decodedUrlString) { - try { - URL url = new URL(decodedUrlString); - URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), - url.getQuery(), url.getRef()); - return uri.toString(); - } catch (MalformedURLException | URISyntaxException f) { - logger.log(Level.WARNING, "problem encoding URL string "+decodedUrlString, f); - return null; - } - } - @Override public String encodeQueryString(String queryString) { try { - return URLEncoder.encode(queryString, "UTF-8"); + // TODO See bug3664 => https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3664 + // Because of changes in the query string encoding, the Sail InSight-App for iOS will now have problems + // while parsing the registration URLs, which are sent to invited competitors, if e.g. the leaderboard name + // contains whitespaces. To avoid this problems, we use this temporary fix until a new version of the Sail + // InSight-App for iOS is rolled out. Afterwards this fix must be replaced by the line below. + return URLEncoder.encode(queryString, "UTF-8").replace("+", "%20"); + // return URLEncoder.encode(queryString, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.log(Level.WARNING, "problem encoding URL query string "+queryString, e); return null; diff --git a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java index d59d1dda356..d8c8f9940e4 100644 --- a/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java +++ b/java/com.sap.sse/src/com/sap/sse/i18n/ResourceBundleStringMessages.java @@ -1,8 +1,7 @@ package com.sap.sse.i18n; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; +import java.util.ResourceBundle.Control; import com.sap.sse.i18n.impl.NullResourceBundleStringMessages; @@ -20,7 +19,6 @@ import com.sap.sse.i18n.impl.NullResourceBundleStringMessages; * */ public interface ResourceBundleStringMessages { - public static final ResourceBundleStringMessages NULL = new NullResourceBundleStringMessages(); public String getResourceBaseName(); @@ -29,38 +27,20 @@ public interface ResourceBundleStringMessages { public String get(Locale locale, String messageKey, String... parameters); public static final class Util { - - private static boolean supportedLocalesHaveBeenInitialized = false; - private static final Locale DEFAULT_LOCALE = Locale.ENGLISH; - private static final Map supportedLocalesMappedByLocaleInfo = new HashMap<>(); - - public static Locale getLocaleFor(String localeInfoName) { - Locale locale = getSupportedLocalesMap().get(localeInfoName); - return locale != null ? locale : DEFAULT_LOCALE; - } - - public static Iterable getSupportedLocales() { - return getSupportedLocalesMap().values(); - } - - private static Map getSupportedLocalesMap() { - if (!supportedLocalesHaveBeenInitialized) { - initializeSupportedLocales(); + private static final Locale FALLBACK_LOCALE = Locale.ROOT; + public static final Control CONTROL = new Control() { + @Override + public Locale getFallbackLocale(String baseName, Locale locale) { + if (baseName == null) throw new NullPointerException(); + return locale.equals(FALLBACK_LOCALE) ? null : FALLBACK_LOCALE; } - - return supportedLocalesMappedByLocaleInfo; - } + }; - private static void initializeSupportedLocales() { - supportedLocalesMappedByLocaleInfo.put("en", Locale.ENGLISH); - supportedLocalesMappedByLocaleInfo.put("de", Locale.GERMAN); - - supportedLocalesHaveBeenInitialized = true; + public static Locale getLocaleFor(String localeInfoName) { + return Locale.forLanguageTag(localeInfoName); } private Util () { } - } - } diff --git a/java/com.sap.sse/src/com/sap/sse/i18n/impl/ResourceBundleStringMessagesImpl.java b/java/com.sap.sse/src/com/sap/sse/i18n/impl/ResourceBundleStringMessagesImpl.java index b0338dc1d8a..755ae9ed026 100644 --- a/java/com.sap.sse/src/com/sap/sse/i18n/impl/ResourceBundleStringMessagesImpl.java +++ b/java/com.sap.sse/src/com/sap/sse/i18n/impl/ResourceBundleStringMessagesImpl.java @@ -26,7 +26,6 @@ public class ResourceBundleStringMessagesImpl implements ResourceBundleStringMes @Override public String get(Locale locale, String messageKey, String... parameters) { String result = getResourceBundle(locale).getString(messageKey); - for (int i = 0; i < parameters.length; i++) { String target = MESSAGE_PARAMETER_START + i + MESSAGE_PARAMETER_END; result = result.replace(target, parameters[i]); @@ -36,9 +35,9 @@ public class ResourceBundleStringMessagesImpl implements ResourceBundleStringMes private ResourceBundle getResourceBundle(Locale locale) { if (resourceClassLoader != null) { - return ResourceBundle.getBundle(resourceBaseName, locale, resourceClassLoader); + return ResourceBundle.getBundle(resourceBaseName, locale, resourceClassLoader, Util.CONTROL); } else { - return ResourceBundle.getBundle(resourceBaseName, locale); + return ResourceBundle.getBundle(resourceBaseName, locale, Util.CONTROL); } } diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index 52c527abf45..b3c23fdb1f6 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.1.6 +Bundle-Version: 3.2.0 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 4e5e0c38f1d..349445ed0b8 100644 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -16,6 +16,85 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.2.0 **************** +******************************************** +This is a final version. New functionality added: + +Release date: 05/06/2016 +Build number: 12293 + +1) New functionality: + + - IRace adds the new property, status, returning a value of the enum RaceStatusType. + - IRace property visibility has changed to return a value of the enum RaceVisibilityType. + - IRaceCompetitor adds a new property: status of type RaceCompetitorStatusType. + - Competitor updates during a race will be propagated to all subscriptors. + +******************************************** +************* TracAPI 3.1.6 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 22/02/2016 +Build number: 11976 + + 1) Bugs + + - The bug of the previous release (3.1.2) was not fixed. + +******************************************** +************* TracAPI 3.1.5 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 17/02/2016 +Build number: 11948 + + 1) Bugs + + - The bug of the previous release (3.1.2) was not fixed. + +******************************************** +************* TracAPI 3.1.4 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 15/02/2016 +Build number: 11938 + + 1) Bugs + + - An infinity loop has been detected in the thread that sends the application + messages (Repored by Axel Uhl, 12/02/2016) + +******************************************** +************* TracAPI 3.1.3 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 25/01/2016 +Build number: 11888 + + 1) Bugs + + - The bug of the previous release (3.1.2) was not fixed. + +******************************************** +************* TracAPI 3.1.2 **************** +******************************************** +This is a final version. Only fixes bugs in the implementation + +Release date: 21/01/2016 +Build number: 11864 + + 1) Bugs + + - The ISubscriber.stop() method never finishes due to a deadlock (it happens sometimes). + There is a deadlock between one of the internal threads responsible to update the progress + and the thread that calls the stop method. The release 3.1.1 fixed a part of the bug but + it continues happening due to a new deadlock (Reported by Axel Uhl, 20/01/2016) + ******************************************** ************* TracAPI 3.1.1 **************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar index 958f1421f1e..144a06c96f4 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar and b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar differ diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 6164d98127b..814f1bb5dd9 100644 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index 4b2d9e0d5f2..a333bd66595 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.1.6 + 3.2.0 eclipse-plugin diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java index afd44e7e57d..852f080ab7a 100644 --- a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java +++ b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/EventListener.java @@ -40,10 +40,6 @@ public class EventListener implements IEventMessageListener, IPositionSnappedListener, IConnectionStatusListener, IControlPointPositionListener, IControlPassingsListener, IRaceStartStopTimesChangeListener, IControlRouteChangeListener { - - long controlPositionsCount = 0; - long competitorPositionsCount = 0; - long controlPassingsCount = 0; private static void show(Object obj) { System.out.println(Thread.currentThread().getName() + ": " + obj); @@ -61,56 +57,45 @@ public class EventListener implements IEventMessageListener, @Override public void gotRouteChange(IControlRoute controlRoute, long timeStamp) { - //show("New route at " + timeStamp + ": " + controlRoute.toString()); + show("New route at " + timeStamp + ": " + controlRoute.toString()); } @Override public void gotControlPassings(IRaceCompetitor raceCompetitor, IControlPassings markPassings) { - //show("New markpassings " + markPassings + " for the competitor " + raceCompetitor.getCompetitor().toString()); - controlPassingsCount++; + show("New markpassings " + markPassings + " for the competitor " + raceCompetitor.getCompetitor().toString()); } @Override public void gotControlPointPosition(IControl control, IPosition position, int markNumber) { - //show("New position " + position + " for the mark " + control + ", " + markNumber); - controlPositionsCount++; + show("New position " + position + " for the mark " + control + ", " + markNumber); } @Override public void gotPositionSnapped(IRaceCompetitor raceCompetitor, IPositionSnapped positionSnapped) { - show("New position " + positionSnapped + " for the competitor " + raceCompetitor.getCompetitor().toString()); - competitorPositionsCount++; + show("New position " + positionSnapped + " for the competitor " + raceCompetitor.getCompetitor().toString()); } @Override public void gotPositionOffset(IRaceCompetitor raceCompetitor, IPositionOffset position) { - show("New position " + position + " for the competitor " + raceCompetitor.getCompetitor().toString()); - competitorPositionsCount++; + show("New position " + position + " for the competitor " + raceCompetitor.getCompetitor().toString()); } @Override public void gotPosition(IRaceCompetitor raceCompetitor, IPosition position) { - //show("New position " + position + " for the competitor " + raceCompetitor.getCompetitor().toString()); - competitorPositionsCount++; + show("New position " + position + " for the competitor " + raceCompetitor.getCompetitor().toString()); } @Override public void gotRaceStartStopTime(IRace race, IStartStopData startStopData) { - //show("New race start/stop times " + startStopData.toString()); + show("New race start/stop times " + startStopData.toString()); } @Override public void gotTrackingStartStopTime(IRace race, IStartStopData startStopData) { - //show("New tracking race start/stop times " + startStopData.toString()); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + show("New tracking race start/stop times " + startStopData.toString()); } @Override @@ -120,10 +105,7 @@ public class EventListener implements IEventMessageListener, @Override public void stopped(Object object) { - show("Stopping the connection with " + object.toString()); - System.out.println("Number of control positions: " + controlPositionsCount); - System.out.println("Number of competitor positions: " + competitorPositionsCount); - System.out.println("Number of control passings: " + controlPassingsCount); + show("Stopping the connection with " + object.toString()); } @Override diff --git a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java index f1063da5697..5527ef81e3f 100644 --- a/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java +++ b/java/com.tractrac.clientmodule/src/com/tractrac/subscription/app/tracapi/Main.java @@ -59,7 +59,7 @@ public class Main { raceSubscriber.subscribeRaceTimesChanges(eventListener); raceSubscriber.subscribeRouteChanges(eventListener); raceSubscriber.start(); - //eventSubscriber.start(); + eventSubscriber.start(); // Go ahead with GUI or other stuff in main thread System.out.println("Press key to cancel live data stream"); @@ -67,7 +67,7 @@ public class Main { System.out.println("Cancelling data stream"); // Stop data streams - //eventSubscriber.stop(); + eventSubscriber.stop(); raceSubscriber.stop(); } diff --git a/mobile/com.sap.sailing.android.shared/res/values/colors.xml b/mobile/com.sap.sailing.android.shared/res/values/colors.xml index 6f16c91497b..8bcad890f7f 100644 --- a/mobile/com.sap.sailing.android.shared/res/values/colors.xml +++ b/mobile/com.sap.sailing.android.shared/res/values/colors.xml @@ -47,4 +47,5 @@ #333333 @color/gray #D6B363 + #7fEEAA29 diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java index 298ca7ed455..e34dc97b757 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java @@ -1,5 +1,15 @@ package com.sap.sailing.android.shared.services.sending; +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Calendar; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + import android.app.Service; import android.content.ComponentName; import android.content.Context; @@ -19,11 +29,6 @@ import com.sap.sailing.android.shared.services.sending.MessageSenderTask.Message import com.sap.sailing.android.shared.util.PrefUtils; import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; -import java.io.Serializable; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.*; - /** * Service that handles sending messages to a webservice. Deals with an offline setting * by buffering the messages in a file, so that they can be sent when the connection is @@ -234,6 +239,7 @@ public class MessageSendingService extends Service implements MessageSendingList ConnectivityChangedReceiver.enable(this); serviceLogger.onMessageSentFailed(); reportApiConnectivity(APIConnectivity.notReachable); + reportUnsentGPSFixesCount(); } else { sendMessage(intent); } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java index 8b51c6e4084..f8ffeb30218 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java @@ -37,7 +37,9 @@ public class AppUtils { */ public static void lockOrientation(Activity activity) { if (with(activity).isPhone()) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } else { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/LocationHelper.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/LocationHelper.java index 54b66c16021..9836c17b58e 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/LocationHelper.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/LocationHelper.java @@ -16,8 +16,14 @@ public class LocationHelper { * @return */ public static boolean isGPSEnabled(Context context) { - LocationManager service = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - return service.isProviderEnabled(LocationManager.GPS_PROVIDER); + boolean enabled = false; + if (context != null) { + LocationManager service = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + if (service != null) { + enabled = service.isProviderEnabled(LocationManager.GPS_PROVIDER); + } + } + return enabled; } /** @@ -45,20 +51,21 @@ public class LocationHelper { * @param errorMessage */ public static void showNoGPSError(final Context context, String errorMessage) { - AlertDialog.Builder builder = new AlertDialog.Builder(context).setCancelable(true).setTitle(context.getString(R.string.warning)) - .setNegativeButton(context.getString(R.string.no), null) - .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - openLocationSettings(context); - } - }); - if (errorMessage != null) { - builder.setMessage(errorMessage); - } else { - builder.setMessage(context.getString(R.string.enable_gps)); + if (context != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(context).setCancelable(true).setTitle(context.getString(R.string.warning)).setNegativeButton(context.getString(R.string.no), null) + .setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + openLocationSettings(context); + } + }); + if (errorMessage != null) { + builder.setMessage(errorMessage); + } else { + builder.setMessage(context.getString(R.string.enable_gps)); + } + builder.show(); } - builder.show(); } /** @@ -66,6 +73,8 @@ public class LocationHelper { * @param context */ public static void openLocationSettings(Context context) { - context.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + if (context != null) { + context.startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml b/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml index 712306f5355..22905108ea0 100644 --- a/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml +++ b/mobile/com.sap.sailing.android.tracking.app/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="13" + android:versionName="1.11"> + diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml b/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml index 8c43896c8bb..aef3520c33f 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml @@ -8,8 +8,8 @@ GPS Updates werden alle 3 Sekunden verschickt. Ein Aus - Anzeige: Rechtweisend - Anzeige: Missweisend + Anzeige: Missweisend + Anzeige: Rechtweisend Kursanzeige diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values-de/strings_uss_sailing.xml b/mobile/com.sap.sailing.android.tracking.app/res/values-de/strings_uss_sailing.xml new file mode 100644 index 00000000000..89112240cc7 --- /dev/null +++ b/mobile/com.sap.sailing.android.tracking.app/res/values-de/strings_uss_sailing.xml @@ -0,0 +1,7 @@ + + + + Die SAP Sail InSight App unterstützt Live-Tracking von Segelbooten mit Mobilgeräten, wodurch Freunde und Fans den Wettfahrten folgen können, bei der Veranstaltung oder online mit Live-Ergebnistabellen und 2D-Visualisierungen. Segler können ihre Rennen später wieder betrachten und ihre Strategie optimieren. All das ist verfügbar unter: https://www.sapsailing.com\n\nSAP unterstützt US Sailing durch die Implementierung einer Reihe innovativer Technologielösungen, die Analysen für Segler und Teams zur Performance-Verbesserung liefern und Organisatoren bei der errizienteren Abwicklung von Veranstaltungen helfen.\n\nAls nationaler Segelverband verhilft US Sailing dem Sport mit Integrität zu Wachstum, insbesondere auch durch Trainings- und Ausbildungsprogramme für Instruktoren und Wettfahrtleiter sowie Administration und Begleitung von Segelwettbewerben überall in den USA. + https://www.sapsailing.com + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml b/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml index 8020d74638b..16d53809a23 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml @@ -30,7 +30,7 @@ On Off - Display: True - Display: Magnetic + Display: Magnetic + Display: True Heading \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java index a92ddb84be6..2b567c842fd 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java @@ -1,5 +1,11 @@ package com.sap.sailing.android.tracking.app.services; +import java.util.UUID; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; @@ -31,14 +37,12 @@ import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; import com.sap.sailing.android.tracking.app.utils.AppPreferences; import com.sap.sailing.android.tracking.app.utils.DatabaseHelper; import com.sap.sailing.android.tracking.app.valueobjects.EventInfo; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedImpl; import com.sap.sailing.domain.common.tracking.impl.FlatSmartphoneUuidAndGPSFixMovingJsonSerializer; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.UUID; - public class TrackingService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { @@ -153,16 +157,26 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti @Override public void onConnectionSuspended(int i) { - + // no-op } public void reportGPSQualityBearingAndSpeed(float gpsAccurracy, float bearing, float speed, double latitude, double longitude, double altitude) { + Bearing bearingImpl = null; + Speed speedImpl = null; - if (prefs.getDisplayHeadingWithSubtractedDeclination()) { + if (bearing != 0.0) { + bearingImpl = new DegreeBearingImpl(bearing); + } + + if (speed > 0.0) { + speedImpl = new MeterPerSecondSpeedImpl(speed); + } + + if (prefs.getDisplayHeadingWithSubtractedDeclination() && bearingImpl != null) { GeomagneticField geomagneticField = new GeomagneticField((float) latitude, (float) longitude, (float) altitude, System.currentTimeMillis()); - bearing = bearing - geomagneticField.getDeclination(); + bearingImpl.add(new DegreeBearingImpl(- geomagneticField.getDeclination())); } GPSQuality quality = GPSQuality.noSignal; @@ -175,7 +189,7 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti quality = GPSQuality.great; } - gpsQualityListener.gpsQualityAndAccurracyUpdated(quality, gpsAccurracy, bearing, speed); + gpsQualityListener.gpsQualityAndAccurracyUpdated(quality, gpsAccurracy, bearingImpl, speedImpl); } } @@ -324,8 +338,8 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti } public interface GPSQualityListener { - public void gpsQualityAndAccurracyUpdated(GPSQuality quality, float gpsAccurracy, float gpsBearing, - float gpsSpeed); + void gpsQualityAndAccurracyUpdated(GPSQuality quality, float gpsAccurracy, Bearing gpsBearing, + Speed gpsSpeed); } } \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java index 21821894fdf..484b16f4288 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java @@ -38,6 +38,8 @@ import com.sap.sailing.android.tracking.app.utils.AppPreferences; import com.sap.sailing.android.tracking.app.utils.DatabaseHelper; import com.sap.sailing.android.tracking.app.utils.ServiceHelper; import com.sap.sailing.android.tracking.app.valueobjects.EventInfo; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Speed; import com.viewpagerindicator.CirclePageIndicator; public class TrackingActivity extends BaseActivity implements GPSQualityListener, APIConnectivityListener { @@ -70,7 +72,7 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener * This isn't nice. The callbacks for fragments inside a view pager are unreliable, but I want the values to be * displayed immediately after device rotation. Thus they are cached here and the fragments can pick them up. */ - public String lastSpeedIndicatorText = "-"; + public String lastSpeedIndicatorText; public String lastCompassIndicatorText = "-°"; @Override @@ -245,7 +247,7 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener } @Override - public void gpsQualityAndAccurracyUpdated(GPSQuality quality, float gpsAccurracy, float bearing, float speed) { + public void gpsQualityAndAccurracyUpdated(GPSQuality quality, float gpsAccurracy, Bearing bearing, Speed speed) { if (trackingFragment.isAdded()) { trackingFragment.setGPSQualityAndAcurracy(quality, gpsAccurracy); } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java index 28bade01086..8d8e0f4d7e4 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/CompassFragment.java @@ -8,6 +8,7 @@ import android.widget.TextView; import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; +import com.sap.sailing.domain.common.Bearing; public class CompassFragment extends BaseFragment { @@ -19,10 +20,14 @@ public class CompassFragment extends BaseFragment { return view; } - public void setBearing(float heading) { + public void setBearing(Bearing bearing) { if (isAdded()) { TextView headingText = (TextView) getActivity().findViewById(R.id.compass_bearing_text_view); - headingText.setText(String.valueOf(Math.round(heading)) + (char) 0x00B0); + if (bearing != null) { + headingText.setText(String.valueOf(Math.round(bearing.getDegrees())) + (char) 0x00B0); + } else { + headingText.setText(R.string.initial_hyphen_degrees); + } TrackingActivity activity = (TrackingActivity) getActivity(); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java index 60a840d7c18..a31ad5c31c8 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/SpeedFragment.java @@ -1,5 +1,9 @@ package com.sap.sailing.android.tracking.app.ui.fragments; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.NumberFormat; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -8,10 +12,7 @@ import android.widget.TextView; import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; - -import java.math.RoundingMode; -import java.text.DecimalFormat; -import java.text.NumberFormat; +import com.sap.sailing.domain.common.Speed; public class SpeedFragment extends BaseFragment { @@ -23,22 +24,29 @@ public class SpeedFragment extends BaseFragment { return view; } - public void setSpeed(float speedInMetersPerSecond) { + public void setSpeed(Speed speedInMetersPerSecond) { if (isAdded()) { - float speedInKnots = speedInMetersPerSecond * 1.9438444924574f; - - NumberFormat df = DecimalFormat.getInstance(); - df.setMinimumFractionDigits(0); - df.setMaximumFractionDigits(2); - df.setRoundingMode(RoundingMode.HALF_UP); - String formattedSpeed = df.format(speedInKnots); - + String speedIndicatorText; TextView speedText = (TextView) getActivity().findViewById(R.id.speed_text_view); - speedText.setText(getString(R.string.knots, formattedSpeed)); + if (speedInMetersPerSecond != null) { + String formattedSpeed; + double speedInKnots = speedInMetersPerSecond.getKnots(); + + NumberFormat df = DecimalFormat.getInstance(); + df.setMinimumFractionDigits(0); + df.setMaximumFractionDigits(2); + df.setRoundingMode(RoundingMode.HALF_UP); + formattedSpeed = df.format(speedInKnots); + + speedIndicatorText = getString(R.string.knots, formattedSpeed); + } else { + speedIndicatorText = getString(R.string.knots, getString(R.string.initial_hyphen)); + } + speedText.setText(speedIndicatorText); TrackingActivity activity = (TrackingActivity) getActivity(); if (activity != null) { - activity.lastSpeedIndicatorText = formattedSpeed; + activity.lastSpeedIndicatorText = speedIndicatorText; } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinHelper.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinHelper.java index d5b8a244190..c7097feafd2 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinHelper.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinHelper.java @@ -10,6 +10,7 @@ public class CheckinHelper { throws JSONException { JSONObject jsonObject = new JSONObject(); jsonObject.put(DeviceMappingConstants.JSON_COMPETITOR_ID_AS_STRING, competitorId); + // TODO alternatively map device to a mark, providing a mark_id; see also LeaderboardsResource.postCheckin jsonObject.put(DeviceMappingConstants.JSON_DEVICE_TYPE, "android"); jsonObject.put(DeviceMappingConstants.JSON_DEVICE_UUID, deviceUuid); jsonObject.put(DeviceMappingConstants.JSON_PUSH_DEVICE_ID, pushDeviceId); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinManager.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinManager.java index c1efa9c33d7..385e722c20c 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinManager.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/CheckinManager.java @@ -91,6 +91,7 @@ public class CheckinManager { String leaderboardNameFromQR = URLEncoder.encode(uri.getQueryParameter(DeviceMappingConstants.URL_LEADERBOARD_NAME), "UTF-8") .replace("+", "%20"); urlData.competitorId = uri.getQueryParameter(DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING); + // TODO alternatively consider a mark_id urlData.checkinURLStr = urlData.hostWithPort + prefs.getServerCheckinPath().replace("{leaderboard-name}", leaderboardNameFromQR); urlData.eventId = uri.getQueryParameter(DeviceMappingConstants.URL_EVENT_ID); urlData.leaderboardName = leaderboardNameFromQR; diff --git a/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml b/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml index f75044f6b8e..6aa4f0a04f6 100644 --- a/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml +++ b/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="13" + android:versionName="1.11"> + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button.xml b/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button.xml index 028c839d38e..dded0f8fa3f 100644 --- a/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button.xml +++ b/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button.xml @@ -2,6 +2,8 @@ + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button_disabled.xml b/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button_disabled.xml new file mode 100644 index 00000000000..ee41d6642d4 --- /dev/null +++ b/mobile/com.sap.sailing.buoy.positioning/res/drawable/position_send_button_disabled.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/res/layout/fragment_buoy_postion_detail.xml b/mobile/com.sap.sailing.buoy.positioning/res/layout/fragment_buoy_postion_detail.xml index f116dad65ef..2624d4fcfe6 100644 --- a/mobile/com.sap.sailing.buoy.positioning/res/layout/fragment_buoy_postion_detail.xml +++ b/mobile/com.sap.sailing.buoy.positioning/res/layout/fragment_buoy_postion_detail.xml @@ -142,9 +142,10 @@ android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:layout_marginTop="15dp" + android:layout_marginBottom="15dp" android:background="@drawable/position_send_button" - android:text="@string/set_position" android:textSize="18sp" + android:textAllCaps="false" android:textStyle="bold"/> + android:textSize="@dimen/buoy_overview_text_size"/> @@ -26,4 +26,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content"/> + diff --git a/mobile/com.sap.sailing.buoy.positioning/res/values-de/strings.xml b/mobile/com.sap.sailing.buoy.positioning/res/values-de/strings.xml index 2b90e734c50..600aadff5b6 100644 --- a/mobile/com.sap.sailing.buoy.positioning/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.buoy.positioning/res/values-de/strings.xml @@ -4,6 +4,8 @@ Kurstyp Kursmarkierungen Position setzen + POSITION SETZEN\nBitte GPS aktivieren + POSITION SETZEN\nWarte auf GPS Verbindung Position zurücksetzen Empfangen der Markierungen… Gesetzt @@ -28,4 +30,6 @@ Checkout Möchten Sie sich wirklich ausloggen? Sie müssen sich erneut mit dem QR code einloggen, um ihre Bojen zu sehen! + Kein Bojen + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/res/values/dimens.xml b/mobile/com.sap.sailing.buoy.positioning/res/values/dimens.xml new file mode 100644 index 00000000000..57b56a4ea09 --- /dev/null +++ b/mobile/com.sap.sailing.buoy.positioning/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 20sp + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/res/values/strings.xml b/mobile/com.sap.sailing.buoy.positioning/res/values/strings.xml index 40a4011fdbb..e1666a31d72 100644 --- a/mobile/com.sap.sailing.buoy.positioning/res/values/strings.xml +++ b/mobile/com.sap.sailing.buoy.positioning/res/values/strings.xml @@ -5,6 +5,8 @@ Course Type Marks Set Position + SET POSITION\nPlease activate GPS + SET POSITION\nWaiting for GPS connection Reset Position Getting race marks… set @@ -30,4 +32,6 @@ Checkout Do you REALLY want to logout? You will need to re-login with the provided QR code to access your marks! + No marks + \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java index 27956fa4eee..e1befae3236 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java @@ -1,16 +1,22 @@ package com.sap.sailing.android.buoy.positioning.app.ui.fragments; +import java.text.DecimalFormat; + import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; +import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -38,8 +44,6 @@ import com.sap.sailing.android.shared.util.LocationHelper; import com.sap.sailing.android.shared.util.ViewHelper; import com.sap.sailing.android.ui.fragments.BaseFragment; -import java.text.DecimalFormat; - public class BuoyFragment extends BaseFragment implements LocationListener { private static final String TAG = BuoyFragment.class.getName(); private static final int GPS_MIN_DISTANCE = 1; @@ -60,6 +64,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { private IntentReceiver mReceiver; private PositioningActivity positioningActivity; private LocalBroadcastManager mBroadcastManager; + private GPSListener mGpsListener; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -71,9 +76,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { distanceTextView = ViewHelper.get(layout, R.id.marker_gps_distance); ClickListener clickListener = new ClickListener(); - setPositionButton = ViewHelper.get(layout, R.id.marker_set_position_button); - setPositionButton.setVisibility(View.GONE); - setPositionButton.setOnClickListener(clickListener); + setUpSetPositionButton(layout, clickListener); resetPositionButton = ViewHelper.get(layout, R.id.marker_reset_position_button); resetPositionButton.setOnClickListener(clickListener); @@ -83,6 +86,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { signalQualityIndicatorView.setSignalQuality(GPSQuality.noSignal.toInt()); mReceiver = new IntentReceiver(); + mGpsListener = new GPSListener(); mBroadcastManager = LocalBroadcastManager.getInstance(inflater.getContext()); return layout; } @@ -90,6 +94,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { @Override public void onResume() { super.onResume(); + disablePositionButton(); positioningActivity = (PositioningActivity) getActivity(); mapFragment = (MapFragment) positioningActivity.getFragmentManager().findFragmentById(R.id.map); mapFragment.getMap().setMapType(GoogleMap.MAP_TYPE_SATELLITE); @@ -111,6 +116,23 @@ public class BuoyFragment extends BaseFragment implements LocationListener { checkGPS(); } + private void setUpSetPositionButton(View layout, ClickListener clickListener) { + setPositionButton = ViewHelper.get(layout, R.id.marker_set_position_button); + disablePositionButton(); + setPositionButton.setOnClickListener(clickListener); + } + + private void disablePositionButton() { + setPositionButton.setEnabled(false); + boolean gpsEnabled = LocationHelper.isGPSEnabled(getActivity()); + int buttonTextId = gpsEnabled ? R.string.set_position_no_gps_yet : R.string.set_position_disabled_gps; + String text = getString(buttonTextId); + SpannableString disabledButtonText = new SpannableString(text); + AbsoluteSizeSpan absoluteSizeSpan = new AbsoluteSizeSpan(10, true); + disabledButtonText.setSpan(absoluteSizeSpan, text.indexOf("\n"), text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + setPositionButton.setText(disabledButtonText); + } + private void checkGPS() { if (lastKnownLocation == null) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) @@ -140,6 +162,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { super.onPause(); // Unsubscribe from location updates for power saving locationManager.removeUpdates(this); + locationManager.removeGpsStatusListener(mGpsListener); mBroadcastManager.unregisterReceiver(mReceiver); } @@ -176,9 +199,12 @@ public class BuoyFragment extends BaseFragment implements LocationListener { @Override public void onLocationChanged(Location location) { if (getActivity() instanceof PositioningActivity) { + mapFragment.getMap().setMyLocationEnabled(true); lastKnownLocation = location; reportGPSQuality(lastKnownLocation.getAccuracy()); - setPositionButton.setVisibility(View.VISIBLE); + setPositionButton.setEnabled(true); + setPositionButton.setAllCaps(true); + setPositionButton.setText(R.string.set_position); setUpTextUI(lastKnownLocation); updateMap(); } @@ -194,7 +220,6 @@ public class BuoyFragment extends BaseFragment implements LocationListener { } private void configureMap(GoogleMap map) { - map.setMyLocationEnabled(true); map.getUiSettings().setZoomControlsEnabled(true); map.setPadding(0, 50, 0, 0); } @@ -224,6 +249,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { locationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE); locationManager.removeUpdates(this); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_MIN_TIME, GPS_MIN_DISTANCE, this); + locationManager.addGpsStatusListener(mGpsListener); Location initialLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); @@ -282,7 +308,7 @@ public class BuoyFragment extends BaseFragment implements LocationListener { } public interface pingListener { - public void updatePing(); + void updatePing(); } private class ClickListener implements OnClickListener { @@ -327,4 +353,21 @@ public class BuoyFragment extends BaseFragment implements LocationListener { } } } + + private class GPSListener implements GpsStatus.Listener { + + @Override + public void onGpsStatusChanged(int event) { + switch (event) { + case GpsStatus.GPS_EVENT_STOPPED: { + disablePositionButton(); + distanceTextView.setText(N_A); + accuracyTextView.setText(N_A); + mapFragment.getMap().setMyLocationEnabled(false); + reportGPSQuality(0); + break; + } + } + } + } } diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/RegattaFragment.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/RegattaFragment.java index 914649f24b9..056ad050d40 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/RegattaFragment.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/RegattaFragment.java @@ -39,8 +39,9 @@ public class RegattaFragment extends BaseFragment implements LoaderCallbacks + android:versionCode="19" + android:versionName="1.11"> - - - - - - - - - - - - - - - - - - - - + - - + - - + + + + + + + diff --git a/mobile/com.sap.sailing.racecommittee.app/project.properties b/mobile/com.sap.sailing.racecommittee.app/project.properties index c16fb912f8d..280985d63c7 100755 --- a/mobile/com.sap.sailing.racecommittee.app/project.properties +++ b/mobile/com.sap.sailing.racecommittee.app/project.properties @@ -12,9 +12,9 @@ # Project target. target=Google Inc.:Google APIs:22 -android.library.reference.1=..\\google-play-services_lib -android.library.reference.2=..\\com.sap.sailing.android.shared -android.library.reference.3=..\\google-android-support-v7-recyclerview_lib -android.library.reference.4=..\\github-advrecyclerview_lib -android.library.reference.5=..\\licensesdialog_lib\\de.psdev.licensesdialog +android.library.reference.1=../google-play-services_lib +android.library.reference.2=../com.sap.sailing.android.shared +android.library.reference.3=../google-android-support-v7-recyclerview_lib +android.library.reference.4=../github-advrecyclerview_lib +android.library.reference.5=../licensesdialog_lib/de.psdev.licensesdialog android.library.reference.6=../google-android-support-v13 diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_normal.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_normal.xml index 8ed46a7b838..eefcef05748 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_normal.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_normal.xml @@ -1,106 +1,110 @@ + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:background="?attr/sap_gray"> - - - - - - - - - - - - - - - - - - + android:layout_marginRight="@dimen/side_padding" + android:layout_marginTop="@dimen/header_margin_top" + android:gravity="center" + android:singleLine="true" + android:textColor="?attr/white" + android:textSize="@dimen/panel_button_header_size" + tools:text="@string/time"/> - + + + + + - + android:layout_centerVertical="true"> - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_xlarge.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_xlarge.xml index 0e637209a2e..e03b51df0e3 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_xlarge.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_time_xlarge.xml @@ -1,5 +1,5 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_headers.xml b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_headers.xml deleted file mode 100644 index 2e04a35958f..00000000000 --- a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_headers.xml +++ /dev/null @@ -1,15 +0,0 @@ - - -
            -
            -
            -
            - diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppPreferences.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppPreferences.java index 74b616c42f5..8d141fc4ecc 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppPreferences.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/AppPreferences.java @@ -11,6 +11,8 @@ import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.preference.PreferenceManager; import android.provider.Settings.Secure; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.gms.maps.model.LatLng; @@ -40,6 +42,7 @@ public class AppPreferences { private final static String HIDDEN_PREFERENCE_AUTHOR_PRIORITY = "authorPriority"; private final static String HIDDEN_PREFERENCE_BOAT_CLASS = "boatClassPref"; + private final static String HIDDEN_PREFERENCE_COURSE_LAYOUT = "courseLayoutPref"; private final static String HIDDEN_PREFERENCE_NUMBER_OF_ROUNDS = "numberOfRoundsPref"; @@ -59,7 +62,11 @@ public class AppPreferences { } public static AppPreferences on(Context context, String preferenceName) { - return new AppPreferences(context, preferenceName); + return new AppPreferences(context, preferenceName, true); + } + + public static AppPreferences on(Context context, String preferenceName, boolean overload) { + return new AppPreferences(context, preferenceName, overload); } protected final Context context; @@ -76,31 +83,38 @@ public class AppPreferences { }; private Set pollingActiveChangedListeners = new HashSet<>(); - protected final SharedPreferences preferences; + protected Helper helper; protected AppPreferences(Context context) { this.context = context.getApplicationContext(); - this.preferences = PreferenceManager.getDefaultSharedPreferences(context); + helper = new Helper(PreferenceManager.getDefaultSharedPreferences(context), null); } - public AppPreferences(Context context, String preferenceName) { - this.context = context.getApplicationContext(); - this.preferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE); + public AppPreferences(Context context, String preferenceName, boolean overload) { + this.context = context; + + SharedPreferences defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences customSharedPreferences = context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE); + if (overload) { + helper = new Helper(defaultSharedPreferences, customSharedPreferences); + } else { + helper = new Helper(customSharedPreferences, null); + } } public void setAuthor(AbstractLogEventAuthor author) { - preferences.edit().putString(HIDDEN_PREFERENCE_AUTHOR_NAME, author.getName()).putInt(HIDDEN_PREFERENCE_AUTHOR_PRIORITY, author.getPriority()) + helper.getEditor().putString(HIDDEN_PREFERENCE_AUTHOR_NAME, author.getName()).putInt(HIDDEN_PREFERENCE_AUTHOR_PRIORITY, author.getPriority()) .commit(); } public AbstractLogEventAuthor getAuthor() { - String authorName = preferences.getString(HIDDEN_PREFERENCE_AUTHOR_NAME, ""); - int authorPriority = preferences.getInt(HIDDEN_PREFERENCE_AUTHOR_PRIORITY, 0); + String authorName = helper.getString(HIDDEN_PREFERENCE_AUTHOR_NAME, ""); + int authorPriority = helper.getInt(HIDDEN_PREFERENCE_AUTHOR_PRIORITY, 0); return new LogEventAuthorImpl(authorName, authorPriority); } public BoatClassType getBoatClass() { - String boatClass = preferences.getString(HIDDEN_PREFERENCE_BOAT_CLASS, null); + String boatClass = helper.getString(HIDDEN_PREFERENCE_BOAT_CLASS, null); if (boatClass == null) { return null; } @@ -108,7 +122,7 @@ public class AppPreferences { } public List getByNameCourseDesignerCourseNames() { - Set values = preferences.getStringSet(key(R.string.preference_course_designer_by_name_course_names_key), new HashSet()); + Set values = helper.getStringSet(key(R.string.preference_course_designer_by_name_course_names_key), new HashSet()); return new ArrayList<>(values); } @@ -117,7 +131,7 @@ public class AppPreferences { } public CourseLayouts getCourseLayout() { - String courseLayout = preferences.getString(HIDDEN_PREFERENCE_COURSE_LAYOUT, null); + String courseLayout = helper.getString(HIDDEN_PREFERENCE_COURSE_LAYOUT, null); if (courseLayout == null) return null; CourseLayouts storedCourseLayout; @@ -132,12 +146,12 @@ public class AppPreferences { } public CourseDesignerMode getDefaultCourseDesignerMode() { - String mode = preferences.getString(key(R.string.preference_course_designer_override_key), ""); + String mode = helper.getString(key(R.string.preference_course_designer_override_key), "BY_NAME"); return CourseDesignerMode.valueOf(mode); } public RacingProcedureType getDefaultRacingProcedureType() { - String defaultStartProcedureType = preferences.getString(key(R.string.preference_racing_procedure_override_key), ""); + String defaultStartProcedureType = helper.getString(key(R.string.preference_racing_procedure_override_key), ""); return RacingProcedureType.valueOf(defaultStartProcedureType); } @@ -146,20 +160,20 @@ public class AppPreferences { } public String getDeviceIdentifier(String defaultValue) { - String identifier = preferences.getString(key(R.string.preference_identifier_key), ""); + String identifier = helper.getString(key(R.string.preference_identifier_key), ""); return TextUtils.isEmpty(identifier) ? defaultValue : identifier; } public boolean getGateStartHasAdditionalGolfDownTime() { - return preferences.getBoolean(key(R.string.preference_racing_procedure_gatestart_hasadditionalgolfdowntime_key), true); + return helper.getBoolean(key(R.string.preference_racing_procedure_gatestart_hasadditionalgolfdowntime_key), true); } public boolean getGateStartHasPathfinder() { - return preferences.getBoolean(key(R.string.preference_racing_procedure_gatestart_haspathfinder_key), true); + return helper.getBoolean(key(R.string.preference_racing_procedure_gatestart_haspathfinder_key), true); } public LoginType getLoginType() { - int type = preferences.getInt(HIDDEN_PREFERENCE_LOGIN_TYPE, -1); + int type = helper.getInt(HIDDEN_PREFERENCE_LOGIN_TYPE, -1); switch (type) { case 0: { return LoginType.NONE; @@ -178,16 +192,16 @@ public class AppPreferences { } public String getMailRecipient() { - return preferences.getString(key(R.string.preference_mail_key), ""); + return helper.getString(key(R.string.preference_mail_key), ""); } public List getManagedCourseAreaNames() { - Set values = preferences.getStringSet(key(R.string.preference_course_areas_key), new HashSet()); + Set values = helper.getStringSet(key(R.string.preference_course_areas_key), new HashSet()); return new ArrayList<>(values); } public NumberOfRounds getNumberOfRounds() { - String numberOfRounds = preferences.getString(HIDDEN_PREFERENCE_NUMBER_OF_ROUNDS, null); + String numberOfRounds = helper.getString(HIDDEN_PREFERENCE_NUMBER_OF_ROUNDS, null); if (numberOfRounds == null) return null; return NumberOfRounds.valueOf(numberOfRounds); @@ -197,12 +211,12 @@ public class AppPreferences { * Gets polling interval in minutes */ public int getPollingInterval() { - return preferences.getInt(key(R.string.preference_polling_interval_key), 0); + return helper.getInt(key(R.string.preference_polling_interval_key), 0); } public Flags getRacingProcedureClassFlag(RacingProcedureType type) { String key = getRacingProcedureClassFlagKey(type); - return Flags.valueOf(preferences.getString(key, Flags.CLASS.name())); + return Flags.valueOf(helper.getString(key, Flags.CLASS.name())); } private String getRacingProcedureClassFlagKey(RacingProcedureType type) { @@ -224,7 +238,7 @@ public class AppPreferences { public boolean getRacingProcedureHasIndividualRecall(RacingProcedureType type) { String key = getRacingProcedureHasIndividualRecallKey(type); - return preferences.getBoolean(key, false); + return helper.getBoolean(key, false); } private String getRacingProcedureHasIndividualRecallKey(RacingProcedureType type) { @@ -246,7 +260,7 @@ public class AppPreferences { public boolean getRacingProcedureIsResultEntryEnabled(RacingProcedureType type) { String key = getRacingProcedureIsResultEntryEnabledKey(type); - return preferences.getBoolean(key, false); + return helper.getBoolean(key, false); } private String getRacingProcedureIsResultEntryEnabledKey(RacingProcedureType type) { @@ -267,7 +281,7 @@ public class AppPreferences { } public Set getRRS26StartmodeFlags() { - Set flagNames = preferences.getStringSet(key(R.string.preference_racing_procedure_rrs26_startmode_flags_key), new HashSet()); + Set flagNames = helper.getStringSet(key(R.string.preference_racing_procedure_rrs26_startmode_flags_key), new HashSet()); Set flags = new HashSet<>(); for (String flagName : flagNames) { flags.add(Flags.valueOf(flagName)); @@ -276,31 +290,31 @@ public class AppPreferences { } public String getServerBaseURL() { - return preferences.getString(key(R.string.preference_server_url_key), null); + return helper.getString(key(R.string.preference_server_url_key), null); } public double getWindBearingFromDirection() { - long windBearingAsLong = preferences.getLong(HIDDEN_PREFERENCE_WIND_BEARING, 0); + long windBearingAsLong = helper.getLong(HIDDEN_PREFERENCE_WIND_BEARING, 0); return Double.longBitsToDouble(windBearingAsLong); } public double getWindSpeed() { - long windSpeedAsLong = preferences.getLong(HIDDEN_PREFERENCE_WIND_SPEED, 0); + long windSpeedAsLong = helper.getLong(HIDDEN_PREFERENCE_WIND_SPEED, 0); return Double.longBitsToDouble(windSpeedAsLong); } public LatLng getWindPosition() { - double lat = Double.longBitsToDouble(preferences.getLong(HIDDEN_PREFERENCE_WIND_LAT, 0)); - double lng = Double.longBitsToDouble(preferences.getLong(HIDDEN_PREFERENCE_WIND_LNG, 0)); + double lat = Double.longBitsToDouble(helper.getLong(HIDDEN_PREFERENCE_WIND_LAT, 0)); + double lng = Double.longBitsToDouble(helper.getLong(HIDDEN_PREFERENCE_WIND_LNG, 0)); return new LatLng(lat, lng); } public boolean isPollingActive() { - return preferences.getBoolean(key(R.string.preference_polling_active_key), false); + return helper.getBoolean(key(R.string.preference_polling_active_key), false); } public boolean isSendingActive() { - return preferences.getBoolean(context.getResources().getString(R.string.preference_isSendingActive_key), context.getResources() + return helper.getBoolean(context.getResources().getString(R.string.preference_isSendingActive_key), context.getResources() .getBoolean(R.bool.preference_isSendingActive_default)); } @@ -310,46 +324,46 @@ public class AppPreferences { public void registerPollingActiveChangedListener(final PollingActiveChangedListener listener) { if (pollingActiveChangedListeners.isEmpty()) { - preferences.registerOnSharedPreferenceChangeListener(pollingActiveChangedListener); + helper.getDevice().registerOnSharedPreferenceChangeListener(pollingActiveChangedListener); } pollingActiveChangedListeners.add(listener); } public void setBoatClass(BoatClassType boatClass) { String boatClassString = boatClass.name(); - preferences.edit().putString(HIDDEN_PREFERENCE_BOAT_CLASS, boatClassString).commit(); + helper.getEditor().putString(HIDDEN_PREFERENCE_BOAT_CLASS, boatClassString).commit(); } public void setByNameCourseDesignerCourseNames(List courseNames) { - preferences.edit().putStringSet(key(R.string.preference_course_designer_by_name_course_names_key), new HashSet<>(courseNames)).commit(); + helper.getEditor().putStringSet(key(R.string.preference_course_designer_by_name_course_names_key), new HashSet<>(courseNames)).commit(); } public void setCourseLayout(CourseLayouts courseLayout) { String courseLayoutString = courseLayout.name(); - preferences.edit().putString(HIDDEN_PREFERENCE_COURSE_LAYOUT, courseLayoutString).commit(); + helper.getEditor().putString(HIDDEN_PREFERENCE_COURSE_LAYOUT, courseLayoutString).commit(); } public void setDefaultCourseDesignerMode(CourseDesignerMode mode) { - preferences.edit().putString(key(R.string.preference_course_designer_override_key), mode.name()).commit(); + helper.getEditor().putString(key(R.string.preference_course_designer_override_key), mode.name()).commit(); } public void setDefaultRacingProcedureType(RacingProcedureType type) { - preferences.edit().putString(key(R.string.preference_racing_procedure_override_key), type.name()).commit(); + helper.getEditor().putString(key(R.string.preference_racing_procedure_override_key), type.name()).commit(); } public void setGateStartHasAdditionalGolfDownTime(boolean hasAdditionalGolfDownTime) { - preferences.edit().putBoolean(key(R.string.preference_racing_procedure_gatestart_hasadditionalgolfdowntime_key), hasAdditionalGolfDownTime) + helper.getEditor().putBoolean(key(R.string.preference_racing_procedure_gatestart_hasadditionalgolfdowntime_key), hasAdditionalGolfDownTime) .commit(); } public void setGateStartHasPathfinder(boolean hasPathfinder) { - preferences.edit().putBoolean(key(R.string.preference_racing_procedure_gatestart_haspathfinder_key), hasPathfinder).commit(); + helper.getEditor().putBoolean(key(R.string.preference_racing_procedure_gatestart_haspathfinder_key), hasPathfinder).commit(); } public void setLoginType(LoginType type) { - ExLog.i(getContext(), this.getClass().toString(), "setLoginType: " + type); + ExLog.i(context, this.getClass().toString(), "setLoginType: " + type); - Editor setEdit = preferences.edit(); + Editor setEdit = helper.getEditor(); switch (type) { case NONE: { @@ -374,31 +388,31 @@ public class AppPreferences { } public void setMailRecipient(String mail) { - preferences.edit().putString(key(R.string.preference_mail_key), mail).commit(); + helper.getEditor().putString(key(R.string.preference_mail_key), mail).commit(); } public void setManagedCourseAreaNames(List courseAreaNames) { - preferences.edit().putStringSet(key(R.string.preference_course_areas_key), new HashSet<>(courseAreaNames)).commit(); + helper.getEditor().putStringSet(key(R.string.preference_course_areas_key), new HashSet<>(courseAreaNames)).commit(); } public void setNumberOfRounds(NumberOfRounds numberOfRounds) { final String numberOfRoundsString = numberOfRounds.name(); - preferences.edit().putString(HIDDEN_PREFERENCE_NUMBER_OF_ROUNDS, numberOfRoundsString).commit(); + helper.getEditor().putString(HIDDEN_PREFERENCE_NUMBER_OF_ROUNDS, numberOfRoundsString).commit(); } public void setRacingProcedureClassFlag(RacingProcedureType type, Flags flag) { final String key = getRacingProcedureClassFlagKey(type); - preferences.edit().putString(key, flag.name()).commit(); + helper.getEditor().putString(key, flag.name()).commit(); } public void setRacingProcedureHasIndividualRecall(RacingProcedureType type, Boolean hasRecall) { final String key = getRacingProcedureHasIndividualRecallKey(type); - preferences.edit().putBoolean(key, hasRecall).commit(); + helper.getEditor().putBoolean(key, hasRecall).commit(); } public void setRacingProcedureIsResultEntryEnabled(RacingProcedureType type, Boolean resultEntryEnabled) { final String key = getRacingProcedureIsResultEntryEnabledKey(type); - preferences.edit().putBoolean(key, resultEntryEnabled).commit(); + helper.getEditor().putBoolean(key, resultEntryEnabled).commit(); } public void setRRS26StartmodeFlags(Set flags) { @@ -406,96 +420,151 @@ public class AppPreferences { for (Flags flag : flags) { flagNames.add(flag.name()); } - preferences.edit().putStringSet(key(R.string.preference_racing_procedure_rrs26_startmode_flags_key), flagNames).commit(); + helper.getEditor().putStringSet(key(R.string.preference_racing_procedure_rrs26_startmode_flags_key), flagNames).commit(); } public void setSendingActive(boolean activate) { - ExLog.i(getContext(), this.getClass().toString(), "setSendingActive: " + activate); - preferences.edit().putBoolean(context.getResources().getString(R.string.preference_isSendingActive_key), activate).commit(); + ExLog.i(context, this.getClass().toString(), "setSendingActive: " + activate); + helper.getEditor().putBoolean(context.getResources().getString(R.string.preference_isSendingActive_key), activate).commit(); } public void setWindBearingFromDirection(double enteredWindBearing) { long windBearingAsLong = Double.doubleToLongBits(enteredWindBearing); - preferences.edit().putLong(HIDDEN_PREFERENCE_WIND_BEARING, windBearingAsLong).commit(); + helper.getEditor().putLong(HIDDEN_PREFERENCE_WIND_BEARING, windBearingAsLong).commit(); } public void setWindSpeed(double enteredWindSpeed) { long windSpeedAsLong = Double.doubleToLongBits(enteredWindSpeed); - preferences.edit().putLong(HIDDEN_PREFERENCE_WIND_SPEED, windSpeedAsLong).commit(); + helper.getEditor().putLong(HIDDEN_PREFERENCE_WIND_SPEED, windSpeedAsLong).commit(); } public void setWindPosition(LatLng latLng) { long lat = Double.doubleToLongBits(latLng.latitude); long lng = Double.doubleToLongBits(latLng.longitude); - preferences.edit().putLong(HIDDEN_PREFERENCE_WIND_LAT, lat).putLong(HIDDEN_PREFERENCE_WIND_LNG, lng).commit(); + helper.getEditor().putLong(HIDDEN_PREFERENCE_WIND_LAT, lat).putLong(HIDDEN_PREFERENCE_WIND_LNG, lng).commit(); } public void unregisterPollingActiveChangedListener(PollingActiveChangedListener listener) { pollingActiveChangedListeners.remove(listener); if (pollingActiveChangedListeners.isEmpty()) { - preferences.unregisterOnSharedPreferenceChangeListener(pollingActiveChangedListener); + helper.getDevice().unregisterOnSharedPreferenceChangeListener(pollingActiveChangedListener); } } public boolean isDemoAllowed() { - return preferences.getBoolean(context.getString(R.string.preference_allow_demo_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_allow_demo_key), context.getResources() .getBoolean(R.bool.preference_allow_demo_default)); } public boolean wakelockEnabled() { - return preferences - .getBoolean(context.getString(R.string.preference_wakelock_key), context.getResources().getBoolean(R.bool.preference_wakelock_default)); + return helper.getBoolean(context.getString(R.string.preference_wakelock_key), context.getResources().getBoolean(R.bool.preference_wakelock_default)); } public boolean isOfflineMode() { - return preferences - .getBoolean(context.getString(R.string.preference_offline_key), context.getResources().getBoolean(R.bool.preference_offline_default)); + return helper.getBoolean(context.getString(R.string.preference_offline_key), context.getResources().getBoolean(R.bool.preference_offline_default)); } public boolean isDependentRacesAllowed() { - return preferences.getBoolean(context.getString(R.string.preference_allow_dependent_races_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_allow_dependent_races_key), context.getResources() .getBoolean(R.bool.preference_allow_dependent_races_default)); } public int getDependentRacesOffset() { - return preferences.getInt(context.getString(R.string.preference_dependent_races_offset_key), context.getResources() + return helper.getInt(context.getString(R.string.preference_dependent_races_offset_key), context.getResources() .getInteger(R.integer.preference_dependent_races_offset_default)); } public String getTheme() { - return preferences - .getString(context.getString(R.string.preference_theme_key), context.getResources().getString(R.string.preference_theme_default)); + return helper.getString(context.getString(R.string.preference_theme_key), context.getResources().getString(R.string.preference_theme_default)); } public void setAccessToken(String accessToken) { - preferences.edit().putString(context.getString(R.string.preference_access_token_key), accessToken).commit(); + helper.getEditor().putString(context.getString(R.string.preference_access_token_key), accessToken).commit(); } public String getAccessToken() { - return preferences.getString(context.getString(R.string.preference_access_token_key), null); + return helper.getString(context.getString(R.string.preference_access_token_key), null); } public boolean isMagnetic() { - return preferences.getBoolean(context.getString(R.string.preference_heading_with_declination_subtracted_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_heading_with_declination_subtracted_key), context.getResources() .getBoolean(R.bool.preference_heading_with_declination_subtracted_default)); } public boolean isRaceFactorChangeAllow() { - return preferences.getBoolean(context.getString(R.string.preference_allow_edit_race_factor_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_allow_edit_race_factor_key), context.getResources() .getBoolean(R.bool.preference_allow_edit_race_factor_default)); } public String showNonPublic() { - return preferences.getBoolean(context.getString(R.string.preference_non_public_events_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_non_public_events_key), context.getResources() .getBoolean(R.bool.preference_non_public_events_default)) ? "true" : "false"; } public boolean needConfigRefresh() { - return preferences.getBoolean(context.getString(R.string.preference_config_needs_refresh_key), context.getResources() + return helper.getBoolean(context.getString(R.string.preference_config_needs_refresh_key), context.getResources() .getBoolean(R.bool.preference_config_needs_refresh_default)); } public void setNeedConfigRefresh(boolean refresh) { - preferences.edit().putBoolean(context.getString(R.string.preference_config_needs_refresh_key), refresh).commit(); + helper.getEditor().putBoolean(context.getString(R.string.preference_config_needs_refresh_key), refresh).commit(); + } + + private class Helper { + private SharedPreferences device; + private SharedPreferences regatta; + + public Helper(@NonNull SharedPreferences device, @Nullable SharedPreferences regatta) { + this.device = device; + this.regatta = regatta; + } + + public Editor getEditor() { + return device.edit(); + } + + public SharedPreferences getDevice() { + return device; + } + + public boolean getBoolean(String key, boolean defValue) { + boolean value = device.getBoolean(key, defValue); + if (regatta != null) { + value = regatta.getBoolean(key, value); + } + return value; + } + + public String getString(String key, String defValue) { + String value = device.getString(key, defValue); + if (regatta != null) { + value = regatta.getString(key, value); + } + return value; + } + + public Set getStringSet(String key, Set defValue) { + Set value = device.getStringSet(key, defValue); + if (regatta != null) { + value = regatta.getStringSet(key, value); + } + return value; + } + + public int getInt(String key, int defValue) { + int value = device.getInt(key, defValue); + if (regatta != null) { + value = regatta.getInt(key, value); + } + return value; + } + + public long getLong(String key, long defValue) { + long value = device.getLong(key, defValue); + if (regatta != null) { + value = regatta.getLong(key, value); + } + return value; + } } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/BaseActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/BaseActivity.java index 8a0566ace3a..61f5627381e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/BaseActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/BaseActivity.java @@ -39,7 +39,7 @@ public class BaseActivity extends SendingServiceAwareActivity { AppUtils.lockOrientation(this); ThemeHelper.setTheme(this); - this.preferences = AppPreferences.on(getApplicationContext()); + preferences = AppPreferences.on(getApplicationContext()); } @Override @@ -102,12 +102,15 @@ public class BaseActivity extends SendingServiceAwareActivity { public void onResume() { super.onResume(); - preferences = AppPreferences.on(this); if (preferences.wakelockEnabled()) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } } + public AppPreferences getPreferences() { + return preferences; + } + public void safeClose(Closeable c) { if (c != null) { try { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/PreferenceActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/PreferenceActivity.java index 3c6eb28ef2a..3f5adb1c5ad 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/PreferenceActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/PreferenceActivity.java @@ -11,69 +11,36 @@ import android.support.v7.widget.Toolbar; import android.view.MenuItem; import android.view.Window; import android.view.WindowManager; -import android.widget.Toast; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.util.AppUtils; -import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.racegroup.RaceGroup; -import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.data.DataManager; -import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; -import com.sap.sailing.racecommittee.app.domain.configuration.impl.PreferencesRegattaConfigurationLoader; import com.sap.sailing.racecommittee.app.ui.fragments.MainPreferenceFragment; import com.sap.sailing.racecommittee.app.ui.fragments.preference.RegattaPreferenceFragment; import com.sap.sailing.racecommittee.app.utils.PreferenceHelper; public class PreferenceActivity extends AppCompatActivity { - public static final String SPECIFIC_REGATTA_PREFERENCES_NAME = "TEMP_PREFERENCE_KEY"; + public static final String SPECIFIC_REGATTA_PREFERENCES_NAME = "REGATTA_PREFERENCES_"; public static final String EXTRA_SPECIFIC_REGATTA_NAME = "EXTRA_SPECIFIC_REGATTA_NAME"; public static final String EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME = "EXTRA_SPECIFIC_REGATTA_PREFERENCE_KEY"; public static final String EXTRA_SHOW_FRAGMENT = "SHOW_FRAGMENT"; public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = "SHOW_FRAGMENT_ARGUMENTS"; private static final String TAG = PreferenceActivity.class.getName(); - private boolean mIsRedirected = false; - public static void openSpecificRegattaConfiguration(Context context, RaceGroup raceGroup) { - // reset temp preferences - PreferenceHelper helper = new PreferenceHelper(context, "TEMP_PREFERENCE_KEY"); - helper.clearPreferences(); - helper.resetPreferences(true); - - // store local configuration in temp preferences - RegattaConfiguration configuration = raceGroup.getRegattaConfiguration(); - AppPreferences preferences = AppPreferences.on(context, "TEMP_PREFERENCE_KEY"); - PreferencesRegattaConfigurationLoader preferencesLoader = new PreferencesRegattaConfigurationLoader(configuration, preferences); - preferencesLoader.store(); + String regattaPreference = PreferenceHelper.getRegattaPrefFileName(raceGroup.getName()); Intent intent = new Intent(context, PreferenceActivity.class); Bundle info = new Bundle(); intent.putExtra(EXTRA_SHOW_FRAGMENT, RegattaPreferenceFragment.class.getName()); - info.putString(EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME, SPECIFIC_REGATTA_PREFERENCES_NAME); + info.putString(EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME, regattaPreference); info.putString(EXTRA_SPECIFIC_REGATTA_NAME, raceGroup.getName()); intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, info); context.startActivity(intent); } - public static void commitSpecificRegattaConfiguration(Context context, String preferencesName, - String raceGroupName) { - - ReadonlyDataManager dataManager = DataManager.create(context); - RaceGroup group = dataManager.getDataStore().getRaceGroup(raceGroupName); - if (group != null) { - RegattaConfiguration localConfiguration = group.getRegattaConfiguration(); - AppPreferences preferences = AppPreferences.on(context, preferencesName); - PreferencesRegattaConfigurationLoader loader = new PreferencesRegattaConfigurationLoader(localConfiguration, preferences); - loader.load(); - } else { - String toastText = context.getString(R.string.no_fitting_race_group_found); - Toast.makeText(context, toastText, Toast.LENGTH_SHORT).show(); - } - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -111,8 +78,6 @@ public class PreferenceActivity extends AppCompatActivity { if (bundle.containsKey(EXTRA_SHOW_FRAGMENT_ARGUMENTS)) { Bundle info = bundle.getBundle(PreferenceActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS); if (info != null) { - mIsRedirected = true; - // sharedPreferencesName = info.getString(EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME); String raceGroupName = info.getString(EXTRA_SPECIFIC_REGATTA_NAME); String title = getString(R.string.preference_regatta_specific_title, raceGroupName); getSupportActionBar().setTitle(title); @@ -170,8 +135,4 @@ public class PreferenceActivity extends AppCompatActivity { super.onBackPressed(); } } - - public boolean isRedirected() { - return mIsRedirected; - } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index 703e25f7e92..8304f7d7a71 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -3,50 +3,10 @@ package com.sap.sailing.racecommittee.app.ui.activities; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; - -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.shared.util.AppUtils; -import com.sap.sailing.android.shared.util.BitmapHelper; -import com.sap.sailing.android.shared.util.BroadcastManager; -import com.sap.sailing.android.shared.util.CollectionUtils; -import com.sap.sailing.android.shared.util.ViewHelper; -import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; -import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; -import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.data.DataManager; -import com.sap.sailing.racecommittee.app.data.DataStore; -import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; -import com.sap.sailing.racecommittee.app.data.clients.LoadClient; -import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.logging.LogEvent; -import com.sap.sailing.racecommittee.app.services.RaceStateService; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeHeader; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace; -import com.sap.sailing.racecommittee.app.ui.fragments.RaceFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.RaceInfoFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.RaceListCallbacks; -import com.sap.sailing.racecommittee.app.ui.fragments.WelcomeFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.BaseFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceFinishingFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceFlagViewerFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceSummaryFragment; -import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.TrackingListFragment; -import com.sap.sailing.racecommittee.app.ui.views.PanelButton; -import com.sap.sailing.racecommittee.app.utils.RaceHelper; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; +import java.util.Set; import android.annotation.TargetApi; import android.app.Fragment; @@ -81,6 +41,54 @@ import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.Toast; +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.shared.util.AppUtils; +import com.sap.sailing.android.shared.util.BitmapHelper; +import com.sap.sailing.android.shared.util.BroadcastManager; +import com.sap.sailing.android.shared.util.CollectionUtils; +import com.sap.sailing.android.shared.util.ViewHelper; +import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; +import com.sap.sailing.domain.base.racegroup.RaceGroup; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesFleet; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.racecommittee.app.AppConstants; +import com.sap.sailing.racecommittee.app.AppPreferences; +import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.data.DataManager; +import com.sap.sailing.racecommittee.app.data.DataStore; +import com.sap.sailing.racecommittee.app.data.ReadonlyDataManager; +import com.sap.sailing.racecommittee.app.data.clients.LoadClient; +import com.sap.sailing.racecommittee.app.data.loaders.DataLoaderResult; +import com.sap.sailing.racecommittee.app.domain.ManagedRace; +import com.sap.sailing.racecommittee.app.domain.configuration.impl.PreferencesRegattaConfigurationLoader; +import com.sap.sailing.racecommittee.app.logging.LogEvent; +import com.sap.sailing.racecommittee.app.services.RaceStateService; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeHeader; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace; +import com.sap.sailing.racecommittee.app.ui.fragments.RaceFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.RaceInfoFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.RaceListFragment.RaceListCallbacks; +import com.sap.sailing.racecommittee.app.ui.fragments.WelcomeFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.BaseFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceFinishingFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceFlagViewerFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.RaceSummaryFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.TrackingListFragment; +import com.sap.sailing.racecommittee.app.ui.views.PanelButton; +import com.sap.sailing.racecommittee.app.utils.PreferenceHelper; +import com.sap.sailing.racecommittee.app.utils.RaceHelper; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + public class RacingActivity extends SessionActivity implements RaceListCallbacks { private static final String TAG = RacingActivity.class.getName(); private static final String WIND = "wind"; @@ -280,6 +288,7 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks } private void loadWelcomeFragment() { + preferences = AppPreferences.on(this); getFragmentManager().beginTransaction().replace(R.id.racing_view_container, WelcomeFragment.newInstance()).commit(); } @@ -313,6 +322,7 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks public void onRaceItemClicked(ManagedRace managedRace, boolean forcedChange) { if (forcedChange || mSelectedRace != managedRace) { + preferences = AppPreferences.on(this, PreferenceHelper.getRegattaPrefFileName(managedRace.getRaceGroup().getName())); mSelectedRace = managedRace; infoFragment = new RaceInfoFragment(); infoFragment.setArguments(RaceFragment.createArguments(managedRace)); @@ -543,9 +553,8 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks TintImageView overflow = (TintImageView) outViews.get(0); overflow.setMinimumWidth(getResources().getDimensionPixelSize(R.dimen.bigger_over_flow_width)); overflow.setMinimumHeight(getResources().getDimensionPixelSize(R.dimen.bigger_over_flow_height)); - Bitmap bitmap = BitmapHelper - .decodeSampledBitmapFromResource(getResources(), R.drawable.ic_more_vert_white_48dp, - ViewCompat.getMinimumWidth(overflow), ViewCompat.getMinimumHeight(overflow)); + Bitmap bitmap = BitmapHelper.decodeSampledBitmapFromResource(getResources(), R.drawable.ic_more_vert_white_48dp, + ViewCompat.getMinimumWidth(overflow), ViewCompat.getMinimumHeight(overflow)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { overflow.setScaleType(ImageView.ScaleType.FIT_END); } @@ -636,14 +645,14 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks transaction.remove(extra); } } - } + } - if (AppConstants.INTENT_ACTION_SHOW_WELCOME.equals(action)) { - loadWelcomeFragment(); - } + if (AppConstants.INTENT_ACTION_SHOW_WELCOME.equals(action)) { + loadWelcomeFragment(); + } - if (AppConstants.INTENT_ACTION_RELOAD_RACES.equals(action)) { - loadRaces(dataManager.getDataStore().getCourseArea(getCourseAreaIdFromIntent())); + if (AppConstants.INTENT_ACTION_RELOAD_RACES.equals(action)) { + loadRaces(dataManager.getDataStore().getCourseArea(getCourseAreaIdFromIntent())); } transaction.commit(); @@ -653,6 +662,30 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks }).start(); } + private void setupRegattaSpecificConfiguration() { + Set raceGroups = new HashSet<>(); + for (ManagedRace race : lastSeenRaces) { + if (!raceGroups.contains(race.getRaceGroup())) { + raceGroups.add(race.getRaceGroup()); + } + } + + for (RaceGroup raceGroup : raceGroups) { + String regattaPreference = PreferenceHelper.getRegattaPrefFileName(raceGroup.getName()); + + // reset regatta preferences + PreferenceHelper helper = new PreferenceHelper(this, regattaPreference); + helper.clearPreferences(); + + RegattaConfiguration configuration = raceGroup.getRegattaConfiguration(); + if (configuration instanceof RegattaConfigurationImpl) { + AppPreferences preferences = AppPreferences.on(this, regattaPreference, false); + PreferencesRegattaConfigurationLoader preferencesLoader = new PreferencesRegattaConfigurationLoader(configuration, preferences); + preferencesLoader.store(); + } + } + } + private class RaceLoadClient implements LoadClient> { private CourseArea courseArea; @@ -691,6 +724,7 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks lastSeenRaces = data; registerOnService(data); mRaceList.setupOn(data); + setupRegattaSpecificConfiguration(); Toast.makeText(RacingActivity.this, String.format(getString(R.string.racing_load_success), data.size()), Toast.LENGTH_SHORT).show(); } setProgressSpinnerVisibility(false); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/MainPreferenceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/MainPreferenceFragment.java index a855230d09a..58803e4a2dc 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/MainPreferenceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/MainPreferenceFragment.java @@ -33,6 +33,10 @@ import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.DataManager; import com.sap.sailing.racecommittee.app.data.clients.LoadClient; import com.sap.sailing.racecommittee.app.domain.configuration.impl.PreferencesDeviceConfigurationLoader; +import com.sap.sailing.racecommittee.app.ui.fragments.preference.CourseDesignerPreferenceFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.preference.GeneralPreferenceFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.preference.RegattaPreferenceFragment; +import com.sap.sailing.racecommittee.app.ui.fragments.preference.RegattaSpecificPreferenceFragment; import com.sap.sailing.racecommittee.app.ui.views.decoration.PreferenceMarginItemDecoration; public class MainPreferenceFragment extends LoggableFragment { @@ -54,14 +58,10 @@ public class MainPreferenceFragment extends LoggableFragment { RecyclerView recyclerView = ViewHelper.get(layout, R.id.preference_header); if (recyclerView != null) { ArrayList items = new ArrayList<>(); - items - .add(new PreferenceItem(getString(R.string.settings_category_general), R.drawable.ic_more_horiz_yellow_24dp, "com.sap.sailing.racecommittee.app.ui.fragments.preference.GeneralPreferenceFragment")); - items - .add(new PreferenceItem(getString(R.string.settings_category_regatta_defaults), R.drawable.ic_boat_yellow_24dp, "com.sap.sailing.racecommittee.app.ui.fragments.preference.RegattaPreferenceFragment")); - items - .add(new PreferenceItem(getString(R.string.settings_category_regattas), R.drawable.ic_boat_yellow_24dp, "com.sap.sailing.racecommittee.app.ui.fragments.preference.RegattaSpecificPreferenceFragment")); - items - .add(new PreferenceItem(getString(R.string.settings_category_course_designer), R.drawable.ic_pin_drop_yellow_24dp, "com.sap.sailing.racecommittee.app.ui.fragments.preference.CourseDesignerPreferenceFragment")); + items.add(new PreferenceItem(getString(R.string.settings_category_general), R.drawable.ic_more_horiz_yellow_24dp, GeneralPreferenceFragment.class.getName())); + items.add(new PreferenceItem(getString(R.string.settings_category_regatta_defaults), R.drawable.ic_boat_yellow_24dp, RegattaPreferenceFragment.class.getName())); + items.add(new PreferenceItem(getString(R.string.settings_category_regattas), R.drawable.ic_boat_yellow_24dp, RegattaSpecificPreferenceFragment.class.getName())); + items.add(new PreferenceItem(getString(R.string.settings_category_course_designer), R.drawable.ic_pin_drop_yellow_24dp, CourseDesignerPreferenceFragment.class.getName())); items.add(new PreferenceItem(getString(R.string.reload_config), R.drawable.ic_autorenew_yellow_24dp, new Runnable() { @Override public void run() { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceFragment.java index 8e2a968917a..76276e5d702 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceFragment.java @@ -1,5 +1,9 @@ package com.sap.sailing.racecommittee.app.ui.fragments; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; + import android.app.Activity; import android.content.Intent; import android.graphics.drawable.Drawable; @@ -20,6 +24,7 @@ import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.OnlineDataManager; import com.sap.sailing.racecommittee.app.domain.ManagedRace; +import com.sap.sailing.racecommittee.app.ui.activities.BaseActivity; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; import com.sap.sailing.racecommittee.app.utils.TickListener; import com.sap.sailing.racecommittee.app.utils.TickSingleton; @@ -27,10 +32,6 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; - public abstract class RaceFragment extends LoggableFragment implements TickListener { @IntDef({MOVE_DOWN, MOVE_NONE, MOVE_UP}) @@ -102,7 +103,12 @@ public abstract class RaceFragment extends LoggableFragment implements TickListe public void onAttach(Activity activity) { super.onAttach(activity); - preferences = AppPreferences.on(activity); + BaseActivity baseActivity = (BaseActivity) activity; + if (baseActivity != null) { + preferences = baseActivity.getPreferences(); + } else { + preferences = AppPreferences.on(activity); + } } @Override diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/MorePanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/MorePanelFragment.java index 9ab9e4bd40b..815ec668222 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/MorePanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/MorePanelFragment.java @@ -150,7 +150,7 @@ public class MorePanelFragment extends BasePanelFragment { if (mGateTiming != null && mGateTimingValue != null) { mGateTiming.setVisibility(View.VISIBLE); - mGateTimingValue.setText(RaceHelper.getGateTiming(getActivity(), typedProcedure)); + mGateTimingValue.setText(RaceHelper.getGateTiming(getActivity(), typedProcedure, getRace().getRaceGroup())); } } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/SetupPanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/SetupPanelFragment.java index a2f7f97ca6f..e9c2e914349 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/SetupPanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/SetupPanelFragment.java @@ -190,7 +190,7 @@ public class SetupPanelFragment extends BasePanelFragment { mButtonPathfinder.setVisibility(View.VISIBLE); } if (mButtonTiming != null) { - mButtonTiming.setPanelText(RaceHelper.getGateTiming(getActivity(), typedProcedure)); + mButtonTiming.setPanelText(RaceHelper.getGateTiming(getActivity(), typedProcedure, getRace().getRaceGroup())); mButtonTiming.setVisibility(View.VISIBLE); } } @@ -589,7 +589,7 @@ public class SetupPanelFragment extends BasePanelFragment { break; case LEVEL_TOGGLED: - replaceFragment(CourseFragment.newInstance(BaseFragment.START_MODE_PLANNED, getRace())); + replaceFragment(CourseFragment.newInstance(BaseFragment.START_MODE_PLANNED, preferences)); break; default: diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java index 0e372e75830..78b8596c2ef 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java @@ -28,6 +28,7 @@ import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.DataManager; import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.ui.fragments.raceinfo.StartTimeFragment; +import com.sap.sailing.racecommittee.app.ui.layouts.TimePanelHeaderLayout; import com.sap.sailing.racecommittee.app.utils.RaceHelper; import com.sap.sailing.racecommittee.app.utils.TickSingleton; import com.sap.sailing.racecommittee.app.utils.TimeUtils; @@ -43,7 +44,7 @@ public class TimePanelFragment extends BasePanelFragment { private IntentReceiver mReceiver; private SimpleDateFormat dateFormat; - private View mRaceHeader; + private TimePanelHeaderLayout mRaceHeader; private View mTimeLock; private TextView mCurrentTime; private TextView mHeaderTime; @@ -69,9 +70,12 @@ public class TimePanelFragment extends BasePanelFragment { mStateListener = new RaceStateChangedListener(); mRaceHeader = ViewHelper.get(layout, R.id.race_content_header); - if (mRaceHeader != null) { - mRaceHeader.setOnClickListener(new RaceHeaderClick()); - } + mRaceHeader.setRunnable(new Runnable() { + @Override + public void run() { + new RaceHeaderClick().onClick(null); + } + }); mTimeLock = ViewHelper.get(layout, R.id.time_start_lock); mCurrentTime = ViewHelper.get(layout, R.id.current_time); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/GeneralPreferenceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/GeneralPreferenceFragment.java index 687113585c6..c4829ece3d1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/GeneralPreferenceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/GeneralPreferenceFragment.java @@ -1,13 +1,5 @@ package com.sap.sailing.racecommittee.app.ui.fragments.preference; -import com.sap.sailing.android.shared.ui.fragments.preference.BasePreferenceFragment; -import com.sap.sailing.android.shared.ui.views.EditSetPreference; -import com.sap.sailing.racecommittee.app.AppPreferences; -import com.sap.sailing.racecommittee.app.BuildConfig; -import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.utils.QRHelper; -import com.sap.sailing.racecommittee.app.utils.autoupdate.AutoUpdater; - import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -22,6 +14,14 @@ import android.preference.PreferenceScreen; import android.support.v7.app.AlertDialog; import android.widget.Toast; +import com.sap.sailing.android.shared.ui.fragments.preference.BasePreferenceFragment; +import com.sap.sailing.android.shared.ui.views.EditSetPreference; +import com.sap.sailing.racecommittee.app.AppPreferences; +import com.sap.sailing.racecommittee.app.BuildConfig; +import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.utils.QRHelper; +import com.sap.sailing.racecommittee.app.utils.autoupdate.AutoUpdater; + public class GeneralPreferenceFragment extends BasePreferenceFragment { private static int requestCodeQRCode = 45392; @@ -183,9 +183,7 @@ public class GeneralPreferenceFragment extends BasePreferenceFragment { if (resultCode == Activity.RESULT_OK) { QRHelper.with(getActivity()).saveData(data.getStringExtra("SCAN_RESULT")); - - AppPreferences.on(getActivity()).setNeedConfigRefresh(true); - + AppPreferences.on(getActivity()).setNeedConfigRefresh(true); } else { Toast.makeText(getActivity(), getString(R.string.error_scanning_qr, resultCode), Toast.LENGTH_LONG).show(); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/RegattaPreferenceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/RegattaPreferenceFragment.java index 46e1aa018b5..11418e024e1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/RegattaPreferenceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/preference/RegattaPreferenceFragment.java @@ -3,13 +3,12 @@ package com.sap.sailing.racecommittee.app.ui.fragments.preference; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Set; -import android.app.Activity; import android.os.Bundle; import android.preference.ListPreference; import android.preference.MultiSelectListPreference; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; +import android.text.TextUtils; import com.sap.sailing.android.shared.ui.fragments.preference.BasePreferenceFragment; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; @@ -21,7 +20,7 @@ import com.sap.sailing.racecommittee.app.ui.activities.PreferenceActivity; /** *

            - * This preference fragment can be redirected to display the {@link RegattaConfiguration} of a specifc regatta. + * This preference fragment can be redirected to display the {@link RegattaConfiguration} of a specific regatta. *

            *

            *

            @@ -29,56 +28,28 @@ import com.sap.sailing.racecommittee.app.ui.activities.PreferenceActivity; */ public class RegattaPreferenceFragment extends BasePreferenceFragment { - private boolean isRedirected = false; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Activity activity = getActivity(); - if (activity instanceof PreferenceActivity) { - PreferenceActivity settingsActivity = (PreferenceActivity) activity; - isRedirected = settingsActivity.isRedirected(); - } - if (isRedirected) { - addPreferencesFromResource(R.xml.preference_regatta_specific); + if (getArguments() != null && !TextUtils.isEmpty(getArguments().getString(PreferenceActivity.EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME))) { + getPreferenceManager().setSharedPreferencesName(getArguments().getString(PreferenceActivity.EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME)); } addPreferencesFromResource(R.xml.preference_regatta_defaults); - if (isRedirected) { - setupSaveButton(); - } - setupGeneral(); - setupRRS26(); - setupGateStart(); - setupESS(); - setupBasic(); - setupLeague(); - bindPreferenceSummaryToValue(findPreference(R.string.preference_racing_procedure_rrs26_classflag_key)); bindPreferenceSummaryToSet(findPreference(R.string.preference_racing_procedure_rrs26_startmode_flags_key)); bindPreferenceSummaryToValue(findPreference(R.string.preference_racing_procedure_gatestart_classflag_key)); bindPreferenceSummaryToValue(findPreference(R.string.preference_racing_procedure_ess_classflag_key)); bindPreferenceSummaryToValue(findPreference(R.string.preference_racing_procedure_basic_classflag_key)); bindPreferenceSummaryToValue(findPreference(R.string.preference_racing_procedure_league_classflag_key)); - } - private void setupSaveButton() { - final String preferencesName = getArguments().getString(PreferenceActivity.EXTRA_SPECIFIC_REGATTA_PREFERENCES_NAME); - final String raceGroupName = getArguments().getString(PreferenceActivity.EXTRA_SPECIFIC_REGATTA_NAME); - - Preference preference = findPreference(R.string.preference_regatta_specific_save_key); - - preference.setTitle(getString(R.string.preference_regatta_specific_save_title, raceGroupName)); - preference.setSummary(getString(R.string.preference_regatta_specific_save_description, raceGroupName)); - preference.setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - PreferenceActivity.commitSpecificRegattaConfiguration(getActivity(), preferencesName, raceGroupName); - getActivity().finish(); - return false; - } - }); + setupGeneral(); + setupRRS26(); + setupGateStart(); + setupESS(); + setupBasic(); + setupLeague(); } private void setupRRS26() { @@ -138,6 +109,8 @@ public class RegattaPreferenceFragment extends BasePreferenceFragment { preference.setEntryValues(entryValues.toArray(new String[entryValues.size()])); bindPreferenceSummaryToValue(preference); + + preference.setSummary(CourseDesignerMode.valueOf(preference.getValue()).toString()); } private void setupDependentRacesPreference() { @@ -154,6 +127,11 @@ public class RegattaPreferenceFragment extends BasePreferenceFragment { Collections.sort(flags); preference.setEntries(flags.toArray(new String[flags.size()])); preference.setEntryValues(flags.toArray(new String[flags.size()])); + + Set values = preference.getValues(); + List checkedFlags = new ArrayList<>(values); + Collections.sort(checkedFlags); + preference.setSummary(checkedFlags.toString()); } private void setupClassFlagList(ListPreference preference) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/CourseFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/CourseFragment.java index 9b0e0223030..0926170cd80 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/CourseFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/CourseFragment.java @@ -6,15 +6,15 @@ import android.view.View; import com.sap.sailing.android.shared.util.AppUtils; import com.sap.sailing.domain.common.CourseDesignerMode; import com.sap.sailing.racecommittee.app.AppConstants; +import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; import com.sap.sailing.racecommittee.app.ui.fragments.RaceFragment; import com.sap.sailing.racecommittee.app.ui.layouts.HeaderLayout; public abstract class CourseFragment extends BaseFragment { - public static RaceFragment newInstance(@START_MODE_VALUES int startMode, ManagedRace race) { - CourseDesignerMode mode = race.getState().getConfiguration().getDefaultCourseDesignerMode(); + public static RaceFragment newInstance(@START_MODE_VALUES int startMode, AppPreferences preferences) { + CourseDesignerMode mode = preferences.getDefaultCourseDesignerMode(); RaceFragment fragment; switch (mode) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/GateStartTimingFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/GateStartTimingFragment.java index 23acda124bf..f86d22a30d8 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/GateStartTimingFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/GateStartTimingFragment.java @@ -11,7 +11,6 @@ import com.sap.sailing.android.shared.util.ActivityHelper; import com.sap.sailing.android.shared.util.ViewHelper; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.GateStartRacingProcedure; import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.ui.layouts.HeaderLayout; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; @@ -57,7 +56,7 @@ public class GateStartTimingFragment extends BaseFragment { View additional = ViewHelper.get(layout, R.id.addition_golf_time); if (additional != null) { - if (AppPreferences.on(getActivity()).getGateStartHasAdditionalGolfDownTime()) { + if (preferences.getGateStartHasAdditionalGolfDownTime()) { additional.setVisibility(View.VISIBLE); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java index 7e7e2196123..aad252bdc5f 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java @@ -124,7 +124,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi Runnable runnable = new Runnable() { @Override public void run() { - openFragment(CourseFragment.newInstance(START_MODE_PRESETUP, getRace())); + openFragment(CourseFragment.newInstance(START_MODE_PRESETUP, preferences)); } }; SelectionItem courseItem = new SelectionItem(getString(R.string.course), null, null, false, false, runnable); @@ -185,7 +185,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi openFragment(GateStartTimingFragment.newInstance(START_MODE_PRESETUP)); } }; - mItems.add(new SelectionItem(getString(R.string.gate_start_timing), RaceHelper.getGateTiming(getActivity(), procedure), null, false, false, runnableTiming)); + mItems.add(new SelectionItem(getString(R.string.gate_start_timing), RaceHelper.getGateTiming(getActivity(), procedure, getRace().getRaceGroup()), null, false, false, runnableTiming)); } } else if (RacingProcedureType.ESS.equals(mRacingProcedureType)) { // Extreme Sailing Series diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PhotoListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PhotoListFragment.java index 644133c16fe..36735bdf6fb 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PhotoListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PhotoListFragment.java @@ -26,7 +26,6 @@ import android.widget.Toast; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.util.ViewHelper; import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.ui.adapters.PhotoListAdapter; import com.sap.sailing.racecommittee.app.ui.layouts.HeaderLayout; @@ -96,7 +95,7 @@ public class PhotoListFragment extends BaseFragment { mSubmit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - String recipient = AppPreferences.on(getActivity()).getMailRecipient(); + String recipient = preferences.getMailRecipient(); MailHelper.send(new String[] { recipient }, getSubject(), getBody(), getPhotos(), getActivity()); } }); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java index 897b5191a65..26cbe7c3195 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java @@ -38,7 +38,6 @@ import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.AppPreferences; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.data.DataStore; import com.sap.sailing.racecommittee.app.data.OnlineDataManager; @@ -594,7 +593,7 @@ public class StartTimeFragment extends BaseFragment } int id; - if (AppConstants.LIGHT_THEME.equals(AppPreferences.on(getActivity()).getTheme())) { + if (AppConstants.LIGHT_THEME.equals(preferences.getTheme())) { id = R.drawable.nav_drawer_tab_button_light; } else { id = R.drawable.nav_drawer_tab_button_dark; diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/TimePanelHeaderLayout.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/TimePanelHeaderLayout.java new file mode 100644 index 00000000000..acfd1905c6e --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/TimePanelHeaderLayout.java @@ -0,0 +1,42 @@ +package com.sap.sailing.racecommittee.app.ui.layouts; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.RelativeLayout; + +import com.sap.sailing.racecommittee.app.ui.utils.TouchEventListener; + +public class TimePanelHeaderLayout extends RelativeLayout { + + private TouchEventListener mTouchEventListener; + + public TimePanelHeaderLayout(Context context) { + this(context, null); + } + + public TimePanelHeaderLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TimePanelHeaderLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + mTouchEventListener = new TouchEventListener(this); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mTouchEventListener != null) { + return mTouchEventListener.onTouchEvent(event); + } else { + return super.onTouchEvent(event); + } + } + + public void setRunnable(Runnable clickRunnable) { + if (mTouchEventListener != null) { + mTouchEventListener.setClickRunnable(clickRunnable); + } + } +} diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/utils/TouchEventListener.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/utils/TouchEventListener.java new file mode 100644 index 00000000000..2f1c2a18c62 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/utils/TouchEventListener.java @@ -0,0 +1,83 @@ +package com.sap.sailing.racecommittee.app.ui.utils; + +import java.lang.ref.WeakReference; + +import android.support.v4.view.ViewConfigurationCompat; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; + +public class TouchEventListener { + + private WeakReference mView; + private Runnable mClickRunnable; + private float mStartX; + private float mStartY; + private boolean mInXRange; + private boolean mInYRange; + private int mTouchSlop; + private boolean mEnabled; + + public TouchEventListener(View view) { + mView = new WeakReference<>(view); + + final ViewConfiguration configuration = ViewConfiguration.get(view.getContext()); + mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); + + mEnabled = true; + } + + public boolean onTouchEvent(MotionEvent event) { + int action = event.getActionMasked(); + View view = mView.get(); + + if (view == null) { + return false; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mInXRange = true; + mInYRange = true; + view.getParent().requestDisallowInterceptTouchEvent(true); + mStartX = event.getX(); + mStartY = event.getY(); + return true; + + case MotionEvent.ACTION_MOVE: + boolean inLimit = false; + if (mInXRange) { + inLimit = Math.abs(event.getX() - mStartX) <= mTouchSlop; + mInXRange = inLimit; + } else { + view.getParent().requestDisallowInterceptTouchEvent(false); + } + if (mInYRange) { + mInYRange = Math.abs(event.getY() - mStartY) <= mTouchSlop; + } + return inLimit; + + case MotionEvent.ACTION_UP: + view.getParent().requestDisallowInterceptTouchEvent(false); + if (mInXRange && mEnabled) { + if (mClickRunnable != null) { + mClickRunnable.run(); + return true; + } + } + break; + + default: + break; + } + return false; + } + + public void setClickRunnable(Runnable runnable) { + mClickRunnable = runnable; + } + + public void setEnabled(boolean enabled) { + mEnabled = enabled; + } +} diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/PanelButton.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/PanelButton.java index 825badf881e..75a5cbe05b1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/PanelButton.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/PanelButton.java @@ -8,6 +8,7 @@ import android.os.Handler; import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -15,9 +16,10 @@ import android.widget.Switch; import android.widget.TextView; import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.ui.utils.TouchEventListener; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; -public class PanelButton extends FrameLayout implements View.OnClickListener, DialogInterface.OnClickListener { +public class PanelButton extends FrameLayout implements DialogInterface.OnClickListener { public final static int LEVEL_UNKNOWN = -1; public final static int LEVEL_NORMAL = 0; @@ -38,6 +40,7 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di private int mType; private PanelButtonClick mListener; + private TouchEventListener mTouchEventListener; public PanelButton(Context context) { this(context, null); @@ -52,6 +55,31 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PanelButton); + Runnable clickRunnable = new Runnable() { + @Override + public void run() { + if (mLock != null && mLock.getVisibility() == VISIBLE && isNormal()) { + if (mType == 2 && mSwitch != null) { + mSwitch.setChecked(!mSwitch.isChecked()); + } + showChangeDialog(PanelButton.this); + } else { + if (mType == 2) { + if (mListener != null && mSwitch != null) { + mListener.onChangedSwitch(PanelButton.this, mSwitch.isChecked()); + } + } else { + if (mListener != null) { + mListener.onClick(PanelButton.this); + } + } + } + } + }; + + mTouchEventListener = new TouchEventListener(this); + mTouchEventListener.setClickRunnable(clickRunnable); + setPanelType(a.getInt(R.styleable.PanelButton_buttonType, 0)); switch (mType) { @@ -73,9 +101,6 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di mSwitch = (Switch) findViewById(R.id.panel_switch); setPanelSwitch(a.getString(R.styleable.PanelButton_buttonSwitch)); - if (mSwitch != null) { - mSwitch.setOnClickListener(this); - } mLock = findViewById(R.id.panel_lock); setLock(a.getBoolean(R.styleable.PanelButton_showLock, false)); @@ -119,11 +144,11 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di mType = type; switch (type) { case 2: - setOnClickListener(null); + mTouchEventListener.setEnabled(false); break; default: - setOnClickListener(this); + mTouchEventListener.setEnabled(true); } } @@ -232,26 +257,6 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di builder.show(); } - @Override - public void onClick(View v) { - if (mLock != null && mLock.getVisibility() == VISIBLE && isNormal()) { - if (mType == 2 && mSwitch != null) { - mSwitch.setChecked(!mSwitch.isChecked()); - } - showChangeDialog(this); - } else { - if (mType == 2) { - if (mListener != null && mSwitch != null) { - mListener.onChangedSwitch(this, mSwitch.isChecked()); - } - } else { - if (mListener != null) { - mListener.onClick(this); - } - } - } - } - @Override public void onClick(DialogInterface dialog, int which) { if (mType == 2) { @@ -333,6 +338,11 @@ public class PanelButton extends FrameLayout implements View.OnClickListener, Di }, 200); } + @Override + public boolean onTouchEvent(MotionEvent event) { + return mTouchEventListener.onTouchEvent(event); + } + public interface PanelButtonClick { void onClick(PanelButton view); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/PreferenceHelper.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/PreferenceHelper.java index b3ad6cbf543..60802910a0c 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/PreferenceHelper.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/PreferenceHelper.java @@ -2,10 +2,12 @@ package com.sap.sailing.racecommittee.app.utils; import android.content.Context; import android.content.SharedPreferences; +import android.net.Uri; import android.preference.PreferenceManager; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.ui.activities.PreferenceActivity; /** * Helps you with maintaining preferences over different app builds @@ -94,4 +96,9 @@ public class PreferenceHelper { private SharedPreferences getSharedPreferences(String preferenceName) { return context.getSharedPreferences(preferenceName, Context.MODE_PRIVATE); } + + public static String getRegattaPrefFileName(String regattaName) { + String fileName = PreferenceActivity.SPECIFIC_REGATTA_PREFERENCES_NAME + regattaName; + return Uri.encode(fileName); + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java index 5a5408834f7..63f8e2d7936 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/RaceHelper.java @@ -177,11 +177,11 @@ public class RaceHelper { return fleetName; } - public static String getGateTiming(Context context, GateStartRacingProcedure procedure) { + public static String getGateTiming(Context context, GateStartRacingProcedure procedure, RaceGroup raceGroup) { String timing; long launchTime = procedure.getGateLaunchStopTime() / GateStartTimingFragment.ONE_MINUTE_MILLISECONDS; long golfTime = procedure.getGolfDownTime() / GateStartTimingFragment.ONE_MINUTE_MILLISECONDS; - if (AppPreferences.on(context).getGateStartHasAdditionalGolfDownTime()) { + if (AppPreferences.on(context, PreferenceHelper.getRegattaPrefFileName(raceGroup.getName())).getGateStartHasAdditionalGolfDownTime()) { timing = context.getString(R.string.gate_time_schedule_long, launchTime, golfTime, launchTime + golfTime); } else { timing = context.getString(R.string.gate_time_schedule_short, launchTime); diff --git a/mobile/google-android-support-v7-appcompat_lib/.gitignore b/mobile/google-android-support-v7-appcompat_lib/.gitignore index 8eb3aaa5c69..01a7f28028e 100644 --- a/mobile/google-android-support-v7-appcompat_lib/.gitignore +++ b/mobile/google-android-support-v7-appcompat_lib/.gitignore @@ -2,4 +2,3 @@ bin gen .gradle build -.settings diff --git a/mobile/google-android-support-v7-appcompat_lib/.settings/org.eclipse.jdt.core.prefs b/mobile/google-android-support-v7-appcompat_lib/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..b58414c5004 --- /dev/null +++ b/mobile/google-android-support-v7-appcompat_lib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,101 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=ignore +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=ignore +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=ignore +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/mobile/google-android-support-v7-recyclerview_lib/.gitignore b/mobile/google-android-support-v7-recyclerview_lib/.gitignore index 8eb3aaa5c69..01a7f28028e 100644 --- a/mobile/google-android-support-v7-recyclerview_lib/.gitignore +++ b/mobile/google-android-support-v7-recyclerview_lib/.gitignore @@ -2,4 +2,3 @@ bin gen .gradle build -.settings diff --git a/mobile/google-android-support-v7-recyclerview_lib/.settings/org.eclipse.jdt.core.prefs b/mobile/google-android-support-v7-recyclerview_lib/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..26906d94283 --- /dev/null +++ b/mobile/google-android-support-v7-recyclerview_lib/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,101 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=ignore +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=ignore +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=ignore +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=ignore +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=ignore +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=ignore +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/translation.json b/translation.json index 96d77989b38..f93605340e1 100644 --- a/translation.json +++ b/translation.json @@ -12,7 +12,7 @@ "targetFolderPath":".", "pseudoLocFolderPath":"." }, - { + { "sourceFolderPath":"java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/i18n", "sourceFilters":[ "TextMessages*.properties" @@ -37,6 +37,20 @@ } ] }, + { + "collectionName":"Polars", + "folders":[ + { + "sourceFolderPath":"java/com.sap.sailing.polars.datamining/resources/stringmessages", + "sourceFilters":[ + "Polars_StringMessages*.properties" + ], + "xlfFolderPath":"./XLF", + "targetFolderPath":".", + "pseudoLocFolderPath":"." + } + ] + }, { "collectionName":"DataMining", "folders":[ @@ -48,11 +62,20 @@ "xlfFolderPath":"./XLF", "targetFolderPath":".", "pseudoLocFolderPath":"." + }, + { + "sourceFolderPath":"java/com.sap.sailing.datamining/resources/stringmessages", + "sourceFilters":[ + "Sailing_StringMessages*.properties" + ], + "xlfFolderPath":"./XLF", + "targetFolderPath":".", + "pseudoLocFolderPath":"." } ] }, { - "collectionName":"SseGwtUi", + "collectionName":"FileStorage", "folders":[ { "sourceFolderPath":"java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/filestorage", @@ -62,10 +85,19 @@ "xlfFolderPath":"./XLF", "targetFolderPath":".", "pseudoLocFolderPath":"." + }, + { + "sourceFolderPath":"java/com.sap.sse.filestorage/resources/stringmessages", + "sourceFilters":[ + "FileStorageStringMessages*.properties" + ], + "xlfFolderPath":"./XLF", + "targetFolderPath":".", + "pseudoLocFolderPath":"." } ] }, - { + { "collectionName":"SecurityGwtUi", "folders":[ { diff --git a/wiki/building-and-deploying.md b/wiki/building-and-deploying.md index 05ac47bc908..1ee2b68aebc 100644 --- a/wiki/building-and-deploying.md +++ b/wiki/building-and-deploying.md @@ -105,7 +105,7 @@ The Android release branch holds the changes required to run a successful Androi We have various branches on which Android mobile app features are being developed. Ultimately, they should be merged into the ``master`` branch from where they get merged into the Android release branch. A so-called "Customer release" build can be triggered for the Android release branch on a [central Jenkins instance](https://xmake-ldi.wdf.sap.corp:8443/view/SAPSail/job/sapsailingcapture-Release/). However, it is important to know that the xMake build profile will make sure that the version specified in the ``pom.xml`` descriptors matches in its major and minor version with a ``rel-x.y`` branch name in the central git repository at ``git.wdf.sap.corp``. For example, if the ``pom.xml`` files specify a ``1.4.0`` version, the commit to be built needs to be contained in a branch called ``rel-1.4``. -Also note that once released to Nexus, the next release build will have to use at least an incremented micro version, such as 1.4.1. In order to change all versions in all `MANIFEST.MF` and `pom.xml` files consistently, the Tycho `set-version` plugin can be used. For this, first comment out the `` definition in the top-level `pom.xml` file which points to `com.sap.ldi/ldi-parent/8.0.0` or else you will get error messages that this component cannot be found (it is only found when the build is run on a central Jenkins instance that sees the Nexus repository at the right place). Then, issue the following command, setting the version number as required: +Also note that once released to Nexus, the next release build will have to use at least an incremented micro version, such as 1.4.1. In order to change all versions in all `MANIFEST.MF` and `pom.xml` files consistently, the Tycho `set-version` plugin can be used. For this, the `` definition in the top-level `pom.xml` file needs to be commented out, which points to `com.sap.ldi/ldi-parent/8.0.0` or else you will get error messages that this component cannot be found (it is only found when the build is run on a central Jenkins instance that sees the Nexus repository at the right place). Then, you would have to issue the following command, setting the version number as required: ``` mvn -Dtycho.mode=maven org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=x.y.z @@ -117,6 +117,14 @@ Next, make sure to upgrade the versions in the `AndroidManifest.xml` files of th In order to get the Android release branch merged into the ``rel-x.y`` branch, the same process as explained above for the merge of iOS branches into ``fa/rel-x.y`` is required. In particular, this means that a Java Correction Workbench Correction Request [needs to be created](https://css.wdf.sap.corp/sap/bc/bsp/spn/jcwb/default.htm?newCMForProject=sapsailingcapture&newCMComponentName=SV-COE-MSO-CDP) and the latest commit to be pushed to ``refs/for/rel-x.y`` needs to be amended so that it contains a valid ``Change-Id:`` and ``CR-Id:`` line. Then, the commit needs to be voted on. Other than for the MiOS branches under ``fa/rel-x.y`` it seems to be required that _another_ committer votes with +2 to enable a merge. Once this has been done, the blue ``Submit`` button will appear on your change page (something like [https://git.wdf.sap.corp/#/c/1436212/](https://git.wdf.sap.corp/#/c/1436212/)) that you can use to technically get the commit merged into the ``rel-x.y`` branch. +Many of the above steps can be automated by using the script ``configuration/releaseAndroidApps.sh``. It accepts the Java Correction Workbench-issued Correction Request ID tag, ready for appending to the git commit message. Call like this: + +``` + ./configuration/releaseAndroidApps.sh CR-Id: 012003146900000486712016 +``` + +This will checkout the `android-xmake-release` branch, merge the `rel-1.4` release branch into it, push the result again so Gerrit knows this commit / change already. Then, the version substitutions for all `pom.xml` and `MANIFEST.MF` files are carried out automatically by looking at the version currently declared in the root `pom.xml`. If that's a `-SNAPSHOT` version, only the `-SNAPSHOT` suffix is removed to obtain the new release version number. If no `-SNAPSHOT` suffix is found, the micro version is increased by one (e.g., 1.4.18 --> 1.4.19). Furthermore, the `android:versionCode` number is increased by one for each of the three apps in their `AndroidManifest.xml`, and the `android:versionName` which is assumed to have the format major.minor will have the minor version increased by one. The result is committed to git, the commit is amended using the `CR-Id` tag passed on the command line, and the resulting amended commit is pushed to the `refs/for/rel-1.4` branch for Gerrit review and approval. + Once this is done, a [Customer release build can be started](https://xmake-ldi.wdf.sap.corp:8443/view/SAPSail/job/sapsailingcapture-Release/). Select ``customer`` as the ``RELEASE_MODE`` and enter the ``rel-x.y`` branch name in the "TREEISH" field, make sure the Maven version 3.1.1 is selected and click the "Build" button. If the build succeeds, Final Assembly should be able to _promote_ the build artifact, such as the ``.apk`` files which should have been signed using the SAP certificate. If the build fails due to missing Nexus artifacts, check out the document describing [how to upload artifacts to Nexus](https://wiki.wdf.sap.corp/wiki/display/LeanDI/Uploading+Third+Party+Artifacts+to+Nexus#UploadingThirdPartyArtifactstoNexus-1.CreateaJiraTicket). You will have to create a JIRA ticked in this process and upload the artifact and its ``pom.xml`` file to Nexus. Usually, these requests get handled within less than 48 hours. Good luck... \ No newline at end of file diff --git a/wiki/onboarding.md b/wiki/onboarding.md index 8f91de47037..93aaa0f5141 100644 --- a/wiki/onboarding.md +++ b/wiki/onboarding.md @@ -52,6 +52,7 @@ First of all, make sure you've looked at http://www.amazon.de/Patterns-Elements- * Make absolutely sure to import CodeFormatter.xml (from $GIT_HOME/java) into your Eclipse preferences (Preferences->Java->Code Style->Fortmatter) * Install the Eclipse GWT-Plugin (now called Google Plugin for Eclipse) * Install the Google Android SDK from the same Google Plugin for Eclipse update site + * In Eclipse go to "Window->Preferences->Java->Build Path->Classpath Variables" and create a new classpath variable called ``ANDROID_HOME``. Set its value to the install location of your Android SDK, e.g., ``c:\apps\android-sdk-windows`` or ``/usr/local/android-sdk-linux``. * Install GWT SDK and add the SDK in Eclipse (Preferences -> Google -> Web Toolkit -> Add...) * Install Eclipse debugger for GWT SuperDevMode * Install Eclipse eGit (optional)