Merge branch 'master' into translation

This commit is contained in:
Axel Uhl committed 2016-09-05 16:19:45 +02:00
commit 987f17ea01
404 files changed
+4753 -3204

No files matched your search

@@ -54,7 +54,8 @@ public class BarbadosResultSpreadsheet {
String crew = row.getCell(4).getStringCellValue();
Iterable<String> names = Arrays.asList(new String[] { helm, crew });
double scoreAfterDiscarding = row.getCell(columnNumberOfNetScore).getNumericCellValue();
double totalPointsBeforeDiscarding = row.getCell(columnNumberOfTotalScore).getNumericCellValue();
final Cell cellValue = row.getCell(columnNumberOfTotalScore);
double totalPointsBeforeDiscarding = cellValue == null ? null : cellValue.getNumericCellValue();
List<CompetitorEntry> rankAndMaxPointsReasonAndPointsAndDiscarded = new ArrayList<>();
for (int raceNumber=0; raceNumber<numberOfRaces; raceNumber++) {
final Cell rankOrMaxPointReasonCell = row.getCell(COLUMN_NUMBER_OF_FIRST_RACE_RANK+raceNumber);
@@ -68,7 +69,8 @@ public class BarbadosResultSpreadsheet {
rank = null;
maxPointsReason = rankOrMaxPointReasonCell.getStringCellValue();
}
double score = row.getCell(columnNumberOfFirstRaceScore+raceNumber).getNumericCellValue();
final Cell scoreCellContents = row.getCell(columnNumberOfFirstRaceScore+raceNumber);
double score = scoreCellContents == null ? 0.0 : scoreCellContents.getNumericCellValue();
if (score != 0.0) {
CompetitorEntry entry = new DefaultCompetitorEntryImpl(rank,
maxPointsReason, score, /* discarded */ false);
+12 -12
View File
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-ajaxloader.jar"/>
<classpathentry kind="lib" path="WEB-INF/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="com.google.gwt.eclipse.core.GWT_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar"/>
<classpathentry exported="true" kind="lib" path="WEB-INF/lib/gwt-ajaxloader.jar"/>
<classpathentry kind="lib" path="WEB-INF/classes"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -1,12 +1,12 @@
eclipse.preferences.version=1
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.8
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
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.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8
org.eclipse.jdt.core.compiler.source=1.7
@@ -5,7 +5,7 @@ Bundle-SymbolicName: com.sap.sailing.dashboards.gwt
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: com.sap.sailing.dashboards.gwt.server.Activator
Bundle-Vendor: SAP
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Require-Bundle: com.sap.sailing.domain,
com.sap.sailing.server,
com.google.gwt.osgi;bundle-version="2.6.1",
@@ -3,7 +3,8 @@
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<stringAttribute key="com.google.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9874"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9874"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
@@ -41,7 +42,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9874 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
</launchConfiguration>
@@ -3,7 +3,7 @@
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT_AUTO" value="true"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
@@ -1,7 +1,7 @@
package com.sap.sailing.dashboards.gwt.client.linker;
import com.google.gwt.core.ext.linker.Shardable;
import com.sap.sse.gwt.client.linker.ManifestLinker;
import com.sap.sse.gwt.linker.ManifestLinker;
/**
* @author Alexander Ries (D062114)
@@ -6,7 +6,8 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation.AbstracPreCalculationDataRetriever;
import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation.StartlineAdvantageCalculationData;
import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation.StartlineAdvantageCalculationDataRetriever;
import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchContext;
import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.StartlineAdvantagesWithMaxAndAverageDTO;
@@ -28,8 +29,9 @@ import com.sap.sse.common.Util.Pair;
* @author Alexander Ries (D062114)
*
*/
public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDataRetriever {
public class StartlineAdvantagesByWindCalculator {
private StartlineAdvantageCalculationDataRetriever startlineAdvantageCalculationDataRetriever;
private DashboardDispatchContext dashboardDispatchContext;
private DomainFactory domainFactory;
private DefaultPolarWindAngleBoatSpeedFunction defaultPolarSpeedWindAngleFunction;
@@ -37,30 +39,30 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
private final Logger logger = Logger.getLogger(StartlineAdvantagesByWindCalculator.class.getName());
public StartlineAdvantagesByWindCalculator(DashboardDispatchContext dashboardDispatchContext, DomainFactory domainFactory) {
super(domainFactory);
this.dashboardDispatchContext = dashboardDispatchContext;
this.domainFactory = domainFactory;
this.defaultPolarSpeedWindAngleFunction = new DefaultPolarWindAngleBoatSpeedFunction();
this.startlineAdvantageCalculationDataRetriever = new StartlineAdvantageCalculationDataRetriever(domainFactory, dashboardDispatchContext.getPolarDataService());
}
public StartlineAdvantagesWithMaxAndAverageDTO getStartLineAdvantagesAccrossLineFromTrackedRaceAtTimePoint(
TrackedRace trackedRace, TimePoint timepoint) {
StartlineAdvantagesWithMaxAndAverageDTO result = new StartlineAdvantagesWithMaxAndAverageDTO();
if (trackedRace != null) {
retrieveDataForCalculation(trackedRace, dashboardDispatchContext.getPolarDataService());
if (getWind() != null) {
StartlineAdvantageCalculationData startlineAdvantageCalculationData = startlineAdvantageCalculationDataRetriever.retrieveDataForTrackedRace(trackedRace);
if (startlineAdvantageCalculationData.getWind() != null) {
Pair<Number[][], Number[][]> startlineAdvantagesAndConfidencesAsArray = null;
if (isStartlineCompletelyUnderneathLaylines()) {
if (isStartlineCompletelyUnderneathLaylines(startlineAdvantageCalculationData)) {
logger.log(Level.INFO, "Startline is completely underneath laylines");
Pair<Double, Double> advantagesRange = new Pair<Double, Double>(0.0, getStartlineLenghtInMeters());
List<StartLineAdvantageDTO> startlineAdvantages = calculateStartlineAdvantagesUnderneathLaylinesInRange(advantagesRange);
Pair<Double, Double> advantagesRange = new Pair<Double, Double>(0.0, startlineAdvantageCalculationData.getStartlineLenghtInMeters());
List<StartLineAdvantageDTO> startlineAdvantages = calculateStartlineAdvantagesUnderneathLaylinesInRange(advantagesRange, startlineAdvantageCalculationData);
double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages);
result.maximum = maximum;
startlineAdvantagesAndConfidencesAsArray = convertStartLineAdvantageDTOListToPointAndConfidenceArrays(startlineAdvantages);
} else if (isStartlineCompletelyAboveLaylines()) {
} else if (isStartlineCompletelyAboveLaylines(startlineAdvantageCalculationData)) {
logger.log(Level.INFO, "Startline is completely above laylines");
Pair<Double, Double> advantagesRange = new Pair<Double, Double>(0.0, getStartlineLenghtInMeters());
List<StartLineAdvantageDTO> startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(advantagesRange);
Pair<Double, Double> advantagesRange = new Pair<Double, Double>(0.0, startlineAdvantageCalculationData.getStartlineLenghtInMeters());
List<StartLineAdvantageDTO> startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(advantagesRange, startlineAdvantageCalculationData);
subtractMinimumOfAllStartlineAdvantages(startlineAdvantages);
double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages);
result.maximum = maximum;
@@ -68,12 +70,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
startlineAdvantagesAndConfidencesAsArray = convertStartLineAdvantageDTOListToPointAndConfidenceArrays(startlineAdvantages);
} else {
logger.log(Level.INFO, "Layline(s) cross startline");
Position intersectionOfRightLaylineAndStartline = getIntersectionOfRightLaylineAndStartline();
Position intersectionOfleftLaylineAndStartline = getIntersectionOfLeftLaylineAndStartline();
Position intersectionOfRightLaylineAndStartline = getIntersectionOfRightLaylineAndStartline(startlineAdvantageCalculationData);
Position intersectionOfleftLaylineAndStartline = getIntersectionOfLeftLaylineAndStartline(startlineAdvantageCalculationData);
Pair<Double, Double> polarBasedStartlineAdvatagesRange = getStartAndEndPointOfPolarBasedStartlineAdvatagesInDistancesToRCBoat(
intersectionOfRightLaylineAndStartline, intersectionOfleftLaylineAndStartline);
Pair<Double, Double> pinEndStartlineAdvatagesRange = getPinEndStartlineAdvantagesRangeFromPolarAdvantagesRange(polarBasedStartlineAdvatagesRange);
List<StartLineAdvantageDTO> startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(polarBasedStartlineAdvatagesRange);
intersectionOfRightLaylineAndStartline, intersectionOfleftLaylineAndStartline, startlineAdvantageCalculationData);
Pair<Double, Double> pinEndStartlineAdvatagesRange = getPinEndStartlineAdvantagesRangeFromPolarAdvantagesRange(polarBasedStartlineAdvatagesRange, startlineAdvantageCalculationData);
List<StartLineAdvantageDTO> startlineAdvantages = calculatePolarBasedStartlineAdvantagesInRange(polarBasedStartlineAdvatagesRange, startlineAdvantageCalculationData);
subtractMinimumOfAllStartlineAdvantages(startlineAdvantages);
double maximum = getMaximumAdvantageOfStartlineAdvantageDTOs(startlineAdvantages);
result.maximum = maximum;
@@ -92,12 +94,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private boolean isBearingAboveAdvantageLines(Bearing bearing) {
private boolean isBearingAboveAdvantageLines(Bearing bearing, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
boolean result = false;
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
- getManeuverAngle() / 2);
Bearing bearingOfLeftLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
+ getManeuverAngle() / 2);
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
- startlineAdvantageCalculationData.getManeuverAngle() / 2);
Bearing bearingOfLeftLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
+ startlineAdvantageCalculationData.getManeuverAngle() / 2);
if (bearing.getDegrees() < bearingOfRightLaylineInDeg.getDegrees() && bearing.getDegrees() > 0
|| bearing.getDegrees() > bearingOfLeftLaylineInDeg.getDegrees() && bearing.getDegrees() < 360) {
result = true;
@@ -105,12 +107,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private boolean isBearingUnderneathAdvantageLines(Bearing bearing) {
private boolean isBearingUnderneathAdvantageLines(Bearing bearing, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
boolean result = false;
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
- getManeuverAngle() / 2);
Bearing bearingOfLeftLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
+ getManeuverAngle() / 2);
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
- startlineAdvantageCalculationData.getManeuverAngle() / 2);
Bearing bearingOfLeftLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
+ startlineAdvantageCalculationData.getManeuverAngle() / 2);
logger.log(Level.INFO, "Underneath bearingOfRightLaylineInDeg?" + bearingOfRightLaylineInDeg);
logger.log(Level.INFO, "Underneath bearingOfLeftLaylineInDeg?" + bearingOfLeftLaylineInDeg);
if (bearing.getDegrees() > bearingOfRightLaylineInDeg.getDegrees()
@@ -120,83 +122,80 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private boolean isStartlineCompletelyAboveLaylines() {
private boolean isStartlineCompletelyAboveLaylines(StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
boolean result = false;
Bearing bearingRCBoatToFirstMark = new DegreeBearingImpl(getStartlineAndFirstMarkPositions().firstMarkPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().startBoatPosition).getDegrees());
Bearing bearingPinEndToFirstMark = new DegreeBearingImpl(getStartlineAndFirstMarkPositions().firstMarkPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().pinEndPosition).getDegrees());
if (isBearingAboveAdvantageLines(bearingRCBoatToFirstMark)
&& isBearingAboveAdvantageLines(bearingPinEndToFirstMark)) {
Bearing bearingRCBoatToFirstMark = new DegreeBearingImpl(startlineAdvantageCalculationData.getFirstMarkPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getStartBoatPosition()).getDegrees());
Bearing bearingPinEndToFirstMark = new DegreeBearingImpl(startlineAdvantageCalculationData.getFirstMarkPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getPinEndPosition()).getDegrees());
if (isBearingAboveAdvantageLines(bearingRCBoatToFirstMark, startlineAdvantageCalculationData)
&& isBearingAboveAdvantageLines(bearingPinEndToFirstMark, startlineAdvantageCalculationData)) {
result = true;
}
return result;
}
private boolean isStartlineCompletelyUnderneathLaylines() {
private boolean isStartlineCompletelyUnderneathLaylines(StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
boolean result = false;
Bearing bearingFirstMarkToRCBoat = new DegreeBearingImpl(getStartlineAndFirstMarkPositions().firstMarkPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().startBoatPosition).getDegrees());
Bearing bearingFirstMarkToPinEnd = new DegreeBearingImpl(getStartlineAndFirstMarkPositions().firstMarkPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().pinEndPosition).getDegrees());
Bearing bearingFirstMarkToRCBoat = new DegreeBearingImpl(startlineAdvantageCalculationData.getFirstMarkPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getStartBoatPosition()).getDegrees());
Bearing bearingFirstMarkToPinEnd = new DegreeBearingImpl(startlineAdvantageCalculationData.getFirstMarkPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getPinEndPosition()).getDegrees());
logger.log(Level.INFO, "Underneath RC?" + bearingFirstMarkToRCBoat);
logger.log(Level.INFO, "Underneath PIN?" + bearingFirstMarkToPinEnd);
if (isBearingUnderneathAdvantageLines(bearingFirstMarkToRCBoat)
&& isBearingUnderneathAdvantageLines(bearingFirstMarkToPinEnd)) {
if (isBearingUnderneathAdvantageLines(bearingFirstMarkToRCBoat, startlineAdvantageCalculationData)
&& isBearingUnderneathAdvantageLines(bearingFirstMarkToPinEnd, startlineAdvantageCalculationData)) {
result = true;
}
return result;
}
private Pair<Double, Double> getStartAndEndPointOfPolarBasedStartlineAdvatagesInDistancesToRCBoat(
Position rightIntersection, Position leftIntersection) {
private Pair<Double, Double> getStartAndEndPointOfPolarBasedStartlineAdvatagesInDistancesToRCBoat(Position rightIntersection, Position leftIntersection, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
Pair<Double, Double> result = null;
if (rightIntersection != null && leftIntersection == null) {
double distanceFromIntersectionToRCBoatInMeters = rightIntersection.getDistance(
getStartlineAndFirstMarkPositions().startBoatPosition).getMeters();
double distanceFromIntersectionToRCBoatInMeters = rightIntersection.getDistance(startlineAdvantageCalculationData.getStartBoatPosition()).getMeters();
result = new Pair<Double, Double>(0.0, distanceFromIntersectionToRCBoatInMeters);
} else if (rightIntersection == null && leftIntersection != null) {
double distanceFromIntersectionToRCBoatInMeters = leftIntersection.getDistance(
getStartlineAndFirstMarkPositions().startBoatPosition).getMeters();
result = new Pair<Double, Double>(distanceFromIntersectionToRCBoatInMeters, getStartlineLenghtInMeters());
double distanceFromIntersectionToRCBoatInMeters = leftIntersection.getDistance(startlineAdvantageCalculationData.getStartBoatPosition()).getMeters();
result = new Pair<Double, Double>(distanceFromIntersectionToRCBoatInMeters, startlineAdvantageCalculationData.getStartlineLenghtInMeters());
} else if (rightIntersection != null && leftIntersection != null) {
result = new Pair<Double, Double>(0.0, getStartlineLenghtInMeters());
result = new Pair<Double, Double>(0.0, startlineAdvantageCalculationData.getStartlineLenghtInMeters());
}
return result;
}
private Position getIntersectionOfRightLaylineAndStartline() {
private Position getIntersectionOfRightLaylineAndStartline(StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
Position result = null;
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
- getManeuverAngle() / 2);
result = calculateIntersectionPointsOfStartlineAndLaylineWithBearing(bearingOfRightLaylineInDeg);
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
- startlineAdvantageCalculationData.getManeuverAngle() / 2);
result = calculateIntersectionPointsOfStartlineAndLaylineWithBearing(bearingOfRightLaylineInDeg, startlineAdvantageCalculationData);
return result;
}
private Position getIntersectionOfLeftLaylineAndStartline() {
private Position getIntersectionOfLeftLaylineAndStartline(StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
Position result = null;
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(getWind().getBearing().getDegrees()
+ getManeuverAngle() / 2);
result = calculateIntersectionPointsOfStartlineAndLaylineWithBearing(bearingOfRightLaylineInDeg);
Bearing bearingOfRightLaylineInDeg = new DegreeBearingImpl(startlineAdvantageCalculationData.getWind().getBearing().getDegrees()
+ startlineAdvantageCalculationData.getManeuverAngle() / 2);
result = calculateIntersectionPointsOfStartlineAndLaylineWithBearing(bearingOfRightLaylineInDeg, startlineAdvantageCalculationData);
return result;
}
private Position calculateIntersectionPointsOfStartlineAndLaylineWithBearing(Bearing bearing) {
private Position calculateIntersectionPointsOfStartlineAndLaylineWithBearing(Bearing bearing, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
Position result = null;
Bearing bearingOfStartlineInRad = getStartlineAndFirstMarkPositions().startBoatPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().pinEndPosition);
Bearing bearingOfStartlineInRad = startlineAdvantageCalculationData.getStartBoatPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getPinEndPosition());
Bearing bearingOfStartlineInDeg = new DegreeBearingImpl(bearingOfStartlineInRad.getDegrees());
logger.log(Level.INFO, "bearingOfStartlineInDeg " + bearingOfStartlineInDeg);
logger.log(Level.INFO, "bearingOfLaylineInDeg " + bearing);
Position intersectionPointLaylineStartline = getStartlineAndFirstMarkPositions().firstMarkPosition.getIntersection(
bearing, getStartlineAndFirstMarkPositions().pinEndPosition, bearingOfStartlineInDeg);
Position intersectionPointLaylineStartline = startlineAdvantageCalculationData.getFirstMarkPosition().getIntersection(
bearing, startlineAdvantageCalculationData.getPinEndPosition(), bearingOfStartlineInDeg);
logger.log(Level.INFO, "rightIntersectionPointLaylineStartline " + intersectionPointLaylineStartline);
Bearing bearingIntersectionPointToFirstMark = new DegreeBearingImpl(
getStartlineAndFirstMarkPositions().firstMarkPosition.getBearingGreatCircle(
startlineAdvantageCalculationData.getFirstMarkPosition().getBearingGreatCircle(
intersectionPointLaylineStartline).getDegrees());
if (bearingIntersectionPointToFirstMark.getDegrees() < bearing.getDegrees() + 1
&& bearingIntersectionPointToFirstMark.getDegrees() > bearing.getDegrees() - 1
&& isOnStartline(intersectionPointLaylineStartline)) {
&& isPositionOnStartline(intersectionPointLaylineStartline, startlineAdvantageCalculationData)) {
result = intersectionPointLaylineStartline;
logger.log(Level.INFO, "Layline crosses startline");
}
@@ -205,10 +204,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private boolean isOnStartline(Position position) {
/**
* On startline mean less than one meter away
* */
private boolean isPositionOnStartline(Position position, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
boolean result = false;
Distance distanceToStartline = position.getDistanceToLine(getStartlineAndFirstMarkPositions().startBoatPosition,
getStartlineAndFirstMarkPositions().pinEndPosition);
Distance distanceToStartline = position.getDistanceToLine(startlineAdvantageCalculationData.getStartBoatPosition(), startlineAdvantageCalculationData.getPinEndPosition());
if (distanceToStartline.getMeters() < 1) {
result = true;
}
@@ -231,18 +232,17 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return advantages;
}
private Pair<Double, Double> getPinEndStartlineAdvantagesRangeFromPolarAdvantagesRange(
Pair<Double, Double> rangePolarBasedStartlineAdvatages) {
private Pair<Double, Double> getPinEndStartlineAdvantagesRangeFromPolarAdvantagesRange(Pair<Double, Double> rangePolarBasedStartlineAdvatages, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
Pair<Double, Double> result = null;
double pinEndStartlineAdvantagesStart;
double pinEndStartlineAdvantagesEnd;
if (rangePolarBasedStartlineAdvatages.getA().doubleValue() == 0.0
&& rangePolarBasedStartlineAdvatages.getB().doubleValue() != getStartlineLenghtInMeters()) {
&& rangePolarBasedStartlineAdvatages.getB().doubleValue() != startlineAdvantageCalculationData.getStartlineLenghtInMeters()) {
pinEndStartlineAdvantagesStart = rangePolarBasedStartlineAdvatages.getB().doubleValue();
pinEndStartlineAdvantagesEnd = getStartlineLenghtInMeters();
pinEndStartlineAdvantagesEnd = startlineAdvantageCalculationData.getStartlineLenghtInMeters();
result = new Pair<Double, Double>(pinEndStartlineAdvantagesStart, pinEndStartlineAdvantagesEnd);
} else if (rangePolarBasedStartlineAdvatages.getA().doubleValue() != 0.0
&& rangePolarBasedStartlineAdvatages.getB().doubleValue() == getStartlineLenghtInMeters()) {
&& rangePolarBasedStartlineAdvatages.getB().doubleValue() == startlineAdvantageCalculationData.getStartlineLenghtInMeters()) {
pinEndStartlineAdvantagesStart = 0.0;
pinEndStartlineAdvantagesEnd = rangePolarBasedStartlineAdvatages.getB().doubleValue();
result = new Pair<Double, Double>(pinEndStartlineAdvantagesStart, pinEndStartlineAdvantagesEnd);
@@ -250,8 +250,7 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private List<StartLineAdvantageDTO> calculateStartlineAdvantagesUnderneathLaylinesInRange(
Pair<Double, Double> rangePinEndStartlineAdvantage) {
private List<StartLineAdvantageDTO> calculateStartlineAdvantagesUnderneathLaylinesInRange(Pair<Double, Double> rangePinEndStartlineAdvantage, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
List<StartLineAdvantageDTO> result = new ArrayList<StartLineAdvantageDTO>();
if (rangePinEndStartlineAdvantage != null) {
logger.log(Level.INFO, "PinEnd startline advantages range " + rangePinEndStartlineAdvantage.getA() + " - "
@@ -262,12 +261,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
StartLineAdvantageDTO leftEdgeAdvantage = new StartLineAdvantageDTO();
leftEdgeAdvantage.confidence = 1.0;
leftEdgeAdvantage.distanceToRCBoatInMeters = rangePinEndStartlineAdvantage.getB();
if (getStartlineAdvantageAtPinEndInMeters() >= 0) {
leftEdgeAdvantage.startLineAdvantage = getStartlineAdvantageAtPinEndInMeters();
if (startlineAdvantageCalculationData.getStartlineAdvantageAtPinEndInMeters() >= 0) {
leftEdgeAdvantage.startLineAdvantage = startlineAdvantageCalculationData.getStartlineAdvantageAtPinEndInMeters();
rightEdgeAdvantage.startLineAdvantage = 0.0;
} else {
leftEdgeAdvantage.startLineAdvantage = 0.0;
rightEdgeAdvantage.startLineAdvantage = Math.abs(getStartlineAdvantageAtPinEndInMeters());
rightEdgeAdvantage.startLineAdvantage = Math.abs(startlineAdvantageCalculationData.getStartlineAdvantageAtPinEndInMeters());
}
result.add(rightEdgeAdvantage);
result.add(leftEdgeAdvantage);
@@ -296,27 +295,26 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private List<StartLineAdvantageDTO> calculatePolarBasedStartlineAdvantagesInRange(
Pair<Double, Double> rangePinEndStartlineAdvantage) {
private List<StartLineAdvantageDTO> calculatePolarBasedStartlineAdvantagesInRange(Pair<Double, Double> rangePinEndStartlineAdvantage, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
List<StartLineAdvantageDTO> result = new ArrayList<StartLineAdvantageDTO>();
if (rangePinEndStartlineAdvantage != null) {
logger.log(Level.INFO, "PolarBased startline advantages range " + rangePinEndStartlineAdvantage.getA()
+ " - " + rangePinEndStartlineAdvantage.getB());
Bearing bearingOfStartlineInRad = getStartlineAndFirstMarkPositions().startBoatPosition
.getBearingGreatCircle(getStartlineAndFirstMarkPositions().pinEndPosition);
Bearing bearingOfStartlineInRad = startlineAdvantageCalculationData.getStartBoatPosition()
.getBearingGreatCircle(startlineAdvantageCalculationData.getPinEndPosition());
Bearing bearingOfStartlineInDeg = new DegreeBearingImpl(bearingOfStartlineInRad.getDegrees());
for (double i = rangePinEndStartlineAdvantage.getA().doubleValue(); i < rangePinEndStartlineAdvantage
.getB().doubleValue() - 1; i++) {
StartLineAdvantageDTO startlineAdvantage = new StartLineAdvantageDTO();
startlineAdvantage.confidence = 0.5;
startlineAdvantage.distanceToRCBoatInMeters = i;
Position startingPosition = getStartlineAndFirstMarkPositions().startBoatPosition.translateRhumb(bearingOfStartlineInDeg, new MeterDistance(i));
Distance startingPositionToFirstMarkDistance = startingPosition.getDistance(getStartlineAndFirstMarkPositions().firstMarkPosition);
Bearing bearingOfFirstMarkToStartPositionPositionInRad = getStartlineAndFirstMarkPositions().firstMarkPosition.getBearingGreatCircle(startingPosition);
Position startingPosition = startlineAdvantageCalculationData.getStartBoatPosition().translateRhumb(bearingOfStartlineInDeg, new MeterDistance(i));
Distance startingPositionToFirstMarkDistance = startingPosition.getDistance(startlineAdvantageCalculationData.getFirstMarkPosition());
Bearing bearingOfFirstMarkToStartPositionPositionInRad = startlineAdvantageCalculationData.getFirstMarkPosition().getBearingGreatCircle(startingPosition);
Bearing bearingOfFirstMarkToStartPositionPositionInDeg = new DegreeBearingImpl(bearingOfFirstMarkToStartPositionPositionInRad.getDegrees());
DegreeBearingImpl angleToWind = new DegreeBearingImpl(Math.abs(getWind().getBearing().getDifferenceTo(bearingOfFirstMarkToStartPositionPositionInDeg).getDegrees()));
DegreeBearingImpl angleToWind = new DegreeBearingImpl(Math.abs(startlineAdvantageCalculationData.getWind().getBearing().getDifferenceTo(bearingOfFirstMarkToStartPositionPositionInDeg).getDegrees()));
logger.log(Level.INFO, "angleToWind" + angleToWind);
SpeedWithConfidence<Void> speedWithConfidence = getBoatSpeedWithConfidenceForWindAngleAndStrength(angleToWind);
SpeedWithConfidence<Void> speedWithConfidence = getBoatSpeedWithConfidenceForWindAngleAndStrength(angleToWind, startlineAdvantageCalculationData);
Speed speed = speedWithConfidence.getObject();
logger.log(Level.INFO, "Speed: " + speed.getKnots()+" Confidence: "+speedWithConfidence.getConfidence());
logger.log(Level.INFO, "startingPositionToFirstMarkDistance.getMeters()" + startingPositionToFirstMarkDistance.getMeters());
@@ -369,12 +367,12 @@ public class StartlineAdvantagesByWindCalculator extends AbstracPreCalculationDa
return result;
}
private SpeedWithConfidence<Void> getBoatSpeedWithConfidenceForWindAngleAndStrength(Bearing angleToWind) {
private SpeedWithConfidence<Void> getBoatSpeedWithConfidenceForWindAngleAndStrength(Bearing angleToWind, StartlineAdvantageCalculationData startlineAdvantageCalculationData) {
SpeedWithConfidence<Void> result = null;
try {
result = dashboardDispatchContext.getPolarDataService().getSpeed(domainFactory.getOrCreateBoatClass("Extreme 40"), new KnotSpeedImpl(getWind().getBeaufort()), angleToWind);
result = dashboardDispatchContext.getPolarDataService().getSpeed(domainFactory.getOrCreateBoatClass("Extreme 40"), new KnotSpeedImpl(startlineAdvantageCalculationData.getWind().getBeaufort()), angleToWind);
} catch (NotEnoughDataHasBeenAddedException e) {
result = defaultPolarSpeedWindAngleFunction.getBoatSpeedForWindAngleAndSpeed(angleToWind, new KnotSpeedImpl(getWind().getBeaufort()));
result = defaultPolarSpeedWindAngleFunction.getBoatSpeedForWindAngleAndSpeed(angleToWind, new KnotSpeedImpl(startlineAdvantageCalculationData.getWind().getBeaufort()));
e.printStackTrace();
}
return result;
@@ -1,59 +0,0 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.TrackedRace;
/**
* @author Alexander Ries (D062114)
*
*/
public abstract class AbstracPreCalculationDataRetriever implements PreCalculationMarkRetriever,
PreCalculationStartlineAdvantageRetriever,
PreCalculationWindRetriever,
PreCalculationPolarDataRetriever {
private StartlineAndFirstMarkPositions startlineAndFirstMarkPositions;
private double startlineAdvantageAtPinEndInMeters;
private double startlineLenghtInMeters;
private Wind wind;
private double maneuverAngle;
private final DomainFactory domainFactory;
public AbstracPreCalculationDataRetriever(DomainFactory domainFactory) {
super();
this.domainFactory = domainFactory;
}
protected void retrieveDataForCalculation(TrackedRace trackedRace, PolarDataService polarDataService) {
startlineAndFirstMarkPositions = retrieveMarkPositions(trackedRace);
startlineAdvantageAtPinEndInMeters = retrieveStartlineAdvantage(trackedRace);
startlineLenghtInMeters = retrieveStartlineLenght(trackedRace);
wind = retrieveWindAtPosition(startlineAndFirstMarkPositions.startBoatPosition, trackedRace);
maneuverAngle = retrieveManouvreAngleAtWindSpeedAndBoatClass(domainFactory.getOrCreateBoatClass("Extreme 40"), ManeuverType.TACK, wind, polarDataService);
}
protected StartlineAndFirstMarkPositions getStartlineAndFirstMarkPositions() {
return startlineAndFirstMarkPositions;
}
protected double getStartlineAdvantageAtPinEndInMeters() {
return startlineAdvantageAtPinEndInMeters;
}
protected double getStartlineLenghtInMeters() {
return startlineLenghtInMeters;
}
protected Wind getWind() {
return wind;
}
protected double getManeuverAngle() {
return maneuverAngle;
}
}
@@ -1,75 +0,0 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import java.util.Iterator;
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.Position;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* @author Alexander Ries (D062114)
*
*/
public interface PreCalculationMarkRetriever {
default StartlineAndFirstMarkPositions retrieveMarkPositions(TrackedRace trackedRace) {
StartlineAndFirstMarkPositions result = new StartlineAndFirstMarkPositions();
Course course = trackedRace.getRace().getCourse();
if (course != null) {
Waypoint startlineWayPoint = course.getFirstLeg().getFrom();
Waypoint firstmarkWayPoint = course.getFirstLeg().getTo();
if (startlineWayPoint != null && firstmarkWayPoint != null) {
Pair<Position, Position> startlineMarkPositions = retrieveStartlineMarkPositionsFromStartLineWayPoint(startlineWayPoint, trackedRace);
Position firstMarkPosition = retrieveFirstMarkPositionFromFirstMarkWayPoint(firstmarkWayPoint, trackedRace);
result.startBoatPosition = startlineMarkPositions.getA();
result.pinEndPosition = startlineMarkPositions.getB();
result.firstMarkPosition = firstMarkPosition;
}
}
return result;
}
default Pair<Position, Position> retrieveStartlineMarkPositionsFromStartLineWayPoint(Waypoint startLineWayPoint, TrackedRace trackedRace) {
Pair<Position, Position> result = null;
Iterator<Mark> markIterator = startLineWayPoint.getMarks().iterator();
if (markIterator.hasNext()) {
Mark startboat = (Mark) markIterator.next();
if (markIterator.hasNext()) {
Mark pinEnd = (Mark) markIterator.next();
TimePoint now = MillisecondsTimePoint.now();
Position startBoatPosition = getPositionFromMarkAtTimePoint(trackedRace, startboat, now);
Position pinEndPosition = getPositionFromMarkAtTimePoint(trackedRace, pinEnd, now);
result = new Pair<Position, Position>(startBoatPosition, pinEndPosition);
}
}
return result;
}
default Position retrieveFirstMarkPositionFromFirstMarkWayPoint(Waypoint firstMarkWayPoint, TrackedRace trackedRace) {
Position result = null;
if (firstMarkWayPoint.getMarks().iterator().hasNext()) {
Mark firstMark = firstMarkWayPoint.getMarks().iterator().next();
TimePoint now = MillisecondsTimePoint.now();
result = getPositionFromMarkAtTimePoint(trackedRace, firstMark, now);
}
return result;
}
default Position getPositionFromMarkAtTimePoint(TrackedRace trackedRace, Mark mark, TimePoint timePoint) {
GPSFixTrack<Mark, GPSFix> fixTrack = trackedRace.getTrack(mark);
return fixTrack.getEstimatedPosition(timePoint, true);
}
class StartlineAndFirstMarkPositions {
public Position startBoatPosition;
public Position pinEndPosition;
public Position firstMarkPosition;
}
}
@@ -1,29 +0,0 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.DefaultPolarValues;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
/**
* @author Alexander Ries (D062114)
*
*/
public interface PreCalculationPolarDataRetriever {
default double retrieveManouvreAngleAtWindSpeedAndBoatClass(BoatClass boatClass, ManeuverType maneuverType, Speed windSpeed, PolarDataService polarDataService) {
double result = DefaultPolarValues.getManouvreAngle(ManeuverType.TACK);
try {
if (boatClass != null && maneuverType != null && windSpeed != null) {
BearingWithConfidence<Void> bearingWithConfidence = polarDataService.getManeuverAngle(boatClass, maneuverType, windSpeed);
result = bearingWithConfidence.getObject().getDegrees();
}
} catch (NotEnoughDataHasBeenAddedException | NullPointerException e) {
e.printStackTrace();
}
return result;
}
}
@@ -1,28 +0,0 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* @author Alexander Ries (D062114)
*
*/
public interface PreCalculationStartlineAdvantageRetriever {
default double retrieveStartlineAdvantage(TrackedRace trackedRace) {
double result = 0;
LineDetails startline = trackedRace.getStartLine(MillisecondsTimePoint.now());
if (startline != null && startline.getAdvantage() != null) {
result = startline.getAdvantage().getMeters();
}
return result;
}
default double retrieveStartlineLenght(TrackedRace trackedRace) {
double result = 0;
LineDetails startline = trackedRace.getStartLine(MillisecondsTimePoint.now());
result = startline.getLength().getMeters();
return result;
}
}
@@ -1,19 +0,0 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* @author Alexander Ries (D062114)
*
*/
public interface PreCalculationWindRetriever {
default Wind retrieveWindAtPosition(Position position, TrackedRace trackedRace) {
Wind result = null;
result = trackedRace.getWind(position, MillisecondsTimePoint.now());
return result;
}
}
@@ -0,0 +1,61 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
/**
* In order to calculate the startline adavantage by wind, the data objects defined in this class
* need to be defined.
*
* @author Alexander Ries (D062114)
*
*/
public class StartlineAdvantageCalculationData {
private Position startBoatPosition;
private Position pinEndPosition;
private Position firstMarkPosition;
private Double startlineAdvantageAtPinEndInMeters;
private Double startlineLenghtInMeters;
private Wind wind;
private Double maneuverAngle;
public StartlineAdvantageCalculationData(Position startBoatPosition, Position pinEndPosition,
Position firstMarkPosition, Double startlineAdvantageAtPinEndInMeters, Double startlineLenghtInMeters,
Wind wind, Double maneuverAngle) {
this.startBoatPosition = startBoatPosition;
this.pinEndPosition = pinEndPosition;
this.firstMarkPosition = firstMarkPosition;
this.startlineAdvantageAtPinEndInMeters = startlineAdvantageAtPinEndInMeters;
this.startlineLenghtInMeters = startlineLenghtInMeters;
this.wind = wind;
this.maneuverAngle = maneuverAngle;
}
public Position getStartBoatPosition() {
return startBoatPosition;
}
public Position getPinEndPosition() {
return pinEndPosition;
}
public Position getFirstMarkPosition() {
return firstMarkPosition;
}
public Double getStartlineAdvantageAtPinEndInMeters() {
return startlineAdvantageAtPinEndInMeters;
}
public Double getStartlineLenghtInMeters() {
return startlineLenghtInMeters;
}
public Wind getWind() {
return wind;
}
public Double getManeuverAngle() {
return maneuverAngle;
}
}
@@ -0,0 +1,152 @@
package com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.precalculation;
import java.util.Iterator;
import com.sap.sailing.dashboards.gwt.server.util.actions.startlineadvantage.DefaultPolarValues;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.confidence.BearingWithConfidence;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* @author Alexander Ries (D062114)
*
*/
public class StartlineAdvantageCalculationDataRetriever {
private PolarDataService polarDataService;
private final DomainFactory domainFactory;
public StartlineAdvantageCalculationDataRetriever(DomainFactory domainFactory, PolarDataService polarDataService) {
super();
this.polarDataService = polarDataService;
this.domainFactory = domainFactory;
}
public StartlineAdvantageCalculationData retrieveDataForTrackedRace(TrackedRace trackedRace) {
StartlineAdvantageCalculationData result = null;
Position startBoatPosition = null;
Position pinEndPosition = null;
Position firstMarkPosition = null;
Double startlineAdvantageAtPinEndInMeters = null;
Double startlineLenghtInMeters = null;
Wind wind = null;
Double maneuverAngle = null;
Pair<Position/* StartBoatPosition */, Position/* PinEndPosition */> startlineMarkPositions = retrieveStartlineMarksPositions(trackedRace);
startBoatPosition = startlineMarkPositions.getA();
pinEndPosition = startlineMarkPositions.getB();
firstMarkPosition = retrieveFirstMarkPosition(trackedRace);
startlineAdvantageAtPinEndInMeters = retrieveStartlineAdvantage(trackedRace);
startlineLenghtInMeters = retrieveStartlineLenght(trackedRace);
wind = retrieveWindAtPosition(startBoatPosition, trackedRace);
maneuverAngle = retrieveManouvreAngleAtWindSpeedAndBoatClass(domainFactory.getOrCreateBoatClass("Extreme 40"), ManeuverType.TACK, wind);
result = new StartlineAdvantageCalculationData(startBoatPosition, pinEndPosition, firstMarkPosition, startlineAdvantageAtPinEndInMeters, startlineLenghtInMeters, wind, maneuverAngle);
return result;
}
private Position retrieveFirstMarkPosition(TrackedRace trackedRace) {
Position result = null;
Course course = trackedRace.getRace().getCourse();
if (course != null) {
Waypoint firstmarkWayPoint = course.getFirstLeg().getTo();
if (firstmarkWayPoint != null) {
result = retrieveFirstMarkPositionFromFirstMarkWayPoint(firstmarkWayPoint, trackedRace);
}
}
return result;
}
private Pair<Position/* StartBoatPosition */, Position/* PinEndPosition */> retrieveStartlineMarksPositions(TrackedRace trackedRace) {
Pair<Position, Position> result = new Pair<Position, Position>(null, null);
Course course = trackedRace.getRace().getCourse();
if (course != null) {
Waypoint startlineWayPoint = course.getFirstLeg().getFrom();
Waypoint firstmarkWayPoint = course.getFirstLeg().getTo();
if (startlineWayPoint != null && firstmarkWayPoint != null) {
result = retrieveStartlineMarkPositionsFromStartLineWayPoint(startlineWayPoint, trackedRace);
}
}
return result;
}
private Pair<Position/*StartBoatPosition*/, Position/*PinEndPosition*/> retrieveStartlineMarkPositionsFromStartLineWayPoint(Waypoint startLineWayPoint, TrackedRace trackedRace) {
Pair<Position, Position> result = null;
Iterator<Mark> markIterator = startLineWayPoint.getMarks().iterator();
if (markIterator.hasNext()) {
Mark startboat = (Mark) markIterator.next();
if (markIterator.hasNext()) {
Mark pinEnd = (Mark) markIterator.next();
TimePoint now = MillisecondsTimePoint.now();
Position startBoatPosition = getPositionFromMarkAtTimePoint(trackedRace, startboat, now);
Position pinEndPosition = getPositionFromMarkAtTimePoint(trackedRace, pinEnd, now);
result = new Pair<Position, Position>(startBoatPosition, pinEndPosition);
}
}
return result;
}
private Position retrieveFirstMarkPositionFromFirstMarkWayPoint(Waypoint firstMarkWayPoint, TrackedRace trackedRace) {
Position result = null;
if (firstMarkWayPoint.getMarks().iterator().hasNext()) {
Mark firstMark = firstMarkWayPoint.getMarks().iterator().next();
TimePoint now = MillisecondsTimePoint.now();
result = getPositionFromMarkAtTimePoint(trackedRace, firstMark, now);
}
return result;
}
private Position getPositionFromMarkAtTimePoint(TrackedRace trackedRace, Mark mark, TimePoint timePoint) {
GPSFixTrack<Mark, GPSFix> fixTrack = trackedRace.getTrack(mark);
return fixTrack.getEstimatedPosition(timePoint, true);
}
private Double retrieveStartlineAdvantage(TrackedRace trackedRace) {
Double result = null;
LineDetails startline = trackedRace.getStartLine(MillisecondsTimePoint.now());
if (startline != null && startline.getAdvantage() != null) {
result = new Double(startline.getAdvantage().getMeters());
}
return result;
}
private Double retrieveStartlineLenght(TrackedRace trackedRace) {
Double result = null;
LineDetails startline = trackedRace.getStartLine(MillisecondsTimePoint.now());
result = new Double(startline.getLength().getMeters());
return result;
}
private Wind retrieveWindAtPosition(Position position, TrackedRace trackedRace) {
Wind result = null;
result = trackedRace.getWind(position, MillisecondsTimePoint.now());
return result;
}
private Double retrieveManouvreAngleAtWindSpeedAndBoatClass(BoatClass boatClass, ManeuverType maneuverType, Speed windSpeed) {
Double result = DefaultPolarValues.getManouvreAngle(ManeuverType.TACK);
try {
if (boatClass != null && maneuverType != null && windSpeed != null) {
BearingWithConfidence<Void> bearingWithConfidence = polarDataService.getManeuverAngle(boatClass, maneuverType, windSpeed);
result = new Double(bearingWithConfidence.getObject().getDegrees());
}
} catch (NotEnoughDataHasBeenAddedException | NullPointerException e) {
e.printStackTrace();
}
return result;
}
}
@@ -47,9 +47,8 @@ public class DashboardLiveRaceProvider {
private void setLiveRaceForLeaderboardName(String leaderboardName, TrackedRace liveRace) {
if (liveRaceForLeaderboardNames.containsKey(leaderboardName)) {
liveRaceForLeaderboardNames.replace(leaderboardName, liveRace);
} else {
liveRaceForLeaderboardNames.put(leaderboardName, liveRace);
liveRaceForLeaderboardNames.remove(leaderboardName);
}
liveRaceForLeaderboardNames.put(leaderboardName, liveRace);
}
}
@@ -29,7 +29,8 @@ public class MovingAveragesCache {
if (averagesForStringKeys.containsKey(key)) {
MovingAverage movingAverage = averagesForStringKeys.get(key);
movingAverage.add(value);
averagesForStringKeys.replace(key, movingAverage);
averagesForStringKeys.remove(key);
averagesForStringKeys.put(key, movingAverage);
}
}
@@ -95,7 +95,7 @@ public class SailingPredefinedQueries {
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getTeam().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getTrackedLegContext().getLegType()", HasGPSFixContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
@@ -118,7 +118,7 @@ public class SailingPredefinedQueries {
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
FunctionDTO dimensionToGroupBy1 = new FunctionDTO(true, "getTrackedLegContext().getLegType()", HasTrackedLegOfCompetitorContext.class.getName(), LegType.class.getName(), new ArrayList<String>(), "", 0);
@@ -142,7 +142,7 @@ public class SailingPredefinedQueries {
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getTeam().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getTrackedLegOfCompetitorContext().getCompetitor().getName()", HasGPSFixContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
@@ -162,7 +162,7 @@ public class SailingPredefinedQueries {
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getName()", HasTrackedLegOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
@@ -181,7 +181,7 @@ public class SailingPredefinedQueries {
ModifiableStatisticQueryDefinitionDTO queryDefinition = new ModifiableStatisticQueryDefinitionDTO("default", statistic, aggregator, retrieverChain);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getTeam().getName()", HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
FunctionDTO dimensionToGroupBy0 = new FunctionDTO(true, "getCompetitor().getName()", HasRaceOfCompetitorContext.class.getName(), String.class.getName(), new ArrayList<String>(), "", 0);
queryDefinition.appendDimensionToGroupBy(dimensionToGroupBy0);
return queryDefinition;
@@ -32,14 +32,16 @@ public class DeviceConfigurationQRCodeUtils {
return accessToken;
}
}
public static String composeQRContent(String deviceIdentifier, String apkUrl, String accessToken) {
// poor man's uri fragment encoding: ' ' as '%20'
String encodedIdentifier = deviceIdentifier.replaceAll(" ", "%20");
return apkUrl + "#" + deviceIdentifierKey + "=" + encodedIdentifier+"&"+accessTokenKey + "=" + accessToken;
public static interface URLDecoder {
String decode(String encodedURL);
}
public static DeviceConfigurationDetails splitQRContent(String qrCodeContent) {
public static String composeQRContent(String urlEncodedDeviceIdentifier, String apkUrl, String accessToken) {
return apkUrl + "#" + deviceIdentifierKey + "=" + urlEncodedDeviceIdentifier + "&"+accessTokenKey + "=" + accessToken;
}
public static DeviceConfigurationDetails splitQRContent(String qrCodeContent, URLDecoder urlDecoder) {
int fragmentIndex = qrCodeContent.lastIndexOf('#');
if (fragmentIndex == -1 || fragmentIndex == 0 || qrCodeContent.length() == fragmentIndex + 1) {
throw new IllegalArgumentException("There is no server or identifier.");
@@ -50,7 +52,7 @@ public class DeviceConfigurationQRCodeUtils {
for (String param : params) {
final String[] keyValue = param.split("=", 2);
if (keyValue.length == 2) {
paramMap.put(keyValue[0], keyValue[1].replaceAll("%20", " "));
paramMap.put(keyValue[0], urlDecoder.decode(keyValue[1]));
}
}
if (!paramMap.containsKey(deviceIdentifierKey)) {
@@ -10,7 +10,7 @@ import com.sap.sse.common.util.UrlHelper;
* {@link DeviceMappingOnRaceQRCodeWidget}).
* <p>
* The structure of the URL is documented in the <a
* href="http://wiki.sapsailing.com/wiki/tracking-app-api-v1-draft">Wiki</a>.
* href="http://wiki.sapsailing.com/wiki/info/api/api-v1">Wiki</a>.
* {@code http://<host>/tracking/checkin?event_id=<e>&leaderboard_name=<l>&competitor_id=<c>}
*
* @author Fredrik Teschke
@@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.tracking;
/**
* Specific {@link SensorFix} for bravo devices (http://www.bravosystems.es/).
*
* <p><b>NOTE:</b> This fix type is currently used to track ESS foiling data, only.</p>
*/
public interface BravoFix extends SensorFix {
@@ -344,7 +344,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory {
logger.warning(String.format(
"Setting the unique index on the %s collection failed because you have too many duplicates. "
+ "This leads to the mongo error code %s and the following message: %s \nTo fix this follow "
+ "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/cook-book#Remove-"
+ "the steps provided on the wiki page: http://wiki.sapsailing.com/wiki/howto/misc/cook-book#Remove-"
+ "duplicates-from-WIND_TRACK-collection", CollectionNames.WIND_TRACKS.name(),
exception.getCode(), exception.getMessage()));
} else {
@@ -37,6 +37,8 @@ import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.common.TypeBasedServiceFinderFactory;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* At the moment, the timerange covered by the fixes for a device, and the number of fixes for a device are stored in a
@@ -52,6 +54,10 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
private final DBCollection fixesCollection;
private final DBCollection metadataCollection;
private final MongoObjectFactoryImpl mongoOF;
/**
* Lock object to be used when accessing {@link #listeners}.
*/
private final NamedReentrantReadWriteLock listenersLock = new NamedReentrantReadWriteLock("Listeners collection lock of " + MongoSensorFixStoreImpl.class.getName(), false);
private final Map<DeviceIdentifier, Set<FixReceivedListener<? extends Timed>>> listeners = new HashMap<>();
public MongoSensorFixStoreImpl(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory,
@@ -168,9 +174,7 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
logger.log(Level.WARNING, "Could not store fix in MongoDB");
e.printStackTrace();
}
for (FixT fix : fixes) {
notifyListeners(device, fix);
}
notifyListeners(device, fixes);
}
@Override
@@ -179,23 +183,30 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <FixT extends Timed> void notifyListeners(DeviceIdentifier device, FixT fix) {
for (FixReceivedListener<FixT> listener : Util.<DeviceIdentifier, Set<FixReceivedListener<FixT>>> get(
(Map) listeners, device, Collections.emptySet())) {
listener.fixReceived(device, fix);
}
private <FixT extends Timed> void notifyListeners(DeviceIdentifier device, Iterable<FixT> fixes) {
LockUtil.executeWithReadLock(listenersLock, () -> {
for (FixT fix : fixes) {
for (FixReceivedListener<FixT> listener : Util.<DeviceIdentifier, Set<FixReceivedListener<FixT>>> get(
(Map) listeners, device, Collections.emptySet())) {
listener.fixReceived(device, fix);
}
}
});
}
@Override
public synchronized void addListener(FixReceivedListener<? extends Timed> listener, DeviceIdentifier device) {
Util.addToValueSet(listeners, device, listener);
public void addListener(FixReceivedListener<? extends Timed> listener, DeviceIdentifier device) {
LockUtil.executeWithWriteLock(listenersLock, () -> Util.addToValueSet(listeners, device, listener));
}
@Override
public synchronized void removeListener(FixReceivedListener<? extends Timed> listener) {
for (Set<FixReceivedListener<? extends Timed>> set : listeners.values()) {
set.remove(listener);
}
public void removeListener(FixReceivedListener<? extends Timed> listener) {
LockUtil.executeWithWriteLock(listenersLock, () -> Util.removeFromAllValueSets(listeners, listener));
}
@Override
public void removeListener(FixReceivedListener<? extends Timed> listener, DeviceIdentifier device) {
LockUtil.executeWithWriteLock(listenersLock, () -> Util.removeFromValueSet(listeners, device, listener));
}
private DBObject getDeviceQuery(DeviceIdentifier device)
@@ -232,6 +243,19 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore {
return ((Number) result.get(FieldNames.NUM_FIXES.name())).longValue();
}
/**
* Try to find a service implementing specified type of objects interface using the {@link #fixServiceFinder}.
* <p>
* To support an additional fix type, an implementation of {@link FixMongoHandler} is required, which specifies how to
* transform a fix forth to and back from database. This implementation needs to be
* registered during OSGi bundle activator startup, providing respective type mapping properties.
* </p>
*
* @param type type object to find service for
* @return the registered {@link FixMongoHandler} implementation
*
* @see TypeBasedServiceFinder#findService(String)
*/
@SuppressWarnings("unchecked")
private <FixT extends Timed> FixMongoHandler<FixT> findService(String type) {
return (FixMongoHandler<FixT>) fixServiceFinder.findService(type);
@@ -0,0 +1,73 @@
package com.sap.sailing.domain.racelogtracking.test;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDefineMarkEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.impl.MarkImpl;
import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import junit.framework.Assert;
public class RevokeDeviceMappingsWithMarkDefinitionTest {
private RegattaLog regattaLog;
private AbstractLogEventAuthor author;
private Mark mark;
@Before
public void prepare() {
regattaLog = new RegattaLogImpl("RevokeTest");
author = new LogEventAuthorImpl("RevokeTest", 1);
mark = new MarkImpl("mark");
}
@Test
public void testRevoking() {
final PingDeviceIdentifierImpl device = new PingDeviceIdentifierImpl();
final RegattaLogDefineMarkEvent defineMarkEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(), author,
MillisecondsTimePoint.now(), UUID.randomUUID(), mark);
regattaLog.add(defineMarkEvent);
regattaLog.add(new RegattaLogDeviceMarkMappingEventImpl(MillisecondsTimePoint.now(), author, mark, device,
MillisecondsTimePoint.now(), MillisecondsTimePoint.now()));
regattaLog.revokeDefineMarkEventAndRelatedDeviceMappings(defineMarkEvent, author, null /* the logger should not be needed */);
for (RegattaLogEvent event : regattaLog.getUnrevokedEvents()) {
if (event instanceof RegattaLogDefineMarkEventImpl) {
Assert.fail("Define mark not revoked.");
}
if (event instanceof RegattaLogDeviceMarkMappingEventImpl) {
Assert.fail("Device mapping not revoked.");
}
}
}
@Test
public void testRevokingWithoutDeviceMapping() {
final RegattaLogDefineMarkEvent defineMarkEvent = new RegattaLogDefineMarkEventImpl(MillisecondsTimePoint.now(), author,
MillisecondsTimePoint.now(), UUID.randomUUID(), mark);
regattaLog.add(defineMarkEvent);
regattaLog.revokeDefineMarkEventAndRelatedDeviceMappings(defineMarkEvent, author, null /* the logger should not be needed */);
for (RegattaLogEvent event : regattaLog.getUnrevokedEvents()) {
if (event instanceof RegattaLogDefineMarkEventImpl) {
Assert.fail("Define mark not revoked.");
}
}
}
}
@@ -0,0 +1,85 @@
package com.sap.sailing.domain.racelogtracking.test.impl;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl;
import com.sap.sailing.domain.racelog.tracking.FixReceivedListener;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest;
public class GPSFixStoreListenerTest extends AbstractGPSFixStoreTest {
@Rule
public Timeout GPSFixStoreListenerTestTimeout = new Timeout(3 * 1000);
/**
* {@link MongoSensorFixStoreImpl} had broken synchronization of the listeners collection (add/removeListener
* methods were synchronized but notifyListeners was not synchronized).
*/
@Test(expected = TimoutRuntimeException.class)
public void lockingOfGPSFixStoreListenersIsWorkingCorrectly() throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(2);
// We need 3 listener instances to guarantee that the iterator isn't finished
// when adding another listener in the thread below.
store.addListener(new ListenerAwaitingBarier(barrier), device);
store.addListener(new ListenerAwaitingBarier(barrier), device);
store.addListener(new ListenerAwaitingBarier(barrier), device);
Thread thread = new Thread() {
public void run() {
try {
barrier.await(100, TimeUnit.MILLISECONDS);
// During iteration in the main thread this causes a modification that makes the iterator throw a
// ConcurrentModificationException on next()
store.addListener((DeviceIdentifier device, GPSFixMoving fix) -> {}, device);
barrier.await(100, TimeUnit.MILLISECONDS);
barrier.await(100, TimeUnit.MILLISECONDS);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
};
thread.start();
try {
store.storeFix(device, createFix(100, 10, 20, 30, 40));
} finally {
// This ensures that the thread is terminated when the test finishes
// JUnit may behave crazy if there are additional tests running after the test finished
thread.join(500);
}
}
private static class ListenerAwaitingBarier implements FixReceivedListener<GPSFixMoving> {
private final CyclicBarrier barrier;
public ListenerAwaitingBarier(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void fixReceived(DeviceIdentifier device, GPSFixMoving fix) {
try {
barrier.await(100, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new TimoutRuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
private static class TimoutRuntimeException extends RuntimeException {
private static final long serialVersionUID = 6349762933223278846L;
public TimoutRuntimeException(TimeoutException e) {
super(e);
}
}
}
@@ -3,12 +3,12 @@ package com.sap.sailing.domain.racelogsensortracking;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sse.common.Timed;
public interface SensorFixMapperFactory {
<FixT extends Timed, TrackT extends Track<?>> SensorFixMapper<FixT, TrackT, Competitor>
<FixT extends Timed, TrackT extends DynamicTrack<FixT>> SensorFixMapper<FixT, TrackT, Competitor>
createCompetitorMapper(Class<? extends RegattaLogDeviceMappingEvent<?>> eventType);
// <FixT extends Timed, TrackT extends Track<?>> SensorFixMapper<FixT, TrackT, Mark>
@@ -7,7 +7,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMapping
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.racelog.tracking.SensorFixMapper;
import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sse.common.Timed;
public class SensorFixMapperFactoryImpl implements SensorFixMapperFactory {
@@ -20,7 +20,7 @@ public class SensorFixMapperFactoryImpl implements SensorFixMapperFactory {
@Override
@SuppressWarnings("unchecked")
public <FixT extends Timed, TrackT extends Track<?>> SensorFixMapper<FixT, TrackT, Competitor>
public <FixT extends Timed, TrackT extends DynamicTrack<FixT>> SensorFixMapper<FixT, TrackT, Competitor>
createCompetitorMapper(Class<? extends RegattaLogDeviceMappingEvent<?>> eventType) {
for (ServiceReference<SensorFixMapper<?, ?, ?>> serviceReference : tracker.getServiceReferences()) {
SensorFixMapper<?, ?, ?> service = tracker.getService(serviceReference);
@@ -13,20 +13,19 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl;
/**
* {@link SensorFixMapper} to handle {@link BravoFix}es.
*
* {@link SensorFixMapper} implementation to handle {@link BravoFix}es.
*/
public class BravoDataFixMapper implements SensorFixMapper<DoubleVectorFix, DynamicSensorFixTrack<Competitor, BravoFix>, Competitor> {
public class BravoDataFixMapper implements SensorFixMapper<BravoFix, DynamicSensorFixTrack<Competitor, BravoFix>, Competitor> {
@Override
public DynamicSensorFixTrack<Competitor, BravoFix> getTrack(DynamicTrackedRace race, Competitor key) {
return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME,
() -> new BravoFixTrackImpl<Competitor>(key, BravoFixTrack.TRACK_NAME));
}
@Override
public void addFix(DynamicSensorFixTrack<Competitor, BravoFix> track, DoubleVectorFix fix) {
track.add(new BravoFixImpl(fix));
public BravoFix map(DoubleVectorFix fix) {
return new BravoFixImpl(fix);
}
@Override
@@ -287,10 +287,10 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (Competitor competitor : competitors) {
final String toAddress = competitor.getEmail();
if (toAddress != null) {
String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String leaderboardDisplayName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String competitorName = competitor.getName();
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
event.getId().toString(), leaderboard.getName(), DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
competitor.getId().toString(), NonGwtUrlHelper.INSTANCE);
String logoUrl = null;
final List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
@@ -299,7 +299,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName, event.getName(), competitorName, url,
sendInvitationEmail(locale, toAddress, leaderboardDisplayName, event.getName(), competitorName, url,
B.get(locale, "sailInSightAppName"), iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
@@ -392,7 +392,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
String emails, String iOSAppUrl, String androidAppUrl, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String leaderboardDisplayName = leaderboard.getDisplayName() == null ? leaderboard.getName() : leaderboard.getDisplayName();
String eventId = event.getId().toString();
String logoUrl = null;
List<ImageDescriptor> imagesWithTag = event.findImagesWithTag(MediaTagConstants.LOGO);
@@ -400,12 +400,12 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
logoUrl = imagesWithTag.get(0).getURL().toString();
}
// http://<host>/buoy-tender/checkin?event_id=<event-id>&leaderboard_name=<leaderboard-name>
String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboardName,
String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboard.getName(),
eventId, NonGwtUrlHelper.INSTANCE);
for (String toAddress : emailArray) {
try {
final ResourceBundleStringMessages B = RaceLogTrackingI18n.STRING_MESSAGES;
sendInvitationEmail(locale, toAddress, leaderboardName,
sendInvitationEmail(locale, toAddress, leaderboardDisplayName,
event.getName(), RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url, B.get(locale, "buoyPingerAppName"),
iOSAppUrl, androidAppUrl, logoUrl);
} catch (MailException e) {
@@ -30,9 +30,9 @@ import com.sap.sailing.domain.racelogtracking.DeviceMapping;
import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent;
import com.sap.sailing.domain.tracking.DynamicGPSFixTrack;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.RaceChangeListener;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sailing.domain.tracking.TrackingDataLoader;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sailing.domain.tracking.impl.TrackedRaceStatusImpl;
@@ -43,8 +43,6 @@ import com.sap.sse.common.Timed;
import com.sap.sse.common.Util;
import com.sap.sse.common.WithID;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* This class listens to RaceLog Events, changes to the race and fix loading events and properly handles mappings and
@@ -60,7 +58,6 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
public class FixLoaderAndTracker implements TrackingDataLoader {
private static final Logger logger = Logger.getLogger(FixLoaderAndTracker.class.getName());
protected final DynamicTrackedRace trackedRace;
private final NamedReentrantReadWriteLock loadingFromFixStoreLock;
private final SensorFixStore sensorFixStore;
private final GPSFixStore gpsFixStore;
private RegattaLogDeviceMappings<WithID> deviceMappings;
@@ -102,7 +99,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
if (preemptiveStopRequested.get()) {
return;
}
SensorFixMapper<DoubleVectorFix, DynamicSensorFixTrack<Competitor, SensorFix>, Competitor> mapper = sensorFixMapperFactory
SensorFixMapper<SensorFix, DynamicSensorFixTrack<Competitor, SensorFix>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
DynamicSensorFixTrack<Competitor, SensorFix> track = mapper.getTrack(trackedRace,
event.getMappedTo());
@@ -158,8 +155,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
this.gpsFixStore = new GPSFixStoreImpl(sensorFixStore);
this.sensorFixMapperFactory = sensorFixMapperFactory;
this.trackedRace = trackedRace;
loadingFromFixStoreLock = new NamedReentrantReadWriteLock(
"Loading from SensorFix store lock for tracked race " + trackedRace.getRace().getName(), false);
startTracking();
}
@@ -171,28 +166,38 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
mapping.getRegattaLogEvent().accept(new MappingEventVisitor() {
@Override
public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) {
SensorFixMapper<Timed, Track<?>, Competitor> mapper = sensorFixMapperFactory
SensorFixMapper<Timed, DynamicTrack<Timed>, Competitor> mapper = sensorFixMapperFactory
.createCompetitorMapper(event.getClass());
Track<?> track = mapper.getTrack(trackedRace, event.getMappedTo());
try {
sensorFixStore.loadFixes((DoubleVectorFix fix) -> mapper.addFix(track, fix), mapping.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), true);
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
logger.log(Level.WARNING, "Could not load track for competitor: " + mapping.getMappedTo()
+ "; device: " + mapping.getDevice());
DynamicTrack<Timed> track = mapper.getTrack(trackedRace, event.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
sensorFixStore.loadFixes((DoubleVectorFix fix) -> mapper.addFix(track, fix), mapping.getDevice(),
timeRangeToLoad.from(), timeRangeToLoad.to(), true);
} catch (NoCorrespondingServiceRegisteredException | TransformationException e) {
logger.log(Level.WARNING, "Could not load track for competitor: " + mapping.getMappedTo()
+ "; device: " + mapping.getDevice());
}
}
}
@Override
public void visit(RegattaLogDeviceCompetitorMappingEvent event) {
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(event.getMappedTo());
try {
@SuppressWarnings({ "unchecked" })
DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping;
gpsFixStore.loadCompetitorTrack(track, competitorMapping, getStartOfTracking(),
getEndOfTracking());
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
if (track != null) {
// for split-fleet racing, device mappings coming from the regatta log may not be relevant
// for the trackedRace because the competitors may not compete in it; in this case, the
// competitor retrieved from the mapping event does not have a track in trackedRace
try {
@SuppressWarnings({ "unchecked" })
DeviceMapping<Competitor> competitorMapping = (DeviceMapping<Competitor>) mapping;
gpsFixStore.loadCompetitorTrack(track, competitorMapping, getStartOfTracking(),
getEndOfTracking());
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not load competitor track " + mapping.getMappedTo());
}
}
}
@@ -244,7 +249,8 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
protected void startTracking() {
trackedRace.addListener(raceChangeListener);
this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs());
this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs(),
trackedRace.getRace().getName());
}
private synchronized void waitForLoadingToFinishRunning() {
@@ -277,7 +283,16 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
});
}
private void updateConcurrent(final Runnable updateCallback) {
/**
* This method runs the given update callback in a separate {@link Thread} by handling technical concurrency aspects
* and potential {@link #preemptiveStopRequested preemptive stop requests} internally. Thus, it separates the
* functional updating process from technical aspects.
*
* @param updateCallback
* the {@link Runnable} callback used to run the update
*/
// TODO Consider using a thread pool here, after merging bug3864 into master
private void updateAsyncInternal(final Runnable updateCallback) {
synchronized (FixLoaderAndTracker.this) {
activeLoaders.incrementAndGet();
setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5);
@@ -290,17 +305,14 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
if (!preemptiveStopRequested.get()) {
trackedRace.lockForSerializationRead();
setStatusAndProgress(TrackedRaceStatusEnum.LOADING, 0.5);
LockUtil.lockForWrite(loadingFromFixStoreLock);
synchronized (FixLoaderAndTracker.this) {
FixLoaderAndTracker.this.notifyAll();
}
updateCallback.run();
}
} finally {
LockUtil.unlockAfterWrite(loadingFromFixStoreLock);
synchronized (FixLoaderAndTracker.this) {
int currentActiveLoaders;
currentActiveLoaders = activeLoaders.decrementAndGet();
int currentActiveLoaders = activeLoaders.decrementAndGet();
FixLoaderAndTracker.this.notifyAll();
if (currentActiveLoaders == 0) {
setStatusAndProgress(stopRequested.get() ? TrackedRaceStatusEnum.FINISHED
@@ -328,27 +340,28 @@ public class FixLoaderAndTracker implements TrackingDataLoader {
}
private class FixLoaderDeviceMappings extends RegattaLogDeviceMappings<WithID> {
public FixLoaderDeviceMappings(Iterable<RegattaLog> initialRegattaLogs) {
super(initialRegattaLogs);
public FixLoaderDeviceMappings(Iterable<RegattaLog> initialRegattaLogs, String raceNameForLock) {
super(initialRegattaLogs, raceNameForLock);
}
@Override
protected void updateMappings() {
updateConcurrent(() -> {
FixLoaderDeviceMappings.super.updateMappings();
// add listeners for devices in mappings already present
forEachDevice((device) -> sensorFixStore.addListener(listener, device));
});
updateAsyncInternal(FixLoaderDeviceMappings.super::updateMappings);
}
@Override
protected void mappingRemoved(DeviceMappingWithRegattaLogEvent<WithID> mapping) {
final DeviceIdentifier device = mapping.getDevice();
if(!hasMappingForDevice(device)) {
sensorFixStore.removeListener(listener, device);
}
// TODO if tracks are always associated to only one device mapping, we could remove tracks here
// TODO remove listener from store if there is no mapping left for the DeviceIdentifier
}
@Override
protected void mappingAdded(DeviceMappingWithRegattaLogEvent<WithID> mapping) {
// The listener is first added to not lose any fix after loading the initial fixes and adding the listener.
sensorFixStore.addListener(listener, mapping.getDevice());
loadFixes(getTrackingTimeRange().intersection(mapping.getTimeRange()), mapping);
}
@@ -30,6 +30,8 @@ import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.common.WithID;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
/**
* Holds DeviceMappings to make it possible to track changes. This makes it possible to only process mappings that
@@ -46,6 +48,16 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* sure to not produce a memory leak.
*/
private final Set<RegattaLog> knownRegattaLogs = new HashSet<>();
/**
* Lock object to be used when accessing {@link #knownRegattaLogs}.
*/
private final NamedReentrantReadWriteLock knownRegattaLogsLock;
/**
* Lock object to be used when accessing {@link #mappings} or {@link #mappingsByDevice}.
*/
private final NamedReentrantReadWriteLock mappingsLock;
private final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> mappings = new HashMap<>();
private final Map<DeviceIdentifier, List<DeviceMappingWithRegattaLogEvent<ItemT>>> mappingsByDevice = new HashMap<>();
@@ -84,11 +96,16 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
};
};
public RegattaLogDeviceMappings(Iterable<RegattaLog> initialRegattaLogs) {
public RegattaLogDeviceMappings(Iterable<RegattaLog> initialRegattaLogs, String raceNameForLock) {
mappingsLock = new NamedReentrantReadWriteLock("DeviceMapping lock for race " + raceNameForLock, false);
knownRegattaLogsLock = new NamedReentrantReadWriteLock("Lock for known RegattaLogs of race " + raceNameForLock, false);
final boolean hasRegattaLogs;
synchronized (knownRegattaLogs) {
LockUtil.lockForWrite(knownRegattaLogsLock);
try {
initialRegattaLogs.forEach(this::addRegattaLogUnlocked);
hasRegattaLogs = !knownRegattaLogs.isEmpty();
} finally {
LockUtil.unlockAfterWrite(knownRegattaLogsLock);
}
if (hasRegattaLogs) {
updateMappings();
@@ -96,9 +113,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
}
public void addRegattaLog(RegattaLog regattaLog) {
synchronized (knownRegattaLogs) {
addRegattaLogUnlocked(regattaLog);
}
LockUtil.executeWithWriteLock(knownRegattaLogsLock, () -> addRegattaLogUnlocked(regattaLog));
updateMappings();
}
@@ -108,39 +123,19 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
}
public void stop() {
synchronized (knownRegattaLogs) {
LockUtil.executeWithWriteLock(knownRegattaLogsLock, () -> {
knownRegattaLogs.forEach((log) -> log.removeListener(regattaLogEventVisitor));
knownRegattaLogs.clear();
}
});
}
protected void updateMappings() {
try {
updateMappings(true);
} catch (Exception e) {
logger.warning("Could not update device mappings");
logger.log(Level.WARNING, "Could not update device mappings", e);
}
}
/**
* Used internally to access {@link #mappings} with a defensive copy to not run into concurrency issues if this is
* called while an update is done.
*
* @return the mappings
*/
private synchronized Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> getDeviceMappings() {
return new HashMap<>(mappings);
}
/**
* Used internally to access {@link #mappingsByDevice} with a defensive copy to not run into concurrency issues if
* this is called while an update is done.
*
* @return the mappings
*/
private synchronized Map<DeviceIdentifier, List<DeviceMappingWithRegattaLogEvent<ItemT>>> getMappingsByDevice() {
return new HashMap<>(mappingsByDevice);
}
/**
* Calls the given callback for every known mapping that's currently known.
@@ -157,7 +152,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* @param callback the callback to call for every mapped device
*/
public void forEachDevice(Consumer<DeviceIdentifier> callback) {
getMappingsByDevice().keySet().forEach(callback::accept);
LockUtil.executeWithReadLock(mappingsLock, () -> mappingsByDevice.keySet().forEach(callback::accept));
}
/**
@@ -166,12 +161,14 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
* @param callback the callback to call for every known mapping
*/
public void forEachMapping(BiConsumer<ItemT, DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : getDeviceMappings().entrySet()) {
ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping);
LockUtil.executeWithReadLock(mappingsLock, () -> {
for (Map.Entry<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> entry : mappings.entrySet()) {
ItemT item = entry.getKey();
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : entry.getValue()) {
callback.accept(item, mapping);
}
}
}
});
}
/**
@@ -188,16 +185,27 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
*/
public void forEachMappingOfDeviceIncludingTimePoint(DeviceIdentifier device, TimePoint timePoint,
Consumer<DeviceMappingWithRegattaLogEvent<ItemT>> callback) {
List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDevice;
synchronized (this) {
mappingsForDevice = mappingsByDevice.get(device);
}
if (mappingsForDevice != null) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) {
if (mapping.getTimeRange().includes(timePoint)) {
callback.accept(mapping);
LockUtil.executeWithReadLock(mappingsLock, () -> {
List<DeviceMappingWithRegattaLogEvent<ItemT>> mappingsForDevice = mappingsByDevice.get(device);
if (mappingsForDevice != null) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : mappingsForDevice) {
if (mapping.getTimeRange().includes(timePoint)) {
callback.accept(mapping);
}
}
}
});
}
/**
* @return true if there is at least one mapping for the given {@link DeviceIdentifier}, false otherwise
*/
public boolean hasMappingForDevice(DeviceIdentifier device) {
LockUtil.lockForRead(mappingsLock);
try {
return mappingsByDevice.containsKey(device);
} finally {
LockUtil.unlockAfterRead(mappingsLock);
}
}
@@ -214,9 +222,7 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
}
protected void forEachRegattaLog(Consumer<RegattaLog> regattaLogConsumer) {
synchronized (knownRegattaLogs) {
knownRegattaLogs.forEach(regattaLogConsumer);
}
LockUtil.executeWithReadLock(knownRegattaLogsLock, () -> knownRegattaLogs.forEach(regattaLogConsumer));
}
/**
@@ -233,58 +239,58 @@ public abstract class RegattaLogDeviceMappings<ItemT extends WithID> {
private final <FixT extends Timed, TrackT extends DynamicTrack<FixT>> void updateMappings(boolean loadIfNotCovered) {
// TODO remove fixes, if mappings have been removed
// check if there are new time ranges not covered so far
Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings();
updateMappings(newMappings, loadIfNotCovered);
}
/**
* Used internally to update the internal set of mappings to the state of the new mappings.
* This ensures that the internal state is updated and all new and changed mappings are correctly processed.
*
* @param newMappings the new mappings
* @param loadIfNotCovered true to inform about new/changed/removed mappings
*/
private synchronized void updateMappings(Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings,
boolean loadIfNotCovered) {
if (loadIfNotCovered) {
Set<ItemT> itemsToProcess = new HashSet<ItemT>(mappings.keySet());
itemsToProcess.addAll(newMappings.keySet());
for(ItemT item : itemsToProcess) {
if(!newMappings.containsKey(item)) {
mappings.get(item).forEach(this::mappingRemoved);
} else {
final List<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = mappings.containsKey(item)
? mappings.get(item) : Collections.emptyList();
for (DeviceMappingWithRegattaLogEvent<ItemT> newMapping : newMappings.get(item)) {
DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping,
oldMappings);
if (oldMapping == null) {
mappingAdded(newMapping);
} else if (newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChanged(oldMapping, newMapping);
}
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings = calculateMappings();
final Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> oldMappings = new HashMap<>();
LockUtil.lockForWrite(mappingsLock);
try {
oldMappings.putAll(mappings);
mappings.clear();
mappings.putAll(newMappings);
mappingsByDevice.clear();
for (ItemT item : newMappings.keySet()) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : newMappings.get(item)) {
List<DeviceMappingWithRegattaLogEvent<ItemT>> list = mappingsByDevice.get(mapping.getDevice());
if (list == null) {
list = new ArrayList<>();
mappingsByDevice.put(mapping.getDevice(), list);
}
oldMappings.forEach(this::mappingRemoved);
list.add(mapping);
}
}
} finally {
LockUtil.unlockAfterWrite(mappingsLock);
}
mappings.clear();
mappings.putAll(newMappings);
mappingsByDevice.clear();
for (ItemT item : newMappings.keySet()) {
for (DeviceMappingWithRegattaLogEvent<ItemT> mapping : newMappings.get(item)) {
List<DeviceMappingWithRegattaLogEvent<ItemT>> list = mappingsByDevice.get(mapping.getDevice());
if (list == null) {
list = new ArrayList<>();
mappingsByDevice.put(mapping.getDevice(), list);
}
list.add(mapping);
}
if (loadIfNotCovered) {
calculateDiff(oldMappings, newMappings);
}
}
private void calculateDiff(Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> previousMappings,
Map<ItemT, List<DeviceMappingWithRegattaLogEvent<ItemT>>> newMappings) {
Set<ItemT> itemsToProcess = new HashSet<ItemT>(previousMappings.keySet());
itemsToProcess.addAll(newMappings.keySet());
for(ItemT item : itemsToProcess) {
if(!newMappings.containsKey(item)) {
previousMappings.get(item).forEach(this::mappingRemoved);
} else {
final List<DeviceMappingWithRegattaLogEvent<ItemT>> oldMappings = previousMappings.containsKey(item)
? previousMappings.get(item) : Collections.emptyList();
for (DeviceMappingWithRegattaLogEvent<ItemT> newMapping : newMappings.get(item)) {
DeviceMappingWithRegattaLogEvent<ItemT> oldMapping = findAndRemoveMapping(newMapping,
oldMappings);
if (oldMapping == null) {
mappingAdded(newMapping);
} else if (newMapping.getTimeRange().equals(oldMapping.getTimeRange())) {
mappingChanged(oldMapping, newMapping);
}
}
oldMappings.forEach(this::mappingRemoved);
}
}
}
/**
* Called when a {@link DeviceMapping} was removed.
*
@@ -1,6 +1,10 @@
package com.sap.sailing.domain.abstractlog.regatta;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.AbstractLog;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDefineMarkEvent;
import com.sap.sse.common.WithID;
/**
* This log gathers information for regattas. {@link FlexibleLeaderboard}s are considered
@@ -9,5 +13,5 @@ import com.sap.sse.common.WithID;
* uses the log supplied by its {@code Regatta}, whereas a {@link FlexibleLeaderboard} manages the log itself.
*/
public interface RegattaLog extends AbstractLog<RegattaLogEvent, RegattaLogEventVisitor>, WithID {
public void revokeDefineMarkEventAndRelatedDeviceMappings(final RegattaLogDefineMarkEvent event, final AbstractLogEventAuthor author, final Logger logger);
}
@@ -1,14 +1,21 @@
package com.sap.sailing.domain.abstractlog.regatta.impl;
import java.io.Serializable;
import java.util.List;
import java.util.logging.Logger;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
import com.sap.sailing.domain.abstractlog.impl.AbstractLogImpl;
import com.sap.sailing.domain.abstractlog.impl.AllEventsOfTypeFinder;
import com.sap.sailing.domain.abstractlog.impl.LogEventComparator;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDefineMarkEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEventImpl;
import com.sap.sailing.domain.base.Mark;
import com.sap.sse.common.WithID;
public class RegattaLogImpl extends AbstractLogImpl<RegattaLogEvent, RegattaLogEventVisitor> implements RegattaLog {
@@ -26,4 +33,26 @@ public class RegattaLogImpl extends AbstractLogImpl<RegattaLogEvent, RegattaLogE
protected RegattaLogEvent createRevokeEvent(AbstractLogEventAuthor author, RegattaLogEvent toRevoke, String reason) {
return new RegattaLogRevokeEventImpl(author, toRevoke, reason);
}
@Override
public void revokeDefineMarkEventAndRelatedDeviceMappings(final RegattaLogDefineMarkEvent event, final AbstractLogEventAuthor author, final Logger logger) {
if (event != null){
RegattaLogEvent revokeEvent = new RegattaLogRevokeEventImpl(author, event, "Revoked by AdminConsole (RaceLogTracking)");
this.add(revokeEvent);
final List<RegattaLogEvent> regattaLogDeviceMarkMappingEvents = new AllEventsOfTypeFinder<>(this, /* only unrevoked */ true, RegattaLogDeviceMappingEvent.class).analyze();
for (RegattaLogEvent deviceMappingEvent : regattaLogDeviceMarkMappingEvents) {
@SuppressWarnings("unchecked") // The list can only contain device mapping events
WithID withID = ((RegattaLogDeviceMappingEvent<WithID>) deviceMappingEvent).getMappedTo();
if (withID instanceof Mark && ((Mark) withID).getId().toString().equals(event.getMark().getId().toString())) {
RegattaLogEvent revokeDeviceMapping = new RegattaLogRevokeEventImpl(author, deviceMappingEvent,
"Revoked because AdminConsole (RaceLogTracking) revoked mark");
this.add(revokeDeviceMapping);
}
}
} else {
logger.warning("Could not revoke event for mark. Mark not found in RegattaLog.");
}
}
}
@@ -0,0 +1,20 @@
package com.sap.sailing.domain.tracking;
import com.sap.sse.common.Timed;
/**
* {@link Track} specialization, which is mapped to a specific type of items.
*
* @param <ItemType>
* the type of item this track is mapped to
* @param <FixType>
* the type of fix that is contained in this track
*/
public interface MappedTrack<ItemType, FixType extends Timed> extends Track<FixType> {
/**
* @return the item this track is mapped to.
*/
ItemType getTrackedItem();
}
@@ -0,0 +1,39 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.tracking.MappedTrack;
import com.sap.sse.common.Timed;
import com.sap.sse.util.impl.ArrayListNavigableSet;
/**
* Default implementation of {@link MappedTrack} interface.
*
* @param <ItemType>
* the type of item this track is mapped to
* @param <FixType>
* the type of fix that is contained in this track
*/
public class MappedTrackImpl<ItemType, FixType extends Timed> extends TrackImpl<FixType>
implements MappedTrack<ItemType, FixType> {
private static final long serialVersionUID = 6165693342087329096L;
private final ItemType trackedItem;
/** @see TrackImpl#TrackImpl(String) */
public MappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
super(nameForReadWriteLock);
this.trackedItem = trackedItem;
}
/** @see TrackImpl#TrackImpl(ArrayListNavigableSet, String) */
protected MappedTrackImpl(ItemType trackedItem, ArrayListNavigableSet<Timed> fixes, String nameForReadWriteLock) {
super(fixes, nameForReadWriteLock);
this.trackedItem = trackedItem;
}
@Override
public ItemType getTrackedItem() {
return trackedItem;
}
}
@@ -2103,6 +2103,92 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki
}
}
/**
* See bug 3752 comment #6: when in an earlier race a competitor does not have a fleet assigned and the other one does, but in
* a later race in the same series both competitors are assigned to the same fleet, the authoritative answer based on the fleets
* must be that they compare equal; an earlier default based on "extreme fleet" considerations must be discarded if fleet equality
* is definitely established.
*/
@Test
public void testTotalRankComparatorForOrderedSplitFleetsWithUnknownFleetInPreviousRaceAndSameFleetInLaterRace() throws NoWindException {
series = new ArrayList<Series>();
// -------- qualification series ------------
{
List<Fleet> qualificationFleets = new ArrayList<Fleet>();
for (String qualificationFleetName : new String[] { "Yellow", "Blue" }) {
qualificationFleets.add(new FleetImpl(qualificationFleetName));
}
List<String> qualificationRaceColumnNames = new ArrayList<String>();
qualificationRaceColumnNames.add("Q");
Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, /* isFleetsCanRunInParallel */ true, qualificationFleets, qualificationRaceColumnNames, /* trackedRegattaRegistry */ null);
// discard the one and only qualification race; it doesn't score
qualificationSeries.setResultDiscardingRule(new ThresholdBasedResultDiscardingRuleImpl(new int[] { 1 }));
series.add(qualificationSeries);
}
// -------- final series ------------
{
List<Fleet> finalFleets = new ArrayList<Fleet>();
int fleetOrdering = 1;
for (String finalFleetName : new String[] { "Gold", "Silver" }) {
finalFleets.add(new FleetImpl(finalFleetName, fleetOrdering++));
}
List<String> finalRaceColumnNames = Arrays.asList("F1", "F2");
Series finalSeries = new SeriesImpl("Final", /* isMedal */false, /* isFleetsCanRunInParallel */ true, finalFleets, finalRaceColumnNames, /* trackedRegattaRegistry */ null);
series.add(finalSeries);
}
final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true);
Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null,
series, /* persistent */false, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", /* course area */null, OneDesignRankingMetric::new);
List<Competitor> competitors = createCompetitors(12);
final int firstYellowCompetitorIndex = 3;
List<Competitor> yellow = new ArrayList<>(competitors.subList(firstYellowCompetitorIndex, firstYellowCompetitorIndex+6));
List<Competitor> blue = new ArrayList<>(competitors);
blue.removeAll(yellow);
Collections.shuffle(yellow);
Collections.shuffle(blue);
final int firstGoldCompetitorIndex = 5;
List<Competitor> gold = new ArrayList<>(competitors.subList(firstGoldCompetitorIndex, firstGoldCompetitorIndex+6));
List<Competitor> silver = new ArrayList<>(competitors);
silver.removeAll(gold);
Collections.shuffle(gold);
Collections.shuffle(silver);
List<Competitor> lastRaceSilver = new ArrayList<>(silver);
final Competitor theUntrackedCompetitorInLastRace = lastRaceSilver.get(lastRaceSilver.size()-1);
lastRaceSilver.remove(theUntrackedCompetitorInLastRace); // one participant accidentally not tracked; expected to end up between silver and gold
List<Competitor> medal = new ArrayList<>(gold.subList(0, 2)); // take two gold race participants as medal race participants
List<Competitor> lastRaceGold = new ArrayList<>(gold);
lastRaceGold.removeAll(medal); // no medal race participant participates in the last race's gold fleet
Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]);
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
RaceColumn qColumn = series.get(0).getRaceColumnByName("Q");
TrackedRace qYellow = new MockedTrackedRaceWithStartTimeAndRanks(now, yellow);
qColumn.setTrackedRace(qColumn.getFleetByName("Yellow"), qYellow);
TrackedRace qBlue = new MockedTrackedRaceWithStartTimeAndRanks(now, blue);
qColumn.setTrackedRace(qColumn.getFleetByName("Blue"), qBlue);
RaceColumn f1Column = series.get(1).getRaceColumnByName("F1");
TrackedRace f1Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, gold);
f1Column.setTrackedRace(f1Column.getFleetByName("Gold"), f1Gold);
final List<Competitor> f1SilverWithOneMissing = new ArrayList<>(silver);
final Competitor missing = silver.get(silver.size()-1); // this competitor is not assigned to a fleet for F1, but for F2
f1SilverWithOneMissing.remove(missing);
TrackedRace f1Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, f1SilverWithOneMissing);
f1Column.setTrackedRace(f1Column.getFleetByName("Silver"), f1Silver);
leaderboard.getScoreCorrection().correctScore(missing, f1Column, silver.size()); // set score to what it would have been
RaceColumn f2Column = series.get(1).getRaceColumnByName("F2");
TrackedRace f2Gold = new MockedTrackedRaceWithStartTimeAndRanks(now, gold);
f2Column.setTrackedRace(f2Column.getFleetByName("Gold"), f2Gold);
TrackedRace f2Silver = new MockedTrackedRaceWithStartTimeAndRanks(now, silver);
f2Column.setTrackedRace(f2Column.getFleetByName("Silver"), f2Silver);
List<Competitor> rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later);
// the competitor missing from F1 Silver has worst score in both Silver fleet races and must rank last;
// with the bug still present, she would have ranked between Gold and Silver
assertEquals(missing, rankedCompetitors.get(rankedCompetitors.size()-1));
}
/**
* See bug 3798: special discarding rule that limits the number of discards for the final series; configured
* by {@link Series#getMaximumNumberOfDiscards()}.
@@ -0,0 +1,30 @@
package com.sap.sailing.domain.test.compiler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.junit.Test;
public class EclipseNeonConstructorReferenceSerializationTest {
/**
* We have serialization problems due to a regression in Eclipse Neon:
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=497879
*/
@Test
public void testConstructorReferenceSerializationThatFailsOnEclipseNeon() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
SerializableSupplier factory = TypeImplementation::new;
oos.writeObject(factory);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}
@@ -0,0 +1,8 @@
package com.sap.sailing.domain.test.compiler;
import java.io.Serializable;
import java.util.function.Supplier;
@FunctionalInterface
public interface SerializableSupplier extends Serializable, Supplier<TypeDefinition> {
}
@@ -0,0 +1,5 @@
package com.sap.sailing.domain.test.compiler;
public interface TypeDefinition {
}
@@ -0,0 +1,8 @@
package com.sap.sailing.domain.test.compiler;
public class TypeImplementation implements TypeDefinition {
public TypeImplementation() {
}
}
@@ -44,6 +44,7 @@ import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.leaderboard.ScoringScheme;
import com.sap.sailing.domain.leaderboard.impl.CompetitorProviderFromRaceColumnsAndRegattaLike;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.ranking.RankingMetric;
@@ -522,6 +523,11 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public void deregisterCompetitors(Iterable<Competitor> competitor) {
}
@Override
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
return null;
}
};
}
@@ -811,6 +817,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public void setStatus(TrackedRaceStatus newStatus) {
}
@Override
public void onStatusChanged(TrackingDataLoader loader, TrackedRaceStatus status) {
}
@@ -89,7 +89,7 @@ public abstract class AbstractRaceColumn extends SimpleAbstractRaceColumn implem
@Override
public TrackedRace getTrackedRace(Fleet fleet) {
return trackedRaces.get(fleet);
return trackedRaces == null ? null : trackedRaces.get(fleet);
}
@Override
@@ -386,6 +386,14 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
return boatClass;
}
@Override
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
if (competitorsProvider == null) {
competitorsProvider = new CompetitorProviderFromRaceColumnsAndRegattaLike(this);
}
return competitorsProvider;
}
@Override
public Iterable<Competitor> getAllCompetitors() {
Set<Competitor> result = new HashSet<Competitor>();
@@ -1,7 +1,10 @@
package com.sap.sailing.domain.leaderboard;
import com.sap.sailing.domain.leaderboard.impl.CompetitorProviderFromRaceColumnsAndRegattaLike;
import com.sap.sailing.domain.regattalike.HasRegattaLike;
public interface HasRaceColumnsAndRegattaLike extends HasRegattaLike, HasRaceColumns {
CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider();
}
@@ -66,7 +66,7 @@ public class LeaderboardDTOCache implements LeaderboardCache {
*/
private final boolean waitForLatestAnalyses;
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3);
/**
* A multi-threaded executor for the currently running leaderboard requests, executing the {@link Future}s currently
* pending.
@@ -101,7 +101,8 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI
return getOrCreateCompetitorsProvider().getAllCompetitors(raceColumn, fleet);
}
private CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
@Override
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
if (competitorsProvider == null) {
competitorsProvider = new CompetitorProviderFromRaceColumnsAndRegattaLike(this);
}
@@ -20,7 +20,6 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
@@ -149,20 +148,28 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private transient Map<com.sap.sse.common.Util.Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
/**
* Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
*/
private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
private final static int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors()/2, 3);
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
/**
* This executor needs to be a different one than {@link #executor} because the tasks run by {@link #executor}
* can depend on the results of the tasks run by {@link #raceDetailsExecutor}, and an {@link Executor} doesn't
* move a task that is blocked by waiting for another {@link FutureTask} to the side but blocks permanently,
* ending in a deadlock (one that cannot easily be detected by the Eclipse debugger either).
*/
private transient Executor raceDetailsExecutor;
private final static Executor raceDetailsExecutor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
/**
* Used to remove all these listeners from their tracked races when this servlet is {@link #destroy() destroyed}.
*/
private transient Set<CacheInvalidationListener> cacheInvalidationListeners;
private transient ThreadPoolExecutor executor;
private transient LiveLeaderboardUpdater liveLeaderboardUpdater;
@@ -343,16 +350,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
private void initTransientFields() {
this.raceDetailsAtEndOfTrackingCache = new HashMap<com.sap.sse.common.Util.Pair<TrackedRace,Competitor>, RunnableFuture<RaceDetails>>();
this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true));
this.cacheInvalidationListeners = new HashSet<CacheInvalidationListener>();
// When many updates are triggered in a short period of time by a single thread, ensure that the single thread
// providing the updates is not outperformed by all the re-calculations happening here. Leave at least one
// core to other things, but by using at least three threads ensure that no simplistic deadlocks may occur.
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
executor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
/* maximumPoolSize */ THREAD_POOL_SIZE,
/* keepAliveTime */ 60, TimeUnit.SECONDS,
/* workQueue */ new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY-1, /* daemon */ true));
}
@Override
@@ -11,6 +11,8 @@ import java.util.Set;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.RaceColumnInSeries;
import com.sap.sailing.domain.base.Series;
import com.sap.sailing.domain.common.NoWindError;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -113,6 +115,14 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
// the competitor scored in this or any subsequent columns
boolean needToResetO1ScoreUponNextValidResult = false;
boolean needToResetO2ScoreUponNextValidResult = false;
// Once we have established the fleet of a competitor in a series the fleet ordering for that competitor in that series
// cannot change anymore; if assigned to an ordered fleet, the ordering will have to remain unchanged throughout the fleet,
// and if assigned to an unordered fleet (ordering==0) the only change may be a re-assignment to a different unordered
// fleet but not to an ordered fleet. Therefore, once we know the fleet ordering for a competitor in a series for sure,
// we don't have to bother computing it again. It can be cached. See also bug 3838.
// The fleet stored here may not be the same fleet for the competitor for all columns in that series but
// it is guaranteed to have equal ordering as all fleets that the competitor is assigned to within the key series.
final Map<Series, Map<Competitor, Fleet>> fleetWithCorrectOrderingForCompetitorBySeries = new HashMap<>();
int defaultFleetBasedComparisonResult = 0; // relevant if no authoritative fleet-based comparison result was determined; based on extreme fleet vs. no fleet comparison
for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) {
needToResetO1ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore();
@@ -174,9 +184,11 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
preemptiveColumnResult = compareByMedalRaceParticipation(o1Score, o2Score);
}
if (preemptiveColumnResult == 0 && raceColumn.isTotalOrderDefinedByFleet()) {
final FleetComparisonResult compareByFleetResult = compareByFleet(raceColumn, o1, o2);
preemptiveColumnResult = compareByFleetResult.getAuthoritativeFleetComparisonResult();
if (defaultFleetBasedComparisonResult == 0) {
final FleetComparisonResult compareByFleetResult = compareByFleet(raceColumn, o1, o2, fleetWithCorrectOrderingForCompetitorBySeries);
if (compareByFleetResult.getAuthoritativeFleetComparisonResult() != null) {
preemptiveColumnResult = compareByFleetResult.getAuthoritativeFleetComparisonResult();
defaultFleetBasedComparisonResult = 0;
} else if (defaultFleetBasedComparisonResult == 0) {
defaultFleetBasedComparisonResult = compareByFleetResult.getDefaultFleetComparisonResultBasedOnUnknownFleetAssignment();
}
}
@@ -231,10 +243,14 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
private static class FleetComparisonResult {
/**
* Is non-{@code 0} if the two competitors have been identified as having raced in different fleets in
* {@code raceColumn} with those fleets having different {@link Fleet#getOrdering() orderings}.
* Evaluation of further comparison criteria is not necessary in this case.
* {@code raceColumn} with those fleets having different {@link Fleet#getOrdering() orderings}, or {@code 0}
* if the two competitors have been identified authoritatively having raced in the same fleet in
* {@code raceColumn}. Remains {@code null} if at least one competitor's fleet couldn't be identified.
* Evaluation of further comparison criteria is not necessary only if an authoritative non-{@code 0}
* answer was found. If fleet comparison has been calculated for all columns and no authoritative answer
* was found, the {@link #defaultFleetComparisonResultBasedOnUnknownFleetAssignment} result can be used.
*/
private final int authoritativeFleetComparisonResult;
private final Integer authoritativeFleetComparisonResult;
/**
* When for one of the two competitors compared the fleet in which she raced in a race column
@@ -248,14 +264,14 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
*/
private final int defaultFleetComparisonResultBasedOnUnknownFleetAssignment;
public FleetComparisonResult(int authoritativeFleetComparisonResult,
public FleetComparisonResult(Integer authoritativeFleetComparisonResult,
int defaultFleetComparisonResultBasedOnUnknownFleetAssignment) {
super();
this.authoritativeFleetComparisonResult = authoritativeFleetComparisonResult;
this.defaultFleetComparisonResultBasedOnUnknownFleetAssignment = defaultFleetComparisonResultBasedOnUnknownFleetAssignment;
}
public int getAuthoritativeFleetComparisonResult() {
public Integer getAuthoritativeFleetComparisonResult() {
return authoritativeFleetComparisonResult;
}
@@ -264,9 +280,10 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
}
}
private FleetComparisonResult compareByFleet(RaceColumn raceColumn, Competitor o1, Competitor o2) {
Fleet o1f = raceColumn.getFleetOfCompetitor(o1);
Fleet o2f = raceColumn.getFleetOfCompetitor(o2);
private FleetComparisonResult compareByFleet(final RaceColumn raceColumn, final Competitor o1, final Competitor o2,
final Map<Series, Map<Competitor, Fleet>> fleetWithCorrectOrderingForCompetitorBySeries) {
final Fleet o1f = getAFleetWithCorrectOrderingOfCompetitorFromCacheOrRaceColumnAndCache(raceColumn, o1, fleetWithCorrectOrderingForCompetitorBySeries);
final Fleet o2f = getAFleetWithCorrectOrderingOfCompetitorFromCacheOrRaceColumnAndCache(raceColumn, o2, fleetWithCorrectOrderingForCompetitorBySeries);
// if the fleet for both was identified because both were tracked in this column, then if the fleets
// don't compare equal, return the fleet comparison as result immediately. Example: o1 competed in Gold fleet,
// o2 in Silver fleet; Gold compares better to Silver, so o1 is compared better to o2.
@@ -277,18 +294,70 @@ public class LeaderboardTotalRankComparator implements Comparator<Competitor> {
} else {
// check if o1's fleet is best or worst in column; in that case, o1's membership in this fleet and the fact
// that o2 is not part of that fleet determines the result
result = new FleetComparisonResult(0, extremeFleetComparison(raceColumn, o1f));
result = new FleetComparisonResult(null, extremeFleetComparison(raceColumn, o1f));
}
} else if (o2f != null) {
// check if o2's fleet is best or worst in column; in that case, o2's membership in this fleet and the fact
// that o1 is not part of that fleet determines the result
result = new FleetComparisonResult(0, -extremeFleetComparison(raceColumn, o2f));
result = new FleetComparisonResult(null, -extremeFleetComparison(raceColumn, o2f));
} else {
result = new FleetComparisonResult(0, 0);
result = new FleetComparisonResult(null, 0);
}
return result;
}
/**
* Tries to find a fleet assignment for the {@code competitor} in the {@code fleetWithCorrectOrderingForCompetitorBySeries} cache for
* the {@link Series} corresponding with {@code RaceColumn} ({@code null} in case the column is not part of a {@link Regatta}).
* If found, that fleet is returned, although it may not be the exact fleet assignment for the competitor in that column, but
* at least it has the correct ordering which suffices for fleet comparisons.<p>
*
* If a fleet assignment is not found in the cache, the race column is {@link RaceColumn#getFleetOfCompetitor(Competitor) asked}
* for the competitor's fleet assignment. If a result is found, it is added to the {@code fleetWithCorrectOrderingForCompetitorBySeries}
* cache.
*/
private Fleet getAFleetWithCorrectOrderingOfCompetitorFromCacheOrRaceColumnAndCache(final RaceColumn raceColumn, final Competitor competitor,
final Map<Series, Map<Competitor, Fleet>> fleetWithCorrectOrderingForCompetitorBySeries) {
Fleet fleetWithCorrectOrdering = null;
final Series series;
if (raceColumn instanceof RaceColumnInSeries) {
series = ((RaceColumnInSeries) raceColumn).getSeries();
} else {
series = null;
}
Map<Competitor, Fleet> fleetForCompetitorInSeries = fleetWithCorrectOrderingForCompetitorBySeries.get(series);
if (fleetForCompetitorInSeries != null && fleetForCompetitorInSeries.containsKey(competitor)) {
fleetWithCorrectOrdering = fleetForCompetitorInSeries.get(competitor);
}
if (fleetWithCorrectOrdering == null) {
fleetWithCorrectOrdering = getFleetOfCompetitorFromRaceColumnAndCache(raceColumn, competitor,
fleetWithCorrectOrderingForCompetitorBySeries, series, fleetForCompetitorInSeries);
}
return fleetWithCorrectOrdering;
}
private Fleet getFleetOfCompetitorInRaceColumn(final RaceColumn raceColumn, final Competitor competitor) {
for (final Fleet fleet : raceColumn.getFleets()) {
if (Util.contains(getLeaderboard().getAllCompetitors(raceColumn, fleet), competitor)) {
return fleet;
}
}
return null;
}
private Fleet getFleetOfCompetitorFromRaceColumnAndCache(final RaceColumn raceColumn, final Competitor competitor,
final Map<Series, Map<Competitor, Fleet>> orderedFleetsForCompetitorsBySeries, final Series series,
Map<Competitor, Fleet> fleetForCompetitorInSeries) {
final Fleet fleetWithCorrectOrdering = getFleetOfCompetitorInRaceColumn(raceColumn, competitor);
if (fleetWithCorrectOrdering != null) {
if (fleetForCompetitorInSeries == null) {
fleetForCompetitorInSeries = new HashMap<>();
orderedFleetsForCompetitorsBySeries.put(series, fleetForCompetitorInSeries);
}
fleetForCompetitorInSeries.put(competitor, fleetWithCorrectOrdering);
}
return fleetWithCorrectOrdering;
}
/**
* If the race column only has one fleet, no decision is made and 0 is returned. Otherwise, if <code>fleet</code> is the
* best fleet with others in the column being worse, return "better" (lesser; -1). If <code>fleet</code> is the worst fleet
@@ -130,4 +130,9 @@ public class RegattaLeaderboardImpl extends AbstractLeaderboardImpl implements R
public BoatClass getBoatClass() {
return getRegatta().getBoatClass();
}
@Override
public CompetitorProviderFromRaceColumnsAndRegattaLike getOrCreateCompetitorsProvider() {
return getRegatta().getOrCreateCompetitorsProvider();
}
}
@@ -53,7 +53,7 @@ public class MarkPassingCalculator {
private static final Logger logger = Logger.getLogger(MarkPassingCalculator.class.getName());
private final MarkPassingUpdateListener listener;
private final static ExecutorService executor = new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime
.getRuntime().availableProcessors() - 1, 3),
.getRuntime().availableProcessors()/2, 3),
/* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3),
/* keepAliveTime */60, TimeUnit.SECONDS,
/* workQueue */new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1, /* daemon */ true));
@@ -150,8 +150,8 @@ public class CandidateFinderImpl implements CandidateFinder {
*/
public CandidateFinderImpl(DynamicTrackedRace race) {
this(race, new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime
.getRuntime().availableProcessors() - 1, 3),
/* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3),
.getRuntime().availableProcessors()/2, 3),
/* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors()/2, 3),
/* keepAliveTime */60, TimeUnit.SECONDS,
/* workQueue */new LinkedBlockingQueue<Runnable>(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1, /* daemon */ true)));
}
@@ -20,6 +20,10 @@ public enum EmptySensorFixStore implements SensorFixStore {
public void removeListener(FixReceivedListener<? extends Timed> listener) {
}
@Override
public void removeListener(FixReceivedListener<? extends Timed> listener, DeviceIdentifier device) {
}
@Override
public TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) {
return null;
@@ -1,20 +1,24 @@
package com.sap.sailing.domain.racelog.tracking;
import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.Track;
import com.sap.sse.common.Timed;
import com.sap.sse.common.WithID;
/**
* This is used by the fix tracking mechanism to make fixes available in a track.
* This makes it possible to e.g. map fixes to another type before adding them to the track.
* <p>
*
* Implementations need to be registered as OSGi service to be correctly found.
*
* @param <FixT> the type of fix this mapper handles
* @param <TrackT> the type of track this mapper adds the fixes to
* @param <K> the type of item the track is mapped to
*/
public interface SensorFixMapper<FixT extends Timed, TrackT extends Track<?>, K extends WithID> {
public interface SensorFixMapper<FixT extends Timed, TrackT extends DynamicTrack<FixT>, K extends WithID> {
/**
* Extracts the track from the TrackedRace or creates a new one if it doesn't already exist.
@@ -26,12 +30,23 @@ public interface SensorFixMapper<FixT extends Timed, TrackT extends Track<?>, K
TrackT getTrack(DynamicTrackedRace race, K key);
/**
* Adds a fix to the given Track. This can also be used to map fixes to another type if necessary.
* This default implementation maps the given {@link DoubleVectorFix} using the {@link #map(DoubleVectorFix)} method
* first. Afterwards, the mapped fix will be added to the given track.
*
* @param track the track to add the fix to
* @param fix the fix to add
*/
void addFix(TrackT track, FixT fix);
default void addFix(TrackT track, DoubleVectorFix fix) {
track.add(map(fix));
}
/**
* This method maps the given {@link DoubleVectorFix} to the required fix type.
*
* @param fix the {@link DoubleVectorFix} to map
* @return the mapped fix type instance
*/
FixT map(DoubleVectorFix fix);
/**
* Checks if this mapper can be used for tracks/fixes that are associated to a given RegattaLog device mapping event.
@@ -59,6 +59,11 @@ public interface SensorFixStore {
*/
void removeListener(FixReceivedListener<? extends Timed> listener);
/**
* Remove the registrations of the listener for the given device.
*/
void removeListener(FixReceivedListener<? extends Timed> listener, DeviceIdentifier device);
TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) throws TransformationException,
NoCorrespondingServiceRegisteredException;
@@ -473,13 +473,13 @@ public abstract class AbstractRankingMetric implements RankingMetric {
/**
* Get's <code>who</code>'s current tracked leg at <code>timePoint</code>, or <code>null</code> if <code>who</code> hasn't
* started at <code>timePoint</code> yet, or <code>who</code>'s tracked leg for the last leg if <code>who</code> has
* already finished the race at <code>timePoint</code>.
* already finished the race at <code>timePoint</code> or
*/
protected TrackedLegOfCompetitor getCurrentLegOrLastLegIfAlreadyFinished(Competitor who, TimePoint timePoint) {
TrackedLegOfCompetitor currentLegWho = getTrackedRace().getCurrentLeg(who, timePoint);
if (currentLegWho == null) { // already finished or not yet started; if already finished, use last leg
final Waypoint lastWaypoint = getTrackedRace().getRace().getCourse().getLastWaypoint();
if (lastWaypoint != null) { // could be an empty course
if (lastWaypoint != null && getTrackedRace().getRace().getCourse().getNumberOfWaypoints() > 1) { // could be an empty course
final TrackedLeg lastTrackedLeg = getTrackedRace().getTrackedLegFinishingAt(lastWaypoint);
TrackedLegOfCompetitor whosLastTrackedLeg = lastTrackedLeg.getTrackedLeg(who);
if (whosLastTrackedLeg.hasFinishedLeg(timePoint)) {
@@ -0,0 +1,15 @@
package com.sap.sailing.domain.tracking;
import com.sap.sse.common.Timed;
/**
* Extension of {@link MappedTrack} interface, which also implements {@link DynamicTrack} interface.
*
* @param <ItemType>
* the type of item this track is mapped to
* @param <FixType>
* the type of fix that is contained in this track
*/
public interface DynamicMappedTrack<ItemType, FixType extends Timed>
extends MappedTrack<ItemType, FixType>, DynamicTrack<FixType> {
}
@@ -185,6 +185,8 @@ public interface DynamicTrackedRace extends TrackedRace {
*/
void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind);
void setStatus(TrackedRaceStatus newStatus);
void onStatusChanged(TrackingDataLoader source, TrackedRaceStatus status);
/**
@@ -29,7 +29,7 @@ import com.sap.sse.common.Util;
*
* @param <ItemType>
*/
public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends Track<FixType> {
public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends MappedTrack<ItemType, FixType> {
static final long DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_SPEED = 10000; // makes for a 5s half-side interval
static final Speed DEFAULT_MAX_SPEED_FOR_SMOOTHING = new KnotSpeedImpl(40);
@@ -40,8 +40,6 @@ public interface GPSFixTrack<ItemType, FixType extends GPSFix> extends Track<Fix
void removeListener(GPSTrackListener<ItemType, FixType> listener);
ItemType getTrackedItem();
/**
* Computes the distance traveled on the smoothened track between the
* {@link #getEstimatedPosition(TimePoint, boolean) estimated positions} at <code>from</code> and <code>to</code>.
@@ -27,6 +27,10 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
* A tracker may be {@link #stop(boolean) stopped}. In this case, it will no longer receive any data at all. Stopping a tracker
* will not modify the {@link Regatta} and the {@link TrackedRegatta} with regards to their ownership of their
* {@link RaceDefiniion} and {@link TrackedRace}, respectively.
* <p>
*
* A {@link RaceTracker} controls the lifecycle of a {@link TrackedRace} in contrast to a {@link TrackingDataLoader} which just
* contributes to the composite status but does not control the lifecycle.
*
* @author Axel Uhl (d043530)
*
@@ -13,7 +13,8 @@ import com.sap.sse.common.WithID;
* @param <ItemType> the type of item this track is mapped to
* @param <FixT> the type of fix that is contained in this track
*/
public interface SensorFixTrack<ItemType extends WithID & Serializable, FixT extends SensorFix> extends Track<FixT> {
public interface SensorFixTrack<ItemType extends WithID & Serializable, FixT extends SensorFix>
extends MappedTrack<ItemType, FixT> {
/**
* Value names to be used when reading values from {@link SensorFix}es by calling {@link SensorFix#get(String)}.
@@ -26,11 +27,6 @@ public interface SensorFixTrack<ItemType extends WithID & Serializable, FixT ext
void removeListener(SensorFixTrackListener<ItemType, FixT> listener);
/**
* @return the item this tracked is mapped to.
*/
ItemType getTrackedItem();
/**
* @return the associated track name by which this track can be obtained from the {@link TrackedRace}.
*/
@@ -1,4 +1,10 @@
package com.sap.sailing.domain.tracking;
/**
* Marker interface for classes that load data for a {@link TrackedRace} and therefore contribute to the composite
* status of a tracked race. A {@link TrackingDataLoader} must update it's state in the associated {@link TrackedRace}
* by calling {@link DynamicTrackedRace#onStatusChanged(TrackingDataLoader, TrackedRaceStatus)} with a self-reference
* and the actual status.
*/
public interface TrackingDataLoader {
}
@@ -0,0 +1,36 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.tracking.DynamicMappedTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sse.common.Timed;
/**
* Default implementation of {@link DynamicMappedTrack} interface.
*
* @param <ItemType>
* the type of item this track is mapped to
* @param <FixType>
* the type of fix that is contained in this track
*/
public class DynamicMappedTrackImpl<ItemType, FixType extends Timed> extends MappedTrackImpl<ItemType, FixType>
implements DynamicMappedTrack<ItemType, FixType> {
private static final long serialVersionUID = -8705397260656473352L;
public DynamicMappedTrackImpl(ItemType trackedItem, String nameForReadWriteLock) {
super(trackedItem, nameForReadWriteLock);
}
/**
* Simply makes the superclass method public as now we need to expose it via the {@link DynamicTrack} interface.
*/
@Override
public boolean add(FixType fix) {
return super.add(fix);
}
@Override
public boolean add(FixType fix, boolean replace) {
return super.add(fix, replace);
}
}
@@ -211,7 +211,7 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
}
@Override
protected void setStatus(TrackedRaceStatus newStatus) {
public void setStatus(TrackedRaceStatus newStatus) {
TrackedRaceStatus oldStatus = getStatus();
super.setStatus(newStatus);
notifyListeners(newStatus, oldStatus);
@@ -45,12 +45,12 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.common.impl.TimeRangeImpl;
import com.sap.sse.util.impl.ArrayListNavigableSet;
public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl<FixType> implements GPSFixTrack<ItemType, FixType> {
public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends MappedTrackImpl<ItemType, FixType>
implements GPSFixTrack<ItemType, FixType> {
private static final Logger logger = Logger.getLogger(GPSFixTrackImpl.class.getName());
private static final long serialVersionUID = 2115321069242514378L;
protected final Speed maxSpeedForSmoothing;
private final ItemType trackedItem;
private long millisecondsOverWhichToAverage;
private final TrackListenerCollection<ItemType, FixType, GPSTrackListener<ItemType, FixType>> listeners;
@@ -131,8 +131,8 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
* inferred speed can be matched.
*/
public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage, Speed maxSpeedForSmoothening) {
super(/* nameForReadWriteLock */ GPSFixTrackImpl.class.getSimpleName()+(trackedItem==null?"":(" for "+trackedItem.toString())));
this.trackedItem = trackedItem;
super(trackedItem, /* nameForReadWriteLock */ GPSFixTrackImpl.class.getSimpleName()
+ (trackedItem == null ? "" : (" for " + trackedItem.toString())));
this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage;
this.maxSpeedForSmoothing = maxSpeedForSmoothening;
this.listeners = new TrackListenerCollection<ItemType, FixType, GPSTrackListener<ItemType,FixType>>();
@@ -166,7 +166,7 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
distanceCache = new DistanceCache(trackedItem.toString());
distanceCache = new DistanceCache(getTrackedItem().toString());
maxSpeedCache = createMaxSpeedCache();
}
@@ -247,11 +247,6 @@ public class GPSFixTrackImpl<ItemType, FixType extends GPSFix> extends TrackImpl
FixType result = (FixType) new DummyGPSFix(timePoint);
return result;
}
@Override
public ItemType getTrackedItem() {
return trackedItem;
}
@Override
public long getMillisecondsOverWhichToAverageSpeed() {
@@ -17,12 +17,11 @@ import com.sap.sse.common.WithID;
* @param <FixT> the type of fix this track holds
*/
public class SensorFixTrackImpl<ItemType extends WithID & Serializable, FixT extends SensorFix> extends
DynamicTrackImpl<FixT> implements DynamicSensorFixTrack<ItemType, FixT> {
DynamicMappedTrackImpl<ItemType, FixT> implements DynamicSensorFixTrack<ItemType, FixT> {
private static final long serialVersionUID = 6383421895429843002L;
private final Iterable<String> valueNames;
private final ItemType trackedItem;
private final String trackName;
private final TrackListenerCollection<ItemType, FixT, SensorFixTrackListener<ItemType, FixT>> listeners;
@@ -34,8 +33,7 @@ public class SensorFixTrackImpl<ItemType extends WithID & Serializable, FixT ext
*/
public SensorFixTrackImpl(ItemType trackedItem, String trackName, Iterable<String> valueNames,
String nameForReadWriteLock) {
super(nameForReadWriteLock);
this.trackedItem = trackedItem;
super(trackedItem, nameForReadWriteLock);
this.trackName = trackName;
this.valueNames = valueNames;
this.listeners = new TrackListenerCollection<>();
@@ -48,7 +46,7 @@ public class SensorFixTrackImpl<ItemType extends WithID & Serializable, FixT ext
try {
final boolean firstFixInTrack = getRawFixes().isEmpty();
result = addWithoutLocking(fix, replace);
this.notifyListeners((listener) -> listener.fixReceived(fix, trackedItem, trackName, firstFixInTrack));
this.notifyListeners((listener) -> listener.fixReceived(fix, getTrackedItem(), trackName, firstFixInTrack));
} finally {
unlockAfterWrite();
}
@@ -74,11 +72,6 @@ public class SensorFixTrackImpl<ItemType extends WithID & Serializable, FixT ext
this.listeners.removeListener(listener);
}
@Override
public ItemType getTrackedItem() {
return trackedItem;
}
@Override
public String getTrackName() {
return trackName;
@@ -119,6 +119,7 @@ import com.sap.sailing.domain.ranking.OneDesignRankingMetric;
import com.sap.sailing.domain.ranking.RankingMetric;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.ranking.RankingMetricConstructor;
import com.sap.sailing.domain.tracking.BravoFixTrack;
import com.sap.sailing.domain.tracking.DynamicSensorFixTrack;
import com.sap.sailing.domain.tracking.DynamicTrack;
import com.sap.sailing.domain.tracking.GPSFixTrack;
@@ -298,6 +299,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
private final ConcurrentMap<Mark, GPSFixTrack<Mark, GPSFix>> markTracks;
/**
* Mapping of {@link Competitor} to generic {@link DynamicTrack} implementation. Because the same competitor could
* be mapped to several different tracks, a combined key of competitor object and track name identifier string is
* used. This identifier is usually defined within the track interface (e.g. see {@link BravoFixTrack#TRACK_NAME}).
*/
private final Map<Pair<Competitor, String>, DynamicTrack<?>> sensorTracks;
private final Map<String, Sideline> courseSidelines;
@@ -568,6 +568,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public void setStatus(TrackedRaceStatus newStatus) {
}
@Override
public void onStatusChanged(TrackingDataLoader loader, TrackedRaceStatus status) {
}
@@ -1,4 +1,4 @@
eclipse.preferences.version=1
entryPointModules=
filesCopiedToWebInfLib=gwt-servlet.jar
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDQgLXN0cmljdCAtbG9nTGV2ZWwgVFJBQ0VdXT48L2V4dHJhLWFyZ3M+PHZtLWFyZ3M+PCFbQ0RBVEFbLVhteDIwMjRtIC1EZ3d0LnVzZWFyY2hpdmVzPWZhbHNlXV0+PC92bS1hcmdzPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC51aS5BZG1pbkNvbnNvbGU8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
gwtCompileSettings=PGd3dC1jb21waWxlLXNldHRpbmdzPjxsb2ctbGV2ZWw+SU5GTzwvbG9nLWxldmVsPjxvdXRwdXQtc3R5bGU+T0JGVVNDQVRFRDwvb3V0cHV0LXN0eWxlPjxleHRyYS1hcmdzPjwhW0NEQVRBWy13YXIgLiAtbG9jYWxXb3JrZXJzIDE1IC1zdHJpY3QgLWxvZ0xldmVsIFRSQUNFXV0+PC9leHRyYS1hcmdzPjx2bS1hcmdzPjwhW0NEQVRBWy1YbXgyMDI0bSAtRGd3dC51c2VhcmNoaXZlcz1mYWxzZV1dPjwvdm0tYXJncz48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuQWRtaW5Db25zb2xlPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLlJhY2VCb2FyZDwvZW50cnktcG9pbnQtbW9kdWxlPjxlbnRyeS1wb2ludC1tb2R1bGU+Y29tLnNhcC5zYWlsaW5nLmd3dC5ob21lLkhvbWU8L2VudHJ5LXBvaW50LW1vZHVsZT48ZW50cnktcG9pbnQtbW9kdWxlPmNvbS5zYXAuc2FpbGluZy5nd3QudWkuTGVhZGVyYm9hcmRFZGl0aW5nPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnVpLkxlYWRlcmJvYXJkPC9lbnRyeS1wb2ludC1tb2R1bGU+PGVudHJ5LXBvaW50LW1vZHVsZT5jb20uc2FwLnNhaWxpbmcuZ3d0LnJlZ2F0dGFvdmVydmlldy5SZWdhdHRhT3ZlcnZpZXc8L2VudHJ5LXBvaW50LW1vZHVsZT48L2d3dC1jb21waWxlLXNldHRpbmdzPg\=\=
+2 -2
View File
@@ -448,8 +448,8 @@ img.openColumn {
.busyIndicator-Simple {
position: absolute;
top: 0.7em;
right: 7.4em;
top: 0.3em;
right: 0.4em;
}
.busyIndicator-Circle {
@@ -3,6 +3,7 @@
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9876"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9876"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart"/>
@@ -3,6 +3,7 @@
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="true"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT_AUTO" value="true"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.ui.RaceBoard"/>
+5 -4
View File
@@ -23,6 +23,10 @@
top: -1000px;
}
html {
font-size: 15px;
}
.titleLabelWrapper {
position: relative;
margin-left: auto;
@@ -832,13 +836,10 @@ img.openColumn {
}
.busyIndicator-Simple {
position: absolute;
top: 0.7em;
right: 7.4em;
padding-right: 10px;
}
.busyIndicator-Circle {
}
.raceBoard-TitlePanel {
@@ -13,7 +13,7 @@ public class AutoPlayEntryPoint extends AbstractMvpEntryPoint<StringMessages, Au
@Override
public void doOnModuleLoad() {
AutoPlayAppClientFactory clientFactory = GWT.create(AutoPlayAppClientFactory.class);
AutoPlayAppHistoryMapper applicationHistoryMapper = GWT.create(AutoPlayAppHistoryMapper.class);
initMvp(clientFactory, applicationHistoryMapper, new AutoPlayAppActivityMapper(clientFactory));
@@ -29,7 +29,7 @@ public class DesktopApplicationClientFactory extends AbstractApplicationClientFa
@Override
public StartView createStartView() {
return new DesktopStartView(getPlaceNavigator(), getEventBus());
return new DesktopStartView(getPlaceNavigator(), getEventBus(), getUserService());
}
@Override
@@ -100,7 +100,7 @@ public class AutoPlayController implements RaceTimesInfoProviderListener, Leader
playerView.clearContent();
boolean withFullscreenButton = autoPlayerConfiguration.isFullscreenMode() && isInitialScreen;
LeaderboardWithHeaderPerspective leaderboardPerspective = new LeaderboardWithHeaderPerspective(leaderboardPerspectiveLifecycleWithAllSettings,
sailingService, asyncActionsExecutor,
sailingService, userService, asyncActionsExecutor,
new CompetitorSelectionModel(/* hasMultiSelection */ true), leaderboardTimer,
autoPlayerConfiguration.getLeaderboardName(), errorReporter, StringMessages.INSTANCE,
userAgent, withFullscreenButton);
@@ -27,6 +27,7 @@ import com.sap.sailing.gwt.autoplay.client.app.PlaceNavigator;
import com.sap.sailing.gwt.autoplay.client.place.player.AutoPlayerConfiguration;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveLifecycle;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.client.SharedResources;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle;
@@ -34,15 +35,17 @@ import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveSettings;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.common.Util;
import com.sap.sse.common.settings.Settings;
import com.sap.sse.gwt.client.GWTLocaleUtil;
import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback;
import com.sap.sse.gwt.client.event.LocaleChangeEvent;
import com.sap.sse.gwt.client.sapheader.SAPHeader;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeLifecycleTabbedSettingsDialog;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycle;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class DesktopStartView extends Composite implements StartView {
private static StartPageViewUiBinder uiBinder = GWT.create(StartPageViewUiBinder.class);
@@ -50,7 +53,7 @@ public class DesktopStartView extends Composite implements StartView {
interface StartPageViewUiBinder extends UiBinder<Widget, DesktopStartView> {
}
@UiField(provided=true) SAPHeader sapHeader;
@UiField(provided=true) SAPHeaderWithAuthentication sapHeader;
@UiField(provided=true) ListBox localeSelectionBox;
@UiField(provided=true) ListBox eventSelectionBox;
@UiField(provided=true) ListBox leaderboardSelectionBox;
@@ -73,15 +76,16 @@ public class DesktopStartView extends Composite implements StartView {
private final int defaultTimeToRaceStartTimeInSeconds = 180;
public DesktopStartView(PlaceNavigator navigator, EventBus eventBus) {
public DesktopStartView(PlaceNavigator navigator, EventBus eventBus, UserService userService) {
super();
this.navigator = navigator;
this.eventBus = eventBus;
this.events = new ArrayList<EventDTO>();
sapHeader = new SAPHeaderWithAuthentication(StringMessages.INSTANCE.sapSailingAnalytics(),
StringMessages.INSTANCE.autoplayConfiguration());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
// sapHeader = new SAPHeaderWithAuthentication(StringMessages.INSTANCE.sapSailingAnalytics(), StringMessages.INSTANCE.autoplayConfiguration());
sapHeader = new SAPHeader(StringMessages.INSTANCE.sapSailingAnalytics());
sapHeader.setHeaderTitle(StringMessages.INSTANCE.autoplayConfiguration());
eventSelectionBox = new ListBox();
eventSelectionBox.setMultipleSelect(false);
leaderboardSelectionBox = new ListBox();
@@ -140,13 +144,12 @@ public class DesktopStartView extends Composite implements StartView {
public void setEvents(List<EventDTO> events) {
this.events.clear();
this.events.addAll(events);
eventSelectionBox.addItem(StringMessages.INSTANCE.pleaseSelectAnEvent());
for(EventDTO event: events) {
for (EventDTO event : Util.sortNamedCollection(events)) {
eventSelectionBox.addItem(event.getName());
}
}
@UiHandler("eventSelectionBox")
void onEventSelectionChange(ChangeEvent event) {
EventDTO selectedEvent = getSelectedEvent();
@@ -3,24 +3,26 @@ package com.sap.sailing.gwt.autoplay.client.shared.header;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.client.FullscreenUtil;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.gwt.client.sapheader.SAPHeader;
import com.sap.sse.gwt.client.shared.components.AbstractCompositeComponent;
import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication;
import com.sap.sse.security.ui.client.UserService;
public class SAPHeaderComponent extends AbstractCompositeComponent<SAPHeaderComponentSettings> {
private SAPHeaderComponentSettings settings;
private final SAPHeaderComponentLifecycle componentLifecycle;
private final SAPHeader sapHeader;
private final SAPHeaderWithAuthentication sapHeader;
public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, SAPHeaderComponentSettings settings,
public SAPHeaderComponent(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings,
StringMessages stringMessages, boolean startInAutoScreenMode) {
this.componentLifecycle = componentLifecycle;
this.settings = settings;
this.sapHeader = new SAPHeader(stringMessages.sapSailingAnalytics());
sapHeader.setHeaderTitle(settings.getTitle());
this.sapHeader = new SAPHeaderWithAuthentication(stringMessages.sapSailingAnalytics(), settings.getTitle());
new FixedSailingAuthentication(userService, sapHeader.getAuthenticationMenuView());
initWidget(sapHeader);
if (startInAutoScreenMode) {
@@ -30,6 +30,7 @@ import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
import com.sap.sse.gwt.client.shared.perspective.AbstractPerspectiveComposite;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.security.ui.client.UserService;
/**
* A perspective managing a header with a single leaderboard filling the rest of the screen.
@@ -40,14 +41,14 @@ import com.sap.sse.gwt.client.useragent.UserAgentDetails;
public class LeaderboardWithHeaderPerspective extends AbstractPerspectiveComposite<LeaderboardWithHeaderPerspectiveLifecycle,
LeaderboardWithHeaderPerspectiveSettings> implements LeaderboardUpdateProvider {
private final DockLayoutPanel dockPanel;
private final static int SAP_HEADER_HEIGHT = 70;
private final static int SAP_HEADER_HEIGHT = 75;
private final Widget currentContentWidget;
private final LeaderboardPanel leaderboardPanel;
private final StringMessages stringMessages;
private final PerspectiveLifecycleWithAllSettings<LeaderboardWithHeaderPerspectiveLifecycle, LeaderboardWithHeaderPerspectiveSettings> componentLifecyclesAndSettings;
public LeaderboardWithHeaderPerspective(PerspectiveLifecycleWithAllSettings<LeaderboardWithHeaderPerspectiveLifecycle, LeaderboardWithHeaderPerspectiveSettings> perspectiveLifecycleWithAllSettings,
SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
SailingServiceAsync sailingService, UserService userService, AsyncActionsExecutor asyncActionsExecutor,
CompetitorSelectionProvider competitorSelectionProvider, Timer timer,
String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages,
UserAgentDetails userAgent, boolean startInFullScreenMode) {
@@ -62,9 +63,8 @@ public class LeaderboardWithHeaderPerspective extends AbstractPerspectiveComposi
}
}
});
SAPHeaderComponentLifecycle sapHeaderLifecycle = getPerspectiveLifecycle().getSapHeaderLifecycle();
SAPHeaderComponent sapHeader = createSAPHeader(sapHeaderLifecycle,
SAPHeaderComponent sapHeader = createSAPHeader(sapHeaderLifecycle, userService,
perspectiveLifecycleWithAllSettings.findComponentSettingsByLifecycle(sapHeaderLifecycle),
stringMessages, startInFullScreenMode);
leaderboardPanel = createLeaderboardPanel(sailingService, asyncActionsExecutor,
@@ -183,9 +183,9 @@ public class LeaderboardWithHeaderPerspective extends AbstractPerspectiveComposi
leaderboardPanel.removeLeaderboardUpdateListener(listener);
}
private SAPHeaderComponent createSAPHeader(SAPHeaderComponentLifecycle componentLifecycle, SAPHeaderComponentSettings settings,
private SAPHeaderComponent createSAPHeader(SAPHeaderComponentLifecycle componentLifecycle, UserService userService, SAPHeaderComponentSettings settings,
final StringMessages stringMessages, boolean withFullscreenButton) {
return new SAPHeaderComponent(componentLifecycle, settings, stringMessages, withFullscreenButton);
return new SAPHeaderComponent(componentLifecycle, userService, settings, stringMessages, withFullscreenButton);
}
private LeaderboardPanel createLeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor,
@@ -6,6 +6,10 @@ import com.sap.sse.security.ui.authentication.generic.sapheader.FixedFlyoutAuthe
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView;
import com.sap.sse.security.ui.client.UserService;
/**
* Sailing-specific extension of {@link GenericAuthentication} using a {@link FixedFlyoutAuthenticationView}, which is
* suitable in cases where the header is fixed on top of the page.
*/
public class FixedSailingAuthentication extends GenericAuthentication {
private static final CommonSharedResources res = CommonSharedResources.INSTANCE;
@@ -6,6 +6,10 @@ import com.sap.sse.security.ui.authentication.generic.sapheader.GenericFlyoutAut
import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView;
import com.sap.sse.security.ui.client.UserService;
/**
* Sailing-specific extension of {@link GenericAuthentication} using a {@link GenericFlyoutAuthenticationView}, which is
* suitable in cases where the header scrolls with the page.
*/
public class GenericSailingAuthentication extends GenericAuthentication {
private static final CommonSharedResources res = CommonSharedResources.INSTANCE;
@@ -2,9 +2,8 @@ package com.sap.sailing.gwt.common.authentication;
import java.util.HashMap;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.user.client.Window;
import com.sap.sse.gwt.client.AbstractEntryPointLinkFactory;
import com.sap.sse.gwt.settings.UrlBuilderUtil;
import com.sap.sse.security.ui.authentication.generic.GenericAuthenticationLinkFactory;
public final class SailingAuthenticationEntryPointLinkFactory extends AbstractEntryPointLinkFactory implements
@@ -17,7 +16,7 @@ public final class SailingAuthenticationEntryPointLinkFactory extends AbstractEn
@Override
public final String createUserProfileLink() {
return createEntryPointLink("/gwt/Home.html#/user/profile/:", new HashMap<String, String>());
return createEntryPointLink("/gwt/Home.html", "/user/profile/:", new HashMap<String, String>());
}
@Override
@@ -30,13 +29,7 @@ public final class SailingAuthenticationEntryPointLinkFactory extends AbstractEn
return createFullQualifiedLink("/gwt/Home.html", "/user/passwordreset/:");
}
private String createFullQualifiedLink(String baseUrl, String hash) {
String path = createEntryPointLink(baseUrl, new HashMap<String, String>());
UrlBuilder urlBuilder = Window.Location.createUrlBuilder();
for (String parameter : Window.Location.getParameterMap().keySet()) {
urlBuilder.removeParameter(parameter);
}
return urlBuilder.setPath(path).setHash(hash).buildString();
private String createFullQualifiedLink(String path, String hash) {
return UrlBuilderUtil.createUrlBuilderFromCurrentLocationWithCleanParametersAndPathAndHash(path, hash).buildString();
}
}
@@ -0,0 +1,40 @@
package com.sap.sailing.gwt.common.client.suggestion;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sap.sse.common.filter.AbstractListFilter;
public abstract class AbstractListSuggestOracle<C> extends AbstractSuggestOracle<C> {
private final List<C> suggestionObjectList = new ArrayList<>();
private final AbstractListFilter<C> suggestionMatchingFilter = new AbstractListFilter<C>() {
@Override
public Iterable<String> getStrings(C value) {
return getMatchingStrings(value);
}
};
@Override
protected final void getSuggestions(Request request, Callback callback, Iterable<String> queryTokens) {
Iterable<C> filteredList = suggestionMatchingFilter.applyFilter(queryTokens, suggestionObjectList);
this.setSuggestions(request, callback, filteredList, queryTokens);
}
public final AbstractListFilter<C> getSuggestionMatchingFilter() {
return suggestionMatchingFilter;
}
public void setSelectableValues(Collection<C> selectableValues) {
suggestionObjectList.clear();
suggestionObjectList.addAll(selectableValues);
}
protected abstract Iterable<String> getMatchingStrings(C value);
protected abstract String createSuggestionKeyString(C value);
protected abstract String createSuggestionAdditionalDisplayString(C value);
}
@@ -0,0 +1,122 @@
package com.sap.sailing.gwt.common.client.suggestion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.sap.sse.common.Util;
public abstract class AbstractSuggestOracle<T> extends SuggestOracle {
public final void setSuggestions(Request request, Callback callback, Iterable<T> suggestionObjects,
Iterable<String> queryTokens) {
List<Suggestion> suggestions = new ArrayList<>();
List<String> normalizedQueryTokens = new ArrayList<>();
for (String token : queryTokens) {
normalizedQueryTokens.add(token.toLowerCase());
}
int count = 0;
for (T match : suggestionObjects) {
suggestions.add(new SimpleSuggestion(match, normalizedQueryTokens));
if (++count >= request.getLimit()) {
break;
}
}
Response response = new Response(suggestions);
response.setMoreSuggestionsCount(Util.size(suggestionObjects) - count);
callback.onSuggestionsReady(request, response);
}
protected abstract void getSuggestions(Request request, Callback callback, Iterable<String> queryTokens);
protected abstract String createSuggestionKeyString(T value);
protected abstract String createSuggestionAdditionalDisplayString(T value);
@Override
public final void requestDefaultSuggestions(Request request, Callback callback) {
getSuggestions(request, callback, Collections.<String> emptyList());
}
@Override
public final void requestSuggestions(Request request, Callback callback) {
String normalizedQuery = request.getQuery().trim();
if (normalizedQuery == null || normalizedQuery.isEmpty()) {
requestDefaultSuggestions(request, callback);
} else {
Iterable<String> queryTokens = Collections.singleton(normalizedQuery);
getSuggestions(request, callback, queryTokens);
}
}
@Override
public final boolean isDisplayStringHTML() {
return true;
}
class SimpleSuggestion implements Suggestion {
private static final String STRONG_TAG_OPEN = "<strong>", STRONG_TAG_CLOSE = "</strong>";
private static final String ITALIC_TAG_OPEN = "<em>", ITALIC_TAG_CLOSE = "</em>";
private final T suggestObject;
private final Iterable<String> queryTokens;
private SimpleSuggestion(T suggestObject, Iterable<String> queryTokens) {
this.suggestObject = suggestObject;
this.queryTokens = queryTokens;
}
@Override
public String getDisplayString() {
SafeHtmlBuilder builder = new SafeHtmlBuilder();
appendHighlighted(builder, createSuggestionKeyString(suggestObject));
String additionalString = createSuggestionAdditionalDisplayString(suggestObject);
if (additionalString != null && !additionalString.isEmpty()) {
builder.append(SafeHtmlUtils.fromTrustedString(ITALIC_TAG_OPEN));
builder.append(SafeHtmlUtils.fromSafeConstant(" - "));
appendHighlighted(builder, additionalString);
builder.append(SafeHtmlUtils.fromTrustedString(ITALIC_TAG_CLOSE));
}
return builder.toSafeHtml().asString();
}
private void appendHighlighted(SafeHtmlBuilder builder, String displayString) {
String normalizedString = displayString.toLowerCase();
int cursor = 0;
while (true) {
int index = displayString.length();
String matchToken = null;
for (String token : queryTokens) {
int matchIndex = normalizedString.indexOf(token, cursor);
if (matchIndex >= 0 && matchIndex < index) {
index = matchIndex;
matchToken = token;
}
}
if (matchToken != null) {
builder.appendEscaped(displayString.substring(cursor, index));
builder.append(SafeHtmlUtils.fromTrustedString(STRONG_TAG_OPEN));
builder.appendEscaped(displayString.substring(index, index + matchToken.length()));
builder.append(SafeHtmlUtils.fromTrustedString(STRONG_TAG_CLOSE));
cursor = index + matchToken.length();
} else {
builder.appendEscaped(displayString.substring(cursor));
break;
}
}
}
@Override
public String getReplacementString() {
return createSuggestionKeyString(suggestObject);
}
public T getSuggestObject() {
return suggestObject;
}
}
}
@@ -0,0 +1,28 @@
package com.sap.sailing.gwt.common.client.suggestion;
import java.util.Arrays;
import com.sap.sailing.domain.common.BoatClassMasterdata;
public class BoatClassMasterdataSuggestOracle extends AbstractListSuggestOracle<BoatClassMasterdata> {
public BoatClassMasterdataSuggestOracle() {
this.setSelectableValues(Arrays.asList(BoatClassMasterdata.values()));
}
@Override
protected String createSuggestionKeyString(BoatClassMasterdata value) {
return value.getDisplayName();
}
@Override
protected String createSuggestionAdditionalDisplayString(BoatClassMasterdata value) {
return null;
}
@Override
protected Iterable<String> getMatchingStrings(BoatClassMasterdata value) {
return value.getBoatClassNames();
}
}
@@ -0,0 +1,47 @@
package com.sap.sailing.gwt.common.client.suggestion;
import java.util.Collection;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.TextBox;
public class CustomSuggestBox<T> extends SuggestBox {
public CustomSuggestBox(AbstractSuggestOracle<T> suggestOracle) {
this(suggestOracle, new CustomSuggestionDisplay());
}
public CustomSuggestBox(AbstractSuggestOracle<T> suggestOracle, SuggestionDisplay suggestionDisplay) {
super(suggestOracle, new TextBox(), suggestionDisplay);
}
public final HandlerRegistration addSuggestionSelectionHandler(final SuggestionSelectionHandler<T> handler) {
return this.addSelectionHandler(new SelectionHandler<SuggestOracle.Suggestion>() {
@Override @SuppressWarnings("unchecked")
public void onSelection(SelectionEvent<Suggestion> event) {
handler.onSuggestionSelected(
((AbstractSuggestOracle<T>.SimpleSuggestion) event.getSelectedItem()).getSuggestObject());
}
});
}
public interface SuggestionSelectionHandler<T> {
void onSuggestionSelected(T suggestObject);
}
private static class CustomSuggestionDisplay extends DefaultSuggestionDisplay {
@Override
protected void showSuggestions(SuggestBox suggestBox, Collection<? extends Suggestion> suggestions,
boolean isDisplayStringHTML, boolean isAutoSelectEnabled, SuggestionCallback callback) {
super.showSuggestions(suggestBox, suggestions, isDisplayStringHTML, isAutoSelectEnabled, callback);
getPopupPanel().getElement().getStyle().setProperty("maxWidth", suggestBox.getOffsetWidth(), Unit.PX);
}
}
}
@@ -18,7 +18,6 @@ import com.sap.sailing.gwt.home.desktop.places.start.StartActivityProxy;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileActivityProxy;
import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewActivityProxy;
import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace;
import com.sap.sailing.gwt.home.shared.ExperimentalFeatures;
import com.sap.sailing.gwt.home.shared.SwitchingEntryPoint;
import com.sap.sailing.gwt.home.shared.app.ApplicationPlaceUpdater;
import com.sap.sailing.gwt.home.shared.places.error.ErrorPlace;
@@ -69,9 +68,6 @@ public class DesktopActivityMapper implements ActivityMapper {
} else if (place instanceof EventsPlace) {
return new EventsActivityProxy((EventsPlace) place, clientFactory, clientFactory.getHomePlacesNavigator());
} else if (place instanceof AbstractUserProfilePlace) {
if (!ExperimentalFeatures.SHOW_USER_MANAGEMENT_ON_DESKTOP) {
return new StartActivityProxy(new StartPlace(), clientFactory);
}
return new UserProfileActivityProxy((AbstractUserProfilePlace) place, clientFactory, clientFactory.getHomePlacesNavigator());
} else if (place instanceof StartPlace) {
return new StartActivityProxy((StartPlace) place, clientFactory);
@@ -1,128 +1,18 @@
package com.sap.sailing.gwt.home.desktop.app;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.place.shared.PlaceController;
import com.sap.sailing.gwt.home.desktop.places.aboutus.AboutUsPlace;
import com.sap.sailing.gwt.home.desktop.places.contact.ContactPlace;
import com.sap.sailing.gwt.home.desktop.places.event.multiregatta.regattastab.MultiregattaRegattasPlace;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.AbstractEventRegattaPlace;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.analyticstab.RegattaCompetitorAnalyticsPlace;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.leaderboardtab.RegattaLeaderboardPlace;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.overviewtab.RegattaOverviewPlace;
import com.sap.sailing.gwt.home.desktop.places.sponsoring.SponsoringPlace;
import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace;
import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace.WhatsNewNavigationTabs;
import com.sap.sailing.gwt.home.shared.app.AbstractPlaceNavigator;
import com.sap.sailing.gwt.home.shared.app.HomePlacesNavigator;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.home.shared.places.event.AbstractEventPlace;
import com.sap.sailing.gwt.home.shared.places.event.EventDefaultPlace;
import com.sap.sailing.gwt.home.shared.places.events.EventsPlace;
import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace;
import com.sap.sailing.gwt.home.shared.places.imprint.ImprintPlace;
import com.sap.sailing.gwt.home.shared.places.solutions.SolutionsPlace;
import com.sap.sailing.gwt.home.shared.places.solutions.SolutionsPlace.SolutionsNavigationTabs;
import com.sap.sailing.gwt.home.shared.places.start.StartPlace;
import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationPlace;
import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationPlace.Action;
import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetPlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace;
import com.sap.sailing.gwt.home.shared.places.user.profile.UserProfileDefaultPlace;
import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory;
public class DesktopPlacesNavigator extends AbstractPlaceNavigator {
public class DesktopPlacesNavigator extends HomePlacesNavigator {
protected DesktopPlacesNavigator(PlaceController placeController, boolean isStandaloneServer) {
super(placeController, isStandaloneServer);
}
public PlaceNavigation<StartPlace> getHomeNavigation() {
return createGlobalPlaceNavigation(new StartPlace());
}
public PlaceNavigation<EventsPlace> getEventsNavigation() {
return createGlobalPlaceNavigation(new EventsPlace());
}
public PlaceNavigation<SolutionsPlace> getSolutionsNavigation(SolutionsNavigationTabs navigationTab) {
return createLocalPlaceNavigation(new SolutionsPlace(navigationTab));
}
public PlaceNavigation<WhatsNewPlace> getWhatsNewNavigation(WhatsNewNavigationTabs navigationTab) {
return createLocalPlaceNavigation(new WhatsNewPlace(navigationTab));
}
public String getSimulatorURL() {
Map<String, String> parameters = new HashMap<String, String>();
return EntryPointLinkFactory.createSimulatorLink(parameters);
}
public PlaceNavigation<SponsoringPlace> getSponsoringNavigation() {
return createGlobalPlaceNavigation(new SponsoringPlace());
}
public PlaceNavigation<AboutUsPlace> getAboutUsNavigation() {
return createGlobalPlaceNavigation(new AboutUsPlace());
}
public PlaceNavigation<ContactPlace> getContactNavigation() {
return createGlobalPlaceNavigation(new ContactPlace());
}
public PlaceNavigation<ImprintPlace> getImprintNavigation() {
return createGlobalPlaceNavigation(new ImprintPlace());
}
public PlaceNavigation<ConfirmationPlace> getMailVerifiedConfirmationNavigation() {
return createLocalPlaceNavigation(new ConfirmationPlace(Action.MAIL_VERIFIED));
}
public PlaceNavigation<ConfirmationPlace> getPasswordResettedConfirmationNavigation(String username) {
return createLocalPlaceNavigation(new ConfirmationPlace(Action.RESET_EXECUTED, username));
}
public PlaceNavigation<PasswordResetPlace> getPasswordResetNavigation() {
return createLocalPlaceNavigation(new PasswordResetPlace());
}
public PlaceNavigation<MultiregattaRegattasPlace> getEventRegattasNavigation(String eventUuidAsString, String baseUrl, boolean isOnRemoteServer) {
MultiregattaRegattasPlace eventPlace = new MultiregattaRegattasPlace(eventUuidAsString);
return createPlaceNavigation(baseUrl, isOnRemoteServer, eventPlace);
}
public PlaceNavigation<SeriesDefaultPlace> getEventSeriesNavigation(String seriesId, String baseUrl, boolean isOnRemoteServer) {
SeriesDefaultPlace place = new SeriesDefaultPlace(seriesId);
return createPlaceNavigation(baseUrl, isOnRemoteServer, place);
}
public PlaceNavigation<EventDefaultPlace> getEventNavigation(String eventUuidAsString, String baseUrl, boolean isOnRemoteServer) {
EventDefaultPlace eventPlace = new EventDefaultPlace(eventUuidAsString);
return createPlaceNavigation(baseUrl, isOnRemoteServer, eventPlace);
}
public <P extends AbstractEventPlace> PlaceNavigation<P> getEventNavigation(P place, String baseUrl,
boolean isOnRemoteServer) {
return createPlaceNavigation(baseUrl, isOnRemoteServer, place);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public PlaceNavigation<AbstractEventRegattaPlace> getRegattaNavigation(String eventUuidAsString, String leaderboardIdAsNameString, String baseUrl, boolean isOnRemoteServer) {
RegattaOverviewPlace eventPlace = new RegattaOverviewPlace(eventUuidAsString, leaderboardIdAsNameString);
return (PlaceNavigation) createPlaceNavigation(baseUrl, isOnRemoteServer, eventPlace);
}
public PlaceNavigation<RegattaCompetitorAnalyticsPlace> getCompetitorAnalyticsNavigation(String eventUuidAsString, String regattaId, String baseUrl, boolean isOnRemoteServer) {
RegattaCompetitorAnalyticsPlace regattaPlace = new RegattaCompetitorAnalyticsPlace(eventUuidAsString, regattaId);
return createPlaceNavigation(baseUrl, isOnRemoteServer, regattaPlace);
}
public PlaceNavigation<RegattaLeaderboardPlace> getLeaderboardNavigation(String eventUuidAsString, String regattaId, String baseUrl, boolean isOnRemoteServer) {
RegattaLeaderboardPlace regattaPlace = new RegattaLeaderboardPlace(eventUuidAsString, regattaId);
return createPlaceNavigation(baseUrl, isOnRemoteServer, regattaPlace);
}
public PlaceNavigation<? extends AbstractUserProfilePlace> getUserProfileNavigation() {
return createLocalPlaceNavigation(new UserProfileDefaultPlace());
}
}
@@ -24,7 +24,6 @@ import com.sap.sailing.gwt.common.client.i18n.TextMessages;
import com.sap.sailing.gwt.home.client.place.event.legacy.EventPlace;
import com.sap.sailing.gwt.home.client.place.event.legacy.RegattaPlace;
import com.sap.sailing.gwt.home.desktop.app.DesktopPlacesNavigator;
import com.sap.sailing.gwt.home.shared.ExperimentalFeatures;
import com.sap.sailing.gwt.home.shared.app.PlaceNavigation;
import com.sap.sailing.gwt.home.shared.places.event.AbstractEventPlace;
import com.sap.sailing.gwt.home.shared.places.events.EventsPlace;
@@ -97,9 +96,6 @@ public class Header extends Composite {
});
authenticationMenuView = new AuthenticationMenuViewImpl(usermenu, HeaderResources.INSTANCE.css().loggedin(), HeaderResources.INSTANCE.css().open());
if (!ExperimentalFeatures.SHOW_USER_MANAGEMENT_ON_DESKTOP) {
usermenu.removeFromParent();
}
}
@UiHandler("startPageLink")
@@ -46,5 +46,6 @@ public class MainMedia extends Composite {
for (SailingImageDTO image : photos) {
imageCarousel.addImage(image);
}
imageCarousel.setInfiniteScrolling(true);
}
}
@@ -5,16 +5,14 @@ import java.util.Collections;
import java.util.TreeSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.FontWeight;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO;
import com.sap.sailing.gwt.home.desktop.places.event.multiregatta.EventMultiregattaView.Presenter;
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.refresh.RefreshableWidget;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util;
@@ -31,6 +29,7 @@ public class MultiRegattaList extends Composite implements RefreshableWidget<Sor
private final Presenter currentPresenter;
private final Collection<String> selectableBoatCategories = new TreeSet<>();
private final boolean showMessageIfEmpty;
private boolean hasData = false;
public MultiRegattaList(Presenter presenter, boolean showMessageIfEmpty) {
this.currentPresenter = presenter;
@@ -46,8 +45,9 @@ public class MultiRegattaList extends Composite implements RefreshableWidget<Sor
public void setListData(Collection<RegattaWithProgressDTO> data) {
regattasContainerUi.clear();
selectableBoatCategories.clear();
if (data.isEmpty() && showMessageIfEmpty) {
regattasContainerUi.add(getNoDataInfoWidget());
hasData = !data.isEmpty();
if (!hasData && showMessageIfEmpty) {
regattasContainerUi.add(new InfoPlaceholder(StringMessages.INSTANCE.noDataForEvent()));
}
for (RegattaWithProgressDTO regattaWithProgress : data) {
regattasContainerUi.add(new MultiRegattaListItem(regattaWithProgress, currentPresenter));
@@ -58,18 +58,14 @@ public class MultiRegattaList extends Composite implements RefreshableWidget<Sor
}
}
private Widget getNoDataInfoWidget() {
Label label = new Label(StringMessages.INSTANCE.noDataForEvent());
label.getElement().getStyle().setMarginTop(1, Unit.EM);
label.getElement().getStyle().setFontWeight(FontWeight.BOLD);
return label;
}
public Collection<String> getSelectableBoatCategories() {
return selectableBoatCategories;
}
public void setVisibleLeaderboardGroup(String leaderboardGroupName) {
if(!hasData) {
return;
}
for (int i=0; i < regattasContainerUi.getWidgetCount(); i++) {
MultiRegattaListItem item = (MultiRegattaListItem) regattasContainerUi.getWidget(i);
item.setVisibilityDependingOnLeaderboardGroup(leaderboardGroupName);
@@ -225,7 +225,7 @@ public class OldLeaderboard extends Composite implements BusyStateChangeListener
@Override
public void onBusyStateChange(boolean busyState) {
busyIndicator.setBusy(busyState);
if(delegate != null) {
if (delegate != null) {
delegate.setBusyState(busyState);
}
}
@@ -36,8 +36,8 @@ public abstract class RaceviewerLaunchPad extends Composite {
}
if (data.isRunning() || data.isFinished()) {
addItem(data, RaceviewerLaunchPadMenuItem.START_ANALYSIS);
addItem(data, RaceviewerLaunchPadMenuItem.WINNING_LANES);
}
addItem(data, RaceviewerLaunchPadMenuItem.WINNING_LANES);
sinkEvents(Event.ONCLICK);
}
@@ -23,10 +23,12 @@ import com.sap.sailing.gwt.home.desktop.places.event.regatta.EventRegattaView.Pr
import com.sap.sailing.gwt.home.desktop.places.event.regatta.RegattaAnalyticsDataManager;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.SharedLeaderboardRegattaTabView;
import com.sap.sailing.gwt.home.shared.ExperimentalFeatures;
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.partials.placeholder.Placeholder;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateProvider;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util;
/**
@@ -63,6 +65,11 @@ public class RegattaCompetitorAnalyticsTabView extends SharedLeaderboardRegattaT
@Override
public void start(RegattaCompetitorAnalyticsPlace myPlace, AcceptsOneWidget contentArea) {
if(currentPresenter.getRegattaMetadata() == null) {
contentArea.setWidget(new InfoPlaceholder(StringMessages.INSTANCE.noDataForEvent()));
return;
}
contentArea.setWidget(new Placeholder());
String regattaId = currentPresenter.getRegattaId();
@@ -17,8 +17,10 @@ import com.sap.sailing.gwt.home.desktop.places.event.regatta.EventRegattaView;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.RegattaAnalyticsDataManager;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.SharedLeaderboardRegattaTabView;
import com.sap.sailing.gwt.home.desktop.places.event.regatta.EventRegattaView.Presenter;
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.partials.placeholder.Placeholder;
import com.sap.sailing.gwt.ui.client.LeaderboardUpdateProvider;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
/**
@@ -58,6 +60,11 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView<R
@Override
public void start(final RegattaLeaderboardPlace myPlace, final AcceptsOneWidget contentArea) {
if(currentPresenter.getRegattaMetadata() == null) {
contentArea.setWidget(new InfoPlaceholder(StringMessages.INSTANCE.noDataForEvent()));
return;
}
contentArea.setWidget(new Placeholder());
String regattaId = currentPresenter.getRegattaId();
if (regattaId != null && !regattaId.isEmpty()) {
@@ -12,7 +12,9 @@ import com.sap.sailing.gwt.home.communication.event.GetLiveRacesForRegattaAction
import com.sap.sailing.gwt.home.communication.event.GetRegattaStatisticsAction;
import com.sap.sailing.gwt.home.communication.event.GetRegattaWithProgressAction;
import com.sap.sailing.gwt.home.communication.event.minileaderboard.GetMiniLeaderbordAction;
import com.sap.sailing.gwt.home.communication.event.statistics.GetEventStatisticsAction;
import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO.EventType;
import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata;
import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata.RegattaState;
import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO;
import com.sap.sailing.gwt.home.desktop.partials.eventstage.EventOverviewStage;
@@ -54,11 +56,7 @@ public class RegattaOverviewTabView extends Composite implements RegattaTabView<
@Override
public TabView.State getState() {
if (currentPresenter.getEventDTO().getType() == EventType.MULTI_REGATTA) {
return State.VISIBLE;
} else {
return State.VISIBLE;
}
return State.VISIBLE;
}
@Override
@@ -66,27 +64,37 @@ public class RegattaOverviewTabView extends Composite implements RegattaTabView<
liveRacesListUi = new LiveRacesList(currentPresenter, false);
stageUi = new EventOverviewStage(currentPresenter);
statisticsBoxUi = new StatisticsBox(false);
standingsUi = new StandingsList(currentPresenter.getRegattaMetadata().getState() == RegattaState.FINISHED, currentPresenter.getRegattaLeaderboardNavigation(currentPresenter.getRegattaId()));
final HasRegattaMetadata regattaMetadata = currentPresenter.getRegattaMetadata();
standingsUi = new StandingsList(regattaMetadata != null && regattaMetadata.getState() == RegattaState.FINISHED, currentPresenter.getRegattaLeaderboardNavigation(currentPresenter.getRegattaId()));
initWidget(ourUiBinder.createAndBindUi(this));
RefreshManager refreshManager = new RefreshManagerWithErrorAndBusy(this, contentArea, currentPresenter.getDispatch(), currentPresenter.getErrorAndBusyClientFactory());
refreshManager.add(new RefreshableWidget<RegattaWithProgressDTO>() {
@Override
public void setData(RegattaWithProgressDTO data) {
regattaInfoContainerUi.setWidget(new MultiRegattaListItem(data, true));
}
}, new GetRegattaWithProgressAction(currentPresenter.getEventDTO().getId(), currentPresenter.getRegattaId()));
if (currentPresenter.getRegattaMetadata() != null) {
refreshManager.add(new RefreshableWidget<RegattaWithProgressDTO>() {
@Override
public void setData(RegattaWithProgressDTO data) {
regattaInfoContainerUi.setWidget(new MultiRegattaListItem(data, true));
}
}, new GetRegattaWithProgressAction(currentPresenter.getEventDTO().getId(),
currentPresenter.getRegattaId()));
}
if (currentPresenter.getEventDTO().getType() == EventType.MULTI_REGATTA) {
stageUi.removeFromParent();
} else {
stageUi.setupRefresh(refreshManager);
}
refreshManager.add(liveRacesListUi.getRefreshable(), new GetLiveRacesForRegattaAction(currentPresenter.getEventDTO()
.getId(), currentPresenter.getRegattaId()));
refreshManager.add(standingsUi, new GetMiniLeaderbordAction(currentPresenter.getEventDTO().getId(), currentPresenter.getRegattaId(), 5));
refreshManager.add(statisticsBoxUi, new GetRegattaStatisticsAction(currentPresenter.getEventDTO().getId(), currentPresenter.getRegattaId()));
if(currentPresenter.getRegattaMetadata() != null) {
refreshManager.add(liveRacesListUi.getRefreshable(), new GetLiveRacesForRegattaAction(currentPresenter.getEventDTO()
.getId(), currentPresenter.getRegattaId()));
refreshManager.add(standingsUi, new GetMiniLeaderbordAction(currentPresenter.getEventDTO().getId(), currentPresenter.getRegattaId(), 5));
}
refreshManager.add(statisticsBoxUi,
currentPresenter.getRegattaMetadata() != null
? new GetRegattaStatisticsAction(currentPresenter.getEventDTO().getId(),
currentPresenter.getRegattaId())
: new GetEventStatisticsAction(currentPresenter.getEventDTO().getId()));
}
@Override
@@ -46,6 +46,7 @@ import com.sap.sailing.gwt.home.shared.partials.filter.FilterPresenter;
import com.sap.sailing.gwt.home.shared.partials.filter.FilterValueChangeHandler;
import com.sap.sailing.gwt.home.shared.partials.filter.FilterWidget;
import com.sap.sailing.gwt.home.shared.partials.filter.RacesByCompetitorTextBoxFilter;
import com.sap.sailing.gwt.home.shared.partials.placeholder.InfoPlaceholder;
import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionPresenter;
import com.sap.sailing.gwt.home.shared.refresh.ActionProvider.AbstractActionProvider;
import com.sap.sailing.gwt.home.shared.refresh.RefreshManager;
@@ -105,6 +106,11 @@ public class RegattaRacesTabView extends Composite implements RegattaTabView<Reg
@Override
public void start(RegattaRacesPlace myPlace, final AcceptsOneWidget contentArea) {
if(currentPresenter.getRegattaMetadata() == null) {
contentArea.setWidget(new InfoPlaceholder(StringMessages.INSTANCE.noDataForEvent()));
return;
}
listNavigationPanelUi = new ListNavigationPanel<Navigation>(new RegattaRacesTabViewNavigationSelectionCallback());
listNavigationPanelUi.setAdditionalWidget(competitorFilterUi);
liveRacesListUi = new LiveRacesList(currentPresenter, false);
@@ -3,8 +3,6 @@
xmlns:um="urn:import:com.sap.sailing.gwt.home.shared.usermanagement.decorator">
<ui:with field="i18n" type="com.sap.sailing.gwt.ui.client.StringMessages" />
<ui:with field="res" type="com.sap.sailing.gwt.common.client.SharedResources" />
<ui:style>
</ui:style>
<um:AuthorizedContentDecoratorDesktop ui:field="decoratorUi">
<um:content>
<ud:UserAccountDetails ui:field="accountDetailsUi"
@@ -1,133 +1,43 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.preferencestab;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.communication.user.profile.CompetitorSuggestionResult;
import com.sap.sailing.gwt.home.communication.user.profile.FavoriteBoatClassesDTO;
import com.sap.sailing.gwt.home.communication.user.profile.FavoriteCompetitorsDTO;
import com.sap.sailing.gwt.home.communication.user.profile.FavoritesResult;
import com.sap.sailing.gwt.home.communication.user.profile.GetCompetitorSuggestionAction;
import com.sap.sailing.gwt.home.communication.user.profile.GetFavoritesAction;
import com.sap.sailing.gwt.home.communication.user.profile.SaveFavoriteBoatClassesAction;
import com.sap.sailing.gwt.home.communication.user.profile.SaveFavoriteCompetitorsAction;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileClientFactory;
import com.sap.sailing.gwt.home.desktop.places.user.profile.UserProfileView;
import com.sap.sailing.gwt.home.shared.partials.multiselection.AbstractSuggestedMultiSelectionBoatClassDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.AbstractSuggestedMultiSelectionCompetitorDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionBoatClassDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionCompetitorDataProvider;
import com.sap.sse.gwt.dispatch.shared.commands.VoidResult;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserPreferencesPresenter;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserPreferencesView;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserPreferencesView.Presenter;
import com.sap.sse.security.ui.authentication.app.AuthenticationContext;
public class UserProfilePreferencesPresenter implements UserProfilePreferencesView.Presenter {
private final UserProfilePreferencesView view;
private final UserProfileView.Presenter userProfilePresenter;
private final SuggestedMultiSelectionCompetitorDataProvider competitorDataProvider =
new SuggestedMultiSelectionCompetitorDataProviderImpl();
private final SuggestedMultiSelectionBoatClassDataProvider boatClassDataProvider =
new SuggestedMultiSelectionBoatClassDataProviderImpl();
private final UserPreferencesView.Presenter userPreferencesPresenter;
public UserProfilePreferencesPresenter(final UserProfilePreferencesView view,
final UserProfileView.Presenter userProfilePresenter) {
this.view = view;
this.userProfilePresenter = userProfilePresenter;
this.userPreferencesPresenter = new UserPreferencesPresenter<UserProfileClientFactory>(
userProfilePresenter.getClientFactory());
view.setPresenter(this);
}
@Override
public Presenter getUserPreferencesPresenter() {
return userPreferencesPresenter;
}
@Override
public void setAuthenticationContext(AuthenticationContext authenticationContext) {
view.getDecorator().setAuthenticationContext(authenticationContext);
if (authenticationContext.isLoggedIn()) {
userProfilePresenter.getClientFactory().getDispatch().execute(new GetFavoritesAction(),
new AsyncCallback<FavoritesResult>() {
@Override
public void onFailure(Throwable caught) {
userProfilePresenter.getClientFactory()
.createErrorView("Error while loading notification preferences!", caught);
}
@Override
public void onSuccess(FavoritesResult result) {
initFavoriteCompetitors(result.getFavoriteCompetitors());
initFavoriteBoatClasses(result.getFavoriteBoatClasses());
}
});
userPreferencesPresenter.loadPreferences();
}
}
private void initFavoriteCompetitors(FavoriteCompetitorsDTO favoriteCompetitors) {
competitorDataProvider.initNotifications(favoriteCompetitors.isNotifyAboutResults());
competitorDataProvider.initSelectedItems(favoriteCompetitors.getSelectedCompetitors());
}
private void initFavoriteBoatClasses(FavoriteBoatClassesDTO favoriteBoatClasses) {
boatClassDataProvider.initNotifications(favoriteBoatClasses.isNotifyAboutUpcomingRaces(),
favoriteBoatClasses.isNotifyAboutResults());
boatClassDataProvider.initSelectedItems(favoriteBoatClasses.getSelectedBoatClasses());
}
@Override
public void doTriggerLoginForm() {
userProfilePresenter.doTriggerLoginForm();
}
@Override
public SuggestedMultiSelectionBoatClassDataProvider getFavoriteBoatClassesDataProvider() {
return boatClassDataProvider;
}
@Override
public SuggestedMultiSelectionCompetitorDataProvider getFavoriteCompetitorsDataProvider() {
return competitorDataProvider;
}
private class SuggestedMultiSelectionBoatClassDataProviderImpl
extends AbstractSuggestedMultiSelectionBoatClassDataProvider {
@Override
protected void persist(FavoriteBoatClassesDTO favorites) {
userProfilePresenter.getClientFactory().getDispatch().execute(
new SaveFavoriteBoatClassesAction(favorites), new SaveAsyncCallback());
}
}
private class SuggestedMultiSelectionCompetitorDataProviderImpl
extends AbstractSuggestedMultiSelectionCompetitorDataProvider {
@Override
protected void getSuggestions(Iterable<String> queryTokens, int limit,
final SuggestionItemsCallback<SimpleCompetitorWithIdDTO> callback) {
userProfilePresenter.getClientFactory().getDispatch().execute(
new GetCompetitorSuggestionAction(queryTokens, limit),
new AsyncCallback<CompetitorSuggestionResult>() {
@Override
public void onFailure(Throwable caught) {
Window.alert("Error while loading competitor suggestion");
}
@Override
public void onSuccess(CompetitorSuggestionResult result) {
callback.setSuggestionItems(result.getValues());
}
});
}
@Override
protected void persist(FavoriteCompetitorsDTO favorites) {
userProfilePresenter.getClientFactory().getDispatch().execute(new SaveFavoriteCompetitorsAction(favorites),
new SaveAsyncCallback());
}
}
private class SaveAsyncCallback implements AsyncCallback<VoidResult> {
@Override
public void onFailure(Throwable caught) {
userProfilePresenter.getClientFactory().createErrorView(
"Error while saving notification preferences!", caught);
}
@Override
public void onSuccess(VoidResult result) {
}
}
}
@@ -1,8 +1,7 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.preferencestab;
import com.google.gwt.user.client.ui.IsWidget;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionBoatClassDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionCompetitorDataProvider;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserPreferencesView;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
import com.sap.sse.security.ui.authentication.decorator.NotLoggedInPresenter;
@@ -13,7 +12,6 @@ public interface UserProfilePreferencesView extends IsWidget {
NeedsAuthenticationContext getDecorator();
public interface Presenter extends NotLoggedInPresenter, NeedsAuthenticationContext {
SuggestedMultiSelectionBoatClassDataProvider getFavoriteBoatClassesDataProvider();
SuggestedMultiSelectionCompetitorDataProvider getFavoriteCompetitorsDataProvider();
UserPreferencesView.Presenter getUserPreferencesPresenter();
}
}
@@ -1,22 +1,12 @@
package com.sap.sailing.gwt.home.desktop.places.user.profile.preferencestab;
import java.util.Collection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasEnabled;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.BoatClassDTO;
import com.sap.sailing.gwt.home.communication.event.SimpleCompetitorWithIdDTO;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelection;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionBoatClassDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelectionCompetitorDataProvider;
import com.sap.sailing.gwt.home.shared.partials.multiselection.SuggestedMultiSelection.NotificationCallback;
import com.sap.sailing.gwt.home.shared.places.user.profile.preferences.UserPreferences;
import com.sap.sailing.gwt.home.shared.usermanagement.decorator.AuthorizedContentDecoratorDesktop;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.security.ui.authentication.app.NeedsAuthenticationContext;
public class UserProfilePreferencesViewImpl extends Composite implements UserProfilePreferencesView {
@@ -27,88 +17,18 @@ public class UserProfilePreferencesViewImpl extends Composite implements UserPro
private static MyBinder uiBinder = GWT.create(MyBinder.class);
@UiField(provided = true) AuthorizedContentDecoratorDesktop decoratorUi;
@UiField(provided = true) SuggestedMultiSelection<SimpleCompetitorWithIdDTO> favoriteCompetitorsSelctionUi;
@UiField(provided = true) SuggestedMultiSelection<BoatClassDTO> favoriteBoatClassesSelctionUi;
@UiField DivElement notificationsTextUi;
@UiField(provided = true) UserPreferences userPreferencesUi;
@Override
public void setPresenter(Presenter presenter) {
decoratorUi = new AuthorizedContentDecoratorDesktop(presenter);
favoriteCompetitorsSelctionUi = new CompetitorDisplayImpl(
presenter.getFavoriteCompetitorsDataProvider()).selectionUi;
favoriteBoatClassesSelctionUi = new BoatClassDisplayImpl(
presenter.getFavoriteBoatClassesDataProvider()).selectionUi;
userPreferencesUi = new UserPreferences(presenter.getUserPreferencesPresenter());
initWidget(uiBinder.createAndBindUi(this));
// TODO hide notificationsTextUi if the user's mail address is already verified
}
@Override
public NeedsAuthenticationContext getDecorator() {
return decoratorUi;
}
private class CompetitorDisplayImpl implements SuggestedMultiSelectionCompetitorDataProvider.Display {
private final SuggestedMultiSelection<SimpleCompetitorWithIdDTO> selectionUi;
private final HasEnabled notifyAboutResultsUi;
private CompetitorDisplayImpl(final SuggestedMultiSelectionCompetitorDataProvider dataProvider) {
selectionUi = SuggestedMultiSelection.forCompetitors(dataProvider, StringMessages.INSTANCE.favoriteCompetitors());
notifyAboutResultsUi = selectionUi.addNotificationToggle(new NotificationCallback() {
@Override
public void onNotificationToggled(boolean enabled) {
dataProvider.setNotifyAboutResults(enabled);
}
}, StringMessages.INSTANCE.notificationAboutNewResults());
dataProvider.addDisplay(this);
}
@Override
public void setSelectedItems(Collection<SimpleCompetitorWithIdDTO> selectedItems) {
selectionUi.setSelectedItems(selectedItems);
}
@Override
public void setNotifyAboutResults(boolean notifyAboutResults) {
notifyAboutResultsUi.setEnabled(notifyAboutResults);
}
}
private class BoatClassDisplayImpl implements SuggestedMultiSelectionBoatClassDataProvider.Display {
private final SuggestedMultiSelection<BoatClassDTO> selectionUi;
private final HasEnabled notifyAboutUpcomingRacesUi;
private final HasEnabled notifyAboutResultsUi;
private BoatClassDisplayImpl(final SuggestedMultiSelectionBoatClassDataProvider dataProvider) {
selectionUi = SuggestedMultiSelection.forBoatClasses(dataProvider, StringMessages.INSTANCE.favoriteBoatClasses());
notifyAboutUpcomingRacesUi = selectionUi.addNotificationToggle(new NotificationCallback() {
@Override
public void onNotificationToggled(boolean enabled) {
dataProvider.setNotifyAboutUpcomingRaces(enabled);
}
}, StringMessages.INSTANCE.notificationAboutUpcomingRaces());
notifyAboutResultsUi = selectionUi.addNotificationToggle(new NotificationCallback() {
@Override
public void onNotificationToggled(boolean enabled) {
dataProvider.setNotifyAboutResults(enabled);
}
}, StringMessages.INSTANCE.notificationAboutNewResults());
dataProvider.addDisplay(this);
}
@Override
public void setSelectedItems(Collection<BoatClassDTO> selectedItems) {
selectionUi.setSelectedItems(selectedItems);
}
@Override
public void setNotifyAboutUpcomingRaces(boolean notifyAboutUpcomingRaces) {
notifyAboutUpcomingRacesUi.setEnabled(notifyAboutUpcomingRaces);
}
@Override
public void setNotifyAboutResults(boolean notifyAboutResults) {
notifyAboutResultsUi.setEnabled(notifyAboutResults);
}
}
}
Loaded 100 of 404 files, more files were not shown because too many files have changed in this diff. Show more