mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-09 13:50:57 +00:00
Merge branch 'master' into racecommittee_app
This commit is contained in:
commit
115deffab3
583 files changed
+44144
-3093
No files matched your search
@@ -35,3 +35,4 @@ Like businesses, sailors need the latest information to make strategic decisions
|
||||
* [Maven Repository Browser](http://maven.sapsailing.com/maven/)
|
||||
* [Main Sailing Website](http://www.sapsailing.com)
|
||||
* [Visitor Statistics](http://analysis.sapsailing.com/)
|
||||
* [Traffic and CPU for app01](http://mrtg.sapsailing.com/)
|
||||
Executable → Regular
File mode changed.
@@ -7,7 +7,9 @@ Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: com.sap.sailing.domain.common,
|
||||
com.sap.sailing.domain.common.filter,
|
||||
com.sap.sailing.domain.common.impl,
|
||||
com.sap.sailing.domain.common.quadtree,
|
||||
com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test",
|
||||
com.sap.sailing.domain.common.racelog
|
||||
com.sap.sailing.domain.common.racelog,
|
||||
com.sap.sailing.domain.common.dto
|
||||
@@ -4,4 +4,5 @@
|
||||
<source path='common'/>
|
||||
<source path='common/impl'/>
|
||||
<source path='common/racelog'/>
|
||||
<source path='common/filter'/>
|
||||
</module>
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@@ -8,13 +9,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
|
||||
public abstract class AbstractLeaderboardDTO implements IsSerializable {
|
||||
public abstract class AbstractLeaderboardDTO implements Serializable {
|
||||
private static final long serialVersionUID = -205106531931903527L;
|
||||
|
||||
public String name;
|
||||
|
||||
private List<RaceColumnDTO> races;
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class BoatClassDTO extends NamedDTO implements IsSerializable {
|
||||
public class BoatClassDTO extends NamedDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1981789833769906676L;
|
||||
private double hullLengthInMeters;
|
||||
|
||||
BoatClassDTO() {}
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class CompetitorDTO extends NamedDTO implements IsSerializable {
|
||||
public class CompetitorDTO extends NamedDTO implements Serializable {
|
||||
private static final long serialVersionUID = -4997852354821083154L;
|
||||
public String twoLetterIsoCountryCode;
|
||||
public String threeLetterIocCountryCode;
|
||||
public String countryName;
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
|
||||
public class FleetDTO extends NamedDTO implements IsSerializable {
|
||||
public class FleetDTO extends NamedDTO {
|
||||
private static final long serialVersionUID = 1336494392278190103L;
|
||||
private Color color;
|
||||
private int orderNo;
|
||||
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -7,8 +8,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
/**
|
||||
* Captures the serializable properties of a leaderboard which in particular has the competitors, any optional display
|
||||
* name mappings for the competitors, races and their net / total points as well as possible reasons for maximum points
|
||||
@@ -17,7 +16,9 @@ import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
* @author Axel Uhl (d043530)
|
||||
*
|
||||
*/
|
||||
public class LeaderboardDTO extends AbstractLeaderboardDTO implements IsSerializable {
|
||||
public class LeaderboardDTO extends AbstractLeaderboardDTO implements Serializable {
|
||||
private static final long serialVersionUID = -520930809792750648L;
|
||||
|
||||
/**
|
||||
* The competitor list, ordered ascending by total rank
|
||||
*/
|
||||
+9
-5
@@ -1,8 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.Tack;
|
||||
@@ -15,7 +15,9 @@ import com.sap.sailing.domain.common.Tack;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class LeaderboardEntryDTO implements IsSerializable {
|
||||
public class LeaderboardEntryDTO implements Serializable {
|
||||
private static final long serialVersionUID = -4678693781217869837L;
|
||||
|
||||
/**
|
||||
* Identifies the race in which the competitor achieved this score. This makes it possible to find out in which
|
||||
* fleet the competitor started in this column.
|
||||
@@ -36,6 +38,8 @@ public class LeaderboardEntryDTO implements IsSerializable {
|
||||
|
||||
public Double totalPoints;
|
||||
|
||||
public Integer rank;
|
||||
|
||||
public boolean discarded;
|
||||
|
||||
public Double windwardDistanceToOverallLeaderInMeters;
|
||||
@@ -54,8 +58,8 @@ public class LeaderboardEntryDTO implements IsSerializable {
|
||||
|
||||
/**
|
||||
* If <code>null</code>, no leg details are known yet, the race is not being tracked or the details
|
||||
* haven't been requested from the server yet. Otherwise, the list holds one entry per {@link Leg} of the
|
||||
* {@link Course} being sailed in the race for which this object holds the scoring details.
|
||||
* haven't been requested from the server yet. Otherwise, the list holds one entry per <code>Leg</code> of the
|
||||
* <code>Course</code> being sailed in the race for which this object holds the scoring details.
|
||||
*/
|
||||
public List<LegEntryDTO> legDetails;
|
||||
|
||||
+4
-4
@@ -1,17 +1,17 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
/**
|
||||
* Holds data about one competitor and all races represented by the owning {@link LeaderboardDTO leaderboard}.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class LeaderboardRowDTO implements IsSerializable {
|
||||
public class LeaderboardRowDTO implements Serializable {
|
||||
private static final long serialVersionUID = -5421934148931661900L;
|
||||
public CompetitorDTO competitor;
|
||||
public Map<String, LeaderboardEntryDTO> fieldsByRaceColumnName;
|
||||
public Double carriedPoints;
|
||||
+4
-3
@@ -1,8 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.NauticalSide;
|
||||
|
||||
@@ -14,7 +14,8 @@ import com.sap.sailing.domain.common.NauticalSide;
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
public class LegEntryDTO implements IsSerializable {
|
||||
public class LegEntryDTO implements Serializable {
|
||||
private static final long serialVersionUID = -1236734337344886025L;
|
||||
public Double distanceTraveledInMeters;
|
||||
public Double averageSpeedOverGroundInKnots;
|
||||
public int rank;
|
||||
+4
-3
@@ -1,8 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class NamedDTO implements IsSerializable {
|
||||
public class NamedDTO implements Serializable {
|
||||
private static final long serialVersionUID = -6620231501480297257L;
|
||||
public String name;
|
||||
|
||||
public NamedDTO() {}
|
||||
+3
-5
@@ -1,9 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
public class PlacemarkDTO extends NamedDTO implements IsSerializable {
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
public class PlacemarkDTO extends NamedDTO {
|
||||
private static final long serialVersionUID = -6734956459060435983L;
|
||||
private String countryCode;
|
||||
private PositionDTO position;
|
||||
private long population;
|
||||
+3
-5
@@ -1,14 +1,12 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
public class PlacemarkOrderDTO extends NamedDTO implements IsSerializable {
|
||||
|
||||
public class PlacemarkOrderDTO extends NamedDTO {
|
||||
private static final long serialVersionUID = -4981887843843495494L;
|
||||
private List<PlacemarkDTO> placemarks;
|
||||
|
||||
public PlacemarkOrderDTO() {
|
||||
+4
-9
@@ -1,9 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import com.google.gwt.i18n.client.NumberFormat;
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PositionDTO implements IsSerializable {
|
||||
public class PositionDTO implements Serializable {
|
||||
private static final long serialVersionUID = -8799012230990258044L;
|
||||
public double latDeg;
|
||||
public double lngDeg;
|
||||
|
||||
@@ -19,11 +19,6 @@ public class PositionDTO implements IsSerializable {
|
||||
return "("+latDeg+", "+lngDeg+")";
|
||||
}
|
||||
|
||||
public String toFormattedString() {
|
||||
NumberFormat fmt = NumberFormat.getFormat("#.###");
|
||||
return fmt.format(latDeg)+", "+fmt.format(lngDeg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@@ -8,12 +9,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.TimingConstants;
|
||||
|
||||
public class RaceColumnDTO extends NamedDTO implements IsSerializable {
|
||||
public class RaceColumnDTO extends NamedDTO implements Serializable {
|
||||
private static final long serialVersionUID = -3228244237400937852L;
|
||||
private boolean medalRace;
|
||||
private List<FleetDTO> fleets;
|
||||
private Map<FleetDTO, RegattaAndRaceIdentifier> trackedRaceIdentifiersPerFleet;
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
public class RaceColumnInSeriesDTO extends RaceColumnDTO {
|
||||
private static final long serialVersionUID = 3308901125173317674L;
|
||||
private String seriesName;
|
||||
private String regattaName;
|
||||
|
||||
+4
-3
@@ -1,12 +1,13 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
|
||||
public class RaceDTO extends NamedDTO implements IsSerializable {
|
||||
public class RaceDTO extends NamedDTO {
|
||||
private static final long serialVersionUID = 2613189982608149975L;
|
||||
|
||||
/**
|
||||
* Tells if this race is currently being tracked, meaning that a {@link RaceTracker} is
|
||||
* listening for incoming GPS fixes, mark passings etc., to update a {@link TrackedRace} object
|
||||
+5
-3
@@ -1,9 +1,11 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
||||
|
||||
public class RaceStatusDTO implements IsSerializable {
|
||||
public class RaceStatusDTO implements Serializable {
|
||||
private static final long serialVersionUID = -5665104615048232751L;
|
||||
public TrackedRaceStatusEnum status;
|
||||
public double loadingProgress;
|
||||
|
||||
+4
-3
@@ -1,10 +1,11 @@
|
||||
package com.sap.sailing.gwt.ui.shared;
|
||||
package com.sap.sailing.domain.common.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
public class TrackedRaceDTO implements Serializable {
|
||||
private static final long serialVersionUID = -9040930745014670924L;
|
||||
|
||||
public class TrackedRaceDTO implements IsSerializable {
|
||||
/**
|
||||
* Default constructor for GWT-Serialization
|
||||
*/
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public abstract class AbstractNumberFilter<FilterObjectType, T extends Number> implements NumberFilter<FilterObjectType, T> {
|
||||
protected T value;
|
||||
protected FilterOperator<T> operator;
|
||||
|
||||
public AbstractNumberFilter() {
|
||||
value = null;
|
||||
operator = null;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public FilterOperator<T> getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void setOperator(FilterOperator<T> operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public abstract class AbstractTextFilter<FilterObjectType> implements TextFilter<FilterObjectType> {
|
||||
protected String value;
|
||||
protected TextOperator operator;
|
||||
|
||||
public AbstractTextFilter() {
|
||||
value = null;
|
||||
operator = null;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public TextOperator getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void setOperator(FilterOperator<String> operator) {
|
||||
this.operator = (TextOperator) operator;
|
||||
}
|
||||
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class BinaryOperator<T extends Number & Comparable<T>> implements FilterOperator<T> {
|
||||
private Operators operator;
|
||||
|
||||
private NumberComparator<T> numberComparator = new NumberComparator<T>();
|
||||
|
||||
/** TODO: Replace with concrete implementations of BinaryOperator */
|
||||
public enum Operators {
|
||||
Equals, /** Returns only records with the specified value */
|
||||
NotEqualTo, /** Returns only records that do not include the specified value */
|
||||
LessThan, /** Returns only records that are less than the specified value */
|
||||
LessThanEquals, /** Returns only records that are less than or equal to the specified value */
|
||||
GreaterThan, /** Returns only records that are more than the specified value */
|
||||
GreaterThanEquals, /** Returns only records that are more than or equal to the specified value */
|
||||
}
|
||||
|
||||
public BinaryOperator(Operators operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public Operators getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void setOperator(Operators operator) {
|
||||
if (operator != null) {
|
||||
this.operator = operator;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchValues(T filterValue, T valueToMatch) {
|
||||
boolean result = false;
|
||||
if (operator != null) {
|
||||
int compareResult = numberComparator.compare(valueToMatch, filterValue);
|
||||
switch (operator) {
|
||||
case LessThanEquals:
|
||||
result = compareResult < 0 || compareResult == 0;
|
||||
break;
|
||||
case Equals:
|
||||
result = compareResult == 0;
|
||||
break;
|
||||
case GreaterThanEquals:
|
||||
result = compareResult > 0 || compareResult == 0;
|
||||
break;
|
||||
case LessThan:
|
||||
result = compareResult < 0;
|
||||
break;
|
||||
case GreaterThan:
|
||||
result = compareResult > 0;
|
||||
break;
|
||||
case NotEqualTo:
|
||||
result = compareResult < 0 || compareResult > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class NumberComparator<S extends Number & Comparable<S>> implements Comparator<S> {
|
||||
public int compare( S a, S b ) throws ClassCastException {
|
||||
return a.compareTo( b );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return operator.name();
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
|
||||
/**
|
||||
* A filter base interface.
|
||||
*/
|
||||
public interface Filter<FilterObjectType> {
|
||||
/**
|
||||
*
|
||||
* @param the object to match against the filter
|
||||
* @return True if the object matches the filter criteria ('is not filtered'), false otherwise
|
||||
*/
|
||||
boolean matches(FilterObjectType object);
|
||||
|
||||
/**
|
||||
* @return The name of the filter
|
||||
*/
|
||||
String getName();
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public interface FilterOperator<ValueType> {
|
||||
boolean matchValues(ValueType filterValue, ValueType valueToMatch);
|
||||
|
||||
String getName();
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A named set of filters.
|
||||
* @author Frank
|
||||
*
|
||||
*/
|
||||
public class FilterSet<FilterObjectType, T extends Filter<FilterObjectType>> {
|
||||
/** the name of the filter set */
|
||||
private String name;
|
||||
|
||||
/** the set of all filters */
|
||||
private final Set<T> filters;
|
||||
|
||||
private boolean editable;
|
||||
|
||||
public FilterSet(String name) {
|
||||
this.name = name;
|
||||
this.editable = true;
|
||||
this.filters = new LinkedHashSet<T>();
|
||||
}
|
||||
|
||||
public boolean addFilter(T filter) {
|
||||
return filters.add(filter);
|
||||
}
|
||||
|
||||
public boolean removeFilter(T filter) {
|
||||
return filters.remove(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* The unmodifiable set of filters
|
||||
*/
|
||||
public Set<T> getFilters() {
|
||||
return Collections.unmodifiableSet(filters);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isEditable() {
|
||||
return editable;
|
||||
}
|
||||
|
||||
public void setEditable(boolean editable) {
|
||||
this.editable = editable;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public interface NumberFilter<FilterObjectType, T extends Number> extends ValueFilter<FilterObjectType, T> {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public interface TextFilter<FilterObjectType> extends ValueFilter<FilterObjectType, String> {
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
public class TextOperator implements FilterOperator<String> {
|
||||
private Operators operator;
|
||||
|
||||
/** TODO: Replace with concrete implementations of TextOperator */
|
||||
public enum Operators {
|
||||
Equals, /** Returns only records with the specified value */
|
||||
NotEqualTo, /** Returns only records that do not include the specified value */
|
||||
Contains, /** Returns only records that contain the specified value */
|
||||
NotContains, /** Returns only records that do not contain the specified value */
|
||||
StartsWith, /** Returns only records that start with the specified value */
|
||||
EndsWith, /** Returns only records that end with the specified value */
|
||||
}
|
||||
|
||||
public TextOperator(Operators operator) {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public Operators getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public void setOperator(Operators operator) {
|
||||
if(operator != null) {
|
||||
this.operator = operator;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchValues(String filterValue, String valueToMatch) {
|
||||
switch(operator) {
|
||||
case Contains:
|
||||
return valueToMatch.indexOf(filterValue) >= 0;
|
||||
case Equals:
|
||||
return valueToMatch.equals(filterValue);
|
||||
case NotContains:
|
||||
return valueToMatch.indexOf(filterValue) < 0;
|
||||
case NotEqualTo:
|
||||
return !valueToMatch.equals(filterValue);
|
||||
case EndsWith:
|
||||
return valueToMatch.endsWith(filterValue);
|
||||
case StartsWith:
|
||||
return valueToMatch.startsWith(filterValue);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return operator.name();
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.sap.sailing.domain.common.filter;
|
||||
|
||||
/**
|
||||
* A value filter using an operator to match a value to be filtered
|
||||
* @author Frank
|
||||
*/
|
||||
public interface ValueFilter<FilterObjectType, ValueType> extends Filter<FilterObjectType> {
|
||||
ValueType getValue();
|
||||
|
||||
FilterOperator<ValueType> getOperator();
|
||||
|
||||
void setValue(ValueType val);
|
||||
|
||||
void setOperator(FilterOperator<ValueType> operator);
|
||||
}
|
||||
+6
@@ -407,6 +407,12 @@ public class StoreAndForward implements Runnable {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception in StoreAndForward", e);
|
||||
try {
|
||||
Thread.sleep(100); // if the exception persists, wait a little, hoping something may recover
|
||||
} catch (InterruptedException e1) {
|
||||
logger.log(Level.INFO, "StoreAndForward.run interrupted; setting stopped=true", e1);
|
||||
stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("StoreAndForward is closing receiving server socket");
|
||||
|
||||
+19
-1
@@ -5,11 +5,13 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.BindException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -55,7 +57,23 @@ public class ActivelyConnectingStoreAndForwardTest {
|
||||
sailMasterDummyListenerThread = new Thread("ActivelyConnectingStoreAndForwardTest-Listener") {
|
||||
public void run() {
|
||||
try {
|
||||
ServerSocket ss = new ServerSocket(RECEIVE_PORT);
|
||||
ServerSocket ss = null;
|
||||
for (int numberOfTries=0; numberOfTries<3 && ss == null; numberOfTries++) {
|
||||
try {
|
||||
ss = new ServerSocket(RECEIVE_PORT);
|
||||
} catch (BindException be) {
|
||||
logger.log(Level.INFO, "Couldn't bind server socket in StoreAndForward"+
|
||||
(numberOfTries<2?". Trying again...":""), be);
|
||||
if (numberOfTries == 2) {
|
||||
throw be;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (ActivelyConnectingStoreAndForwardTest.this) {
|
||||
sendingSocket = ss.accept();
|
||||
logger.info("ActivelyConnectingStoreAndForwardTest-Listener accepted socket connect request on port "+RECEIVE_PORT);
|
||||
|
||||
+19
-3
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertTrue;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.BindException;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.text.DateFormat;
|
||||
@@ -17,6 +18,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -87,8 +89,20 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
swissTimingAdapterPersistence = SwissTimingAdapterPersistence.INSTANCE;
|
||||
swissTimingAdapterPersistence.dropAllMessageData();
|
||||
swissTimingAdapterPersistence.dropAllRaceMasterData();
|
||||
storeAndForward = new StoreAndForward(RECEIVE_PORT, CLIENT_PORT, SwissTimingFactory.INSTANCE,
|
||||
swissTimingAdapterPersistence, mongoDBService);
|
||||
storeAndForward = null;
|
||||
for (int numberOfTries = 0; numberOfTries < 3 && storeAndForward == null; numberOfTries++) {
|
||||
try {
|
||||
storeAndForward = new StoreAndForward(RECEIVE_PORT, CLIENT_PORT, SwissTimingFactory.INSTANCE,
|
||||
swissTimingAdapterPersistence, mongoDBService);
|
||||
} catch (BindException be) {
|
||||
logger.log(Level.INFO, "Couldn't bind server socket in StoreAndForward"+
|
||||
(numberOfTries<2?". Trying again...":""), be);
|
||||
if (numberOfTries == 2) {
|
||||
throw be;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
}
|
||||
sendingSocket = new Socket("localhost", RECEIVE_PORT);
|
||||
sendingStream = sendingSocket.getOutputStream();
|
||||
swissTimingFactory = SwissTimingFactory.INSTANCE;
|
||||
@@ -114,7 +128,9 @@ public class EndToEndListeningStoreAndFowardTest {
|
||||
racingEventService.stopTracking(raceHandle.getRegatta());
|
||||
}
|
||||
logger.info("Calling StoreAndForward.stop() in tearDown");
|
||||
storeAndForward.stop();
|
||||
if (storeAndForward != null) {
|
||||
storeAndForward.stop();
|
||||
}
|
||||
logger.exiting(getClass().getName(), "tearDown");
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
com.sap.sailing.domain.base.Course domainCourse = new CourseImpl("Course", waypoints);
|
||||
BoatClass boatClass = getOrCreateBoatClassFromRaceID(raceID);
|
||||
logger.info("Creating RaceDefinitionImpl for race "+raceID);
|
||||
RaceDefinition result = new RaceDefinitionImpl(raceID, domainCourse, boatClass, competitors);
|
||||
RaceDefinition result = new RaceDefinitionImpl(regatta.getId().toString()+"/"+raceID, domainCourse, boatClass, competitors);
|
||||
regatta.addRace(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
+519
-519
File diff suppressed because it is too large.
Load diff
+6
@@ -4,6 +4,7 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
@@ -44,4 +45,9 @@ public class DummyTrackedRegattaRegistry implements TrackedRegattaRegistry {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRaceBeingTracked(RaceDefinition r) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ Export-Package: com.sap.sailing.domain.base,
|
||||
com.sap.sailing.domain.confidence,
|
||||
com.sap.sailing.domain.confidence.impl;x-friends:="com.sap.sailing.domain.test",
|
||||
com.sap.sailing.domain.leaderboard,
|
||||
com.sap.sailing.domain.leaderboard.caching,
|
||||
com.sap.sailing.domain.leaderboard.impl,
|
||||
com.sap.sailing.domain.leaderboard.meta,
|
||||
com.sap.sailing.domain.polarsheets,
|
||||
@@ -24,4 +25,5 @@ Bundle-ActivationPolicy: lazy
|
||||
Require-Bundle: com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
|
||||
com.sap.sailing.domain.common,
|
||||
org.json.simple;bundle-version="1.1.0"
|
||||
org.json.simple;bundle-version="1.1.0",
|
||||
com.sap.sailing.geocoding
|
||||
@@ -3,14 +3,23 @@ package com.sap.sailing.domain.base;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import com.sap.sailing.domain.base.impl.DomainFactoryImpl;
|
||||
import com.sap.sailing.domain.common.MarkType;
|
||||
import com.sap.sailing.domain.common.NauticalSide;
|
||||
import com.sap.sailing.domain.common.Placemark;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.PlacemarkDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
|
||||
public interface DomainFactory extends SharedDomainFactory {
|
||||
static DomainFactory INSTANCE = new DomainFactoryImpl();
|
||||
@@ -82,4 +91,16 @@ public interface DomainFactory extends SharedDomainFactory {
|
||||
|
||||
ScoringScheme createScoringScheme(ScoringSchemeType scoringSchemeType);
|
||||
|
||||
CompetitorDTO convertToCompetitorDTO(Competitor c);
|
||||
|
||||
FleetDTO convertToFleetDTO(Series series, Fleet fleet);
|
||||
|
||||
FleetDTO convertToFleetDTO(RaceColumn raceColumn, Fleet fleet);
|
||||
|
||||
RaceDTO createRaceDTO(TrackedRegattaRegistry trackedRegattaRegistry, boolean b, RegattaAndRaceIdentifier raceIdentifier, TrackedRace trackedRace);
|
||||
|
||||
PlacemarkDTO convertToPlacemarkDTO(Placemark placemark);
|
||||
|
||||
List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors);
|
||||
|
||||
}
|
||||
+166
@@ -6,29 +6,52 @@ import java.io.Serializable;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Boat;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Gate;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.Nationality;
|
||||
import com.sap.sailing.domain.base.ObjectInputStreamResolvingAgainstDomainFactory;
|
||||
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.base.Team;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.CountryCode;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.MarkType;
|
||||
import com.sap.sailing.domain.common.NauticalSide;
|
||||
import com.sap.sailing.domain.common.Placemark;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WithID;
|
||||
import com.sap.sailing.domain.common.dto.BoatClassDTO;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.PlacemarkDTO;
|
||||
import com.sap.sailing.domain.common.dto.PlacemarkOrderDTO;
|
||||
import com.sap.sailing.domain.common.dto.PositionDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.dto.TrackedRaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.leaderboard.ScoringScheme;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointExtremeSailingSeriesOverall;
|
||||
@@ -36,10 +59,17 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointFirstGets10LastBreaksTie
|
||||
import com.sap.sailing.domain.leaderboard.impl.HighPointLastBreaksTie;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
|
||||
import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.impl.MarkPassingImpl;
|
||||
import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl;
|
||||
import com.sap.sailing.geocoding.ReverseGeocoder;
|
||||
|
||||
public class DomainFactoryImpl implements DomainFactory {
|
||||
private static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName());
|
||||
|
||||
/**
|
||||
* Ensure that the <em>same</em> string is used as key that is also used to set the {@link Nationality}
|
||||
* object's {@link Nationality#getThreeLetterIOCAcronym() IOC code}.
|
||||
@@ -68,6 +98,8 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
|
||||
private final ReferenceQueue<Waypoint> waypointCacheReferenceQueue;
|
||||
|
||||
private final WeakHashMap<Competitor, CompetitorDTO> weakCompetitorDTOCache;
|
||||
|
||||
/**
|
||||
* Weak references to {@link Waypoint} objects of this type are registered with
|
||||
* {@link DomainFactoryImpl#waypointCacheReferenceQueue} upon construction so that when their referents are no
|
||||
@@ -94,6 +126,7 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
private final Set<String> mayStartWithNoUpwindLeg;
|
||||
|
||||
public DomainFactoryImpl() {
|
||||
weakCompetitorDTOCache = new WeakHashMap<Competitor, CompetitorDTO>();
|
||||
waypointCacheReferenceQueue = new ReferenceQueue<Waypoint>();
|
||||
nationalityCache = new HashMap<String, Nationality>();
|
||||
markCache = new HashMap<Serializable, Mark>();
|
||||
@@ -294,4 +327,137 @@ public class DomainFactoryImpl implements DomainFactory {
|
||||
}
|
||||
throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompetitorDTO convertToCompetitorDTO(Competitor c) {
|
||||
CompetitorDTO competitorDTO = weakCompetitorDTOCache.get(c);
|
||||
if (competitorDTO == null) {
|
||||
CountryCode countryCode = c.getTeam().getNationality().getCountryCode();
|
||||
competitorDTO = new CompetitorDTO(c.getName(), countryCode == null ? ""
|
||||
: countryCode.getTwoLetterISOCode(),
|
||||
countryCode == null ? "" : countryCode.getThreeLetterIOCCode(), countryCode == null ? ""
|
||||
: countryCode.getName(), c.getBoat().getSailID(), c.getId().toString(),
|
||||
new BoatClassDTO(c.getBoat().getBoatClass().getName(), c.getBoat().getBoatClass().getHullLength()
|
||||
.getMeters()));
|
||||
weakCompetitorDTOCache.put(c, competitorDTO);
|
||||
}
|
||||
return competitorDTO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FleetDTO convertToFleetDTO(Series series, Fleet fleet) {
|
||||
return new FleetDTO(fleet.getName(), series.getName(), fleet.getOrdering(), fleet.getColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public FleetDTO convertToFleetDTO(RaceColumn raceColumn, Fleet fleet) {
|
||||
String seriesNameOfFleet = null;
|
||||
if (raceColumn instanceof RaceColumnInSeries) {
|
||||
seriesNameOfFleet = ((RaceColumnInSeries) raceColumn).getSeries().getName();
|
||||
}
|
||||
return new FleetDTO(fleet.getName(), seriesNameOfFleet, fleet.getOrdering(), fleet.getColor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceDTO createRaceDTO(TrackedRegattaRegistry trackedRegattaRegistry, boolean withGeoLocationData, RegattaAndRaceIdentifier raceIdentifier, TrackedRace trackedRace) {
|
||||
// Optional: Getting the places of the race
|
||||
PlacemarkOrderDTO racePlaces = withGeoLocationData ? getRacePlaces(trackedRace) : null;
|
||||
TrackedRaceDTO trackedRaceDTO = new TrackedRaceDTO();
|
||||
trackedRaceDTO.startOfTracking = trackedRace.getStartOfTracking() == null ? null : trackedRace.getStartOfTracking().asDate();
|
||||
trackedRaceDTO.hasWindData = trackedRace.hasWindData();
|
||||
trackedRaceDTO.hasGPSData = trackedRace.hasGPSData();
|
||||
RaceDTO raceDTO = new RaceDTO(raceIdentifier, trackedRaceDTO, trackedRegattaRegistry.isRaceBeingTracked(trackedRace.getRace()));
|
||||
raceDTO.places = racePlaces;
|
||||
raceDTO.startOfRace = trackedRace.getStartOfRace() == null ? null : trackedRace.getStartOfRace().asDate();
|
||||
raceDTO.endOfRace = trackedRace.getEndOfRace() == null ? null : trackedRace.getEndOfRace().asDate();
|
||||
return raceDTO;
|
||||
}
|
||||
|
||||
private PlacemarkOrderDTO getRacePlaces(TrackedRace trackedRace) {
|
||||
Pair<Placemark, Placemark> startAndFinish = getStartFinishPlacemarksForTrackedRace(trackedRace);
|
||||
PlacemarkOrderDTO racePlaces = new PlacemarkOrderDTO();
|
||||
if (startAndFinish.getA() != null) {
|
||||
racePlaces.getPlacemarks().add(convertToPlacemarkDTO(startAndFinish.getA()));
|
||||
}
|
||||
if (startAndFinish.getB() != null) {
|
||||
racePlaces.getPlacemarks().add(convertToPlacemarkDTO(startAndFinish.getB()));
|
||||
}
|
||||
if (racePlaces.isEmpty()) {
|
||||
racePlaces = null;
|
||||
}
|
||||
return racePlaces;
|
||||
}
|
||||
|
||||
private Pair<Placemark, Placemark> getStartFinishPlacemarksForTrackedRace(TrackedRace race) {
|
||||
double radiusCalculationFactor = 10.0;
|
||||
Placemark startBest = null;
|
||||
Placemark finishBest = null;
|
||||
|
||||
// Get start postition
|
||||
Iterator<Mark> startMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
|
||||
GPSFix startMarkFix = startMarks.hasNext() ? race.getOrCreateTrack(startMarks.next()).getLastRawFix() : null;
|
||||
Position startPosition = startMarkFix != null ? startMarkFix.getPosition() : null;
|
||||
if (startPosition != null) {
|
||||
try {
|
||||
// Get distance to nearest placemark and calculate the search radius
|
||||
Placemark startNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(startPosition);
|
||||
if (startNearest != null) {
|
||||
Distance startNearestDistance = startNearest.distanceFrom(startPosition);
|
||||
double startRadius = startNearestDistance.getKilometers() * radiusCalculationFactor;
|
||||
|
||||
// Get the estimated best start place
|
||||
startBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(startPosition, startRadius,
|
||||
new Placemark.ByPopulationDistanceRatio(startPosition));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
|
||||
} catch (org.json.simple.parser.ParseException e) {
|
||||
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Get finish position
|
||||
Iterator<Mark> finishMarks = race.getRace().getCourse().getFirstWaypoint().getMarks().iterator();
|
||||
GPSFix finishMarkFix = finishMarks.hasNext() ? race.getOrCreateTrack(finishMarks.next()).getLastRawFix() : null;
|
||||
Position finishPosition = finishMarkFix != null ? finishMarkFix.getPosition() : null;
|
||||
if (startPosition != null && finishPosition != null) {
|
||||
if (startPosition.getDistance(finishPosition).getKilometers() <= ReverseGeocoder.POSITION_CACHE_DISTANCE_LIMIT_IN_KM) {
|
||||
finishBest = startBest;
|
||||
} else {
|
||||
try {
|
||||
// Get distance to nearest placemark and calculate the search radius
|
||||
Placemark finishNearest = ReverseGeocoder.INSTANCE.getPlacemarkNearest(finishPosition);
|
||||
Distance finishNearestDistance = finishNearest.distanceFrom(finishPosition);
|
||||
double finishRadius = finishNearestDistance.getKilometers() * radiusCalculationFactor;
|
||||
|
||||
// Get the estimated best finish place
|
||||
finishBest = ReverseGeocoder.INSTANCE.getPlacemarkLast(finishPosition, finishRadius,
|
||||
new Placemark.ByPopulationDistanceRatio(finishPosition));
|
||||
} catch (IOException e) {
|
||||
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
|
||||
} catch (org.json.simple.parser.ParseException e) {
|
||||
logger.throwing(TrackedRaceImpl.class.getName(), "getPlaceOrder()", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Pair<Placemark, Placemark> placemarks = new Pair<Placemark, Placemark>(startBest, finishBest);
|
||||
return placemarks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlacemarkDTO convertToPlacemarkDTO(Placemark placemark) {
|
||||
Position position = placemark.getPosition();
|
||||
return new PlacemarkDTO(placemark.getName(), placemark.getCountryCode(), new PositionDTO(position.getLatDeg(),
|
||||
position.getLngDeg()), placemark.getPopulation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompetitorDTO> getCompetitorDTOList(List<Competitor> competitors) {
|
||||
List<CompetitorDTO> result = new ArrayList<CompetitorDTO>();
|
||||
for (Competitor competitor : competitors) {
|
||||
result.add(convertToCompetitorDTO(competitor));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,13 +60,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
*/
|
||||
private final boolean persistent;
|
||||
|
||||
/**
|
||||
* Constructs a regatta with an empty {@link RaceLogStore}.
|
||||
*/
|
||||
public RegattaImpl(String baseName, BoatClass boatClass, TrackedRegattaRegistry trackedRegattaRegistry, ScoringScheme scoringScheme, Serializable id) {
|
||||
this(EmptyRaceLogStore.INSTANCE, baseName, boatClass, trackedRegattaRegistry, scoringScheme, id, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a regatta with an empty {@link RaceLogStore}.
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CourseArea;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
@@ -16,9 +19,12 @@ import com.sap.sailing.domain.common.Named;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
|
||||
/**
|
||||
* A leaderboard is used to display the results of one or more {@link TrackedRace races}. It manages the competitors'
|
||||
@@ -61,6 +67,10 @@ public interface Leaderboard extends Named {
|
||||
Fleet getFleet();
|
||||
}
|
||||
|
||||
LeaderboardDTO computeDTO(final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory)
|
||||
throws NoWindException;
|
||||
|
||||
/**
|
||||
* Obtains the unique set of {@link Competitor} objects from all {@link TrackedRace}s currently linked to this
|
||||
* leaderboard, with suppressed competitors removed. See also {@link #getAllCompetitors()} which also returns
|
||||
@@ -381,4 +391,43 @@ public interface Leaderboard extends Named {
|
||||
* @return the default {@link CourseArea} for all races of this leaderboard.
|
||||
*/
|
||||
CourseArea getDefaultCourseArea();
|
||||
|
||||
/**
|
||||
* Must be called when the leaderboard is removed from its server, becoming in accessible. This will give the leaderboard
|
||||
* a chance to release all its resources that won't be collected or freed automatically. In particular, a leaderboard may
|
||||
* hold on to listeners which in turn are registered with {@link TrackedRace}s and therefore won't be released to the garbage
|
||||
* collector unless the tracked race becomes unreferenced. This may take long because the tracked race can generally
|
||||
* be referenced by more than one leaderboard. For example, a test leaderboard may additionally reference a tracked
|
||||
* race already referenced by an "official" leaderboard. When the test leaderboard is removed, its listeners would not
|
||||
* be released and avoid garbage-collecting the test leaderboard. When calling this method, the leaderboard will cleanly
|
||||
* unregister its listeners from the tracked races and therefore become eligible for garbage collection.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Returns a data transfer object (DTO) that has the leaderboard's data for the race columns with basic information
|
||||
* for all columns, and with detailed information for those columns whose names are provided in
|
||||
* <code>namesOfRaceColumnsForWhichToLoadLegDetails</code>. The leaderboard is evaluated at <code>timePoint</code>,
|
||||
* or, if <code>timePoint</code> is <code>null</code>, for the "live" time point (now - delay).
|
||||
* <p>
|
||||
*
|
||||
* The implementation uses different approaches for caching the results. For "live" requests, a
|
||||
* {@link LiveLeaderboardUpdater} is used to keep refreshing the cached results. Other queries are managed by a
|
||||
* {@link LeaderboardDTOCache} which remembers a number of results before it starts evicting the least frequently
|
||||
* used ones.
|
||||
*
|
||||
* @param timePoint
|
||||
* <code>null</code> for "live" results for the time point "now" - delay; otherwise, the explicit time
|
||||
* point at which to evaluate the leaderboard status
|
||||
* @param namesOfRaceColumnsForWhichToLoadLegDetails
|
||||
* the names of the race columns of which to expand the details in the result
|
||||
* @param trackedRegattaRegistry
|
||||
* used to determine which of the races are still being tracked and which ones are not
|
||||
* @param baseDomainFactory
|
||||
* required as factory and cache for various DTO types
|
||||
*/
|
||||
LeaderboardDTO getLeaderboardDTO(TimePoint timePoint,
|
||||
Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException,
|
||||
InterruptedException, ExecutionException;
|
||||
}
|
||||
+1
@@ -15,6 +15,7 @@ import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LeaderboardCache;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEvent;
|
||||
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
|
||||
+4
-1
@@ -1,4 +1,7 @@
|
||||
package com.sap.sailing.domain.leaderboard;
|
||||
package com.sap.sailing.domain.leaderboard.caching;
|
||||
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
|
||||
|
||||
/**
|
||||
* Interface for leaderboard state cache management. When passed to a {@link LeaderboardCacheManager}, leaderboards
|
||||
+61
-77
@@ -1,10 +1,9 @@
|
||||
package com.sap.sailing.gwt.ui.server;
|
||||
package com.sap.sailing.domain.leaderboard.caching;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -15,18 +14,19 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardCache;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardCacheManager;
|
||||
import com.sap.sailing.domain.leaderboard.ScoreCorrection;
|
||||
import com.sap.sailing.domain.leaderboard.ScoreCorrectionListener;
|
||||
import com.sap.sailing.domain.tracking.RaceChangeListener;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.util.impl.LockUtil;
|
||||
|
||||
/**
|
||||
@@ -45,13 +45,13 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
private static final Logger logger = Logger.getLogger(LeaderboardDTOCache.class.getName());
|
||||
|
||||
/**
|
||||
* In live operations, {@link #getLeaderboardByName(String, Date, Collection)} is the application's
|
||||
* In live operations, {@link #getLeaderboardByName(Date, Collection, DomainFactory, TrackedRegattaRegistry)} is the application's
|
||||
* bottleneck. When two clients ask the same data for the same leaderboard with their
|
||||
* <code>waitForLatestAnalyses</code> parameters set to <code>false</code>, expansion state and (quantized) time
|
||||
* stamp, no two computations should be spawned for the two clients. Instead, if the computation is still running,
|
||||
* all clients asking the same wait for the single result. Results are cached in this LRU-based evicting cache.
|
||||
*/
|
||||
private final WeakHashMap<Leaderboard, Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>> leaderboardCache;
|
||||
private final Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> leaderboardCache;
|
||||
private int leaderboardByNameCacheHitCount;
|
||||
private int leaderboardByNameCacheMissCount;
|
||||
|
||||
@@ -62,51 +62,45 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
*/
|
||||
private final boolean waitForLatestAnalyses;
|
||||
|
||||
private final SailingServiceImpl sailingService;
|
||||
|
||||
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
|
||||
/**
|
||||
* A multi-threaded executor for the currently running leaderboard requests, executing the {@link Future}s currently
|
||||
* pending.
|
||||
*/
|
||||
private final Executor computeLeadearboardByNameExecutor;
|
||||
private static final Executor computeLeadearboardByNameExecutor = new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
|
||||
/* maximumPoolSize */ THREAD_POOL_SIZE,
|
||||
/* keepAliveTime */ 60, TimeUnit.SECONDS,
|
||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
private final LeaderboardCacheManager leaderboardCacheManager;
|
||||
|
||||
public LeaderboardDTOCache(SailingServiceImpl sailingService, boolean waitForLatestAnalyses) {
|
||||
this.sailingService = sailingService;
|
||||
private final Leaderboard leaderboard;
|
||||
|
||||
public LeaderboardDTOCache(boolean waitForLatestAnalyses, Leaderboard leaderboard) {
|
||||
this.leaderboard = leaderboard;
|
||||
this.waitForLatestAnalyses = waitForLatestAnalyses;
|
||||
// if the leaderboard becomes weakly referenced and eventually GCed, then so can the cached results for it
|
||||
this.leaderboardCache = new WeakHashMap<Leaderboard, Map<Util.Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>>();
|
||||
this.leaderboardCacheManager = new LeaderboardCacheManager(this);
|
||||
final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
|
||||
this.computeLeadearboardByNameExecutor =
|
||||
new ThreadPoolExecutor(/* corePoolSize */ THREAD_POOL_SIZE,
|
||||
/* maximumPoolSize */ THREAD_POOL_SIZE,
|
||||
/* keepAliveTime */ 60, TimeUnit.SECONDS,
|
||||
/* workQueue */ new LinkedBlockingQueue<Runnable>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate(Leaderboard leaderboard) {
|
||||
synchronized (leaderboardCache) {
|
||||
leaderboardCache.remove(leaderboard);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Leaderboard leaderboard) {
|
||||
LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> map = new LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>() {
|
||||
this.leaderboardCache = new LinkedHashMap<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>>() {
|
||||
private static final long serialVersionUID = 7287916997229815039L;
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> e) {
|
||||
return size() > 10; // remember 10 LeaderboardDTOs per leaderborad
|
||||
}
|
||||
};
|
||||
this.leaderboardCacheManager = new LeaderboardCacheManager(this);
|
||||
this.leaderboardCacheManager.add(leaderboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate(Leaderboard leaderboard) {
|
||||
synchronized (leaderboardCache) {
|
||||
leaderboardCache.put(leaderboard, map);
|
||||
leaderboardCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Leaderboard leaderboard) {}
|
||||
|
||||
/**
|
||||
* If the cache holds entries for the <code>leaderboard</code> requested, compare <code>timePoint</code> to the
|
||||
* {@link #getLatestModification latest modification} affecting the <code>leaderboard</code>. If
|
||||
@@ -119,9 +113,9 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
* {@link SailingServiceImpl#computeLeaderboardByName(Leaderboard, TimePoint, Collection, boolean)} if a new cache
|
||||
* entry needs to be computed. Caching distinguished between
|
||||
*/
|
||||
public LeaderboardDTO getLeaderboardByName(final Leaderboard leaderboard, final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails)
|
||||
throws NoWindException, InterruptedException, ExecutionException {
|
||||
public LeaderboardDTO getLeaderboardByName(final TimePoint timePoint, final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
final DomainFactory baseDomainFactory, final TrackedRegattaRegistry trackedRegattaRegistry) throws NoWindException, InterruptedException,
|
||||
ExecutionException {
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
final TimePoint adjustedTimePoint;
|
||||
TimePoint timePointOfLastModification = leaderboard.getTimePointOfLatestModification();
|
||||
@@ -134,50 +128,40 @@ public class LeaderboardDTOCache implements LeaderboardCache {
|
||||
Util.Pair<TimePoint, Collection<String>> key = new Util.Pair<TimePoint, Collection<String>>(adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
FutureTask<LeaderboardDTO> future = null;
|
||||
Map<Pair<TimePoint, Collection<String>>, FutureTask<LeaderboardDTO>> map = null;
|
||||
boolean cacheHit = false;
|
||||
synchronized (leaderboardCache) {
|
||||
map = leaderboardCache.get(leaderboard);
|
||||
if (map == null) {
|
||||
future = null;
|
||||
leaderboardCacheManager.add(leaderboard); // adds it to leaderboardCache by calling back our add(Leaderboard) method
|
||||
map = leaderboardCache.get(leaderboard);
|
||||
} else {
|
||||
/*
|
||||
* Waiting for latest analyzes results largely regards wind estimation and maneuver cache; see
|
||||
* SmartFutureCache. Even if waitForLatestAnalysis is requested, it is OK to cache. The cache would be
|
||||
* invalidated when the race changes, forcing a new re-calculation based on the latest analysis results.
|
||||
* Once the race stabilizes, the latest analysis results for maneuvers and wind estimation will no
|
||||
* longer change and can quickly be obtained from the respective SmartFutureCache. At the same time, if
|
||||
* a LeaderboardDTOCache entry is found, that was based on the latest analysis results at the time. If
|
||||
* new evidence is received, that would also invalidate the LeaderboardDTOCache. Therefore, it's okay to
|
||||
* re-use the LeaderboardDTOCache match even if the latest analysis results are requested.
|
||||
*/
|
||||
future = map.get(key);
|
||||
}
|
||||
if (future == null) {
|
||||
final Thread callerThread = Thread.currentThread();
|
||||
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
|
||||
@Override
|
||||
public LeaderboardDTO call() throws Exception {
|
||||
// The outer getLeaderboardByName(...) method will always wait for this future's completion.
|
||||
// Therefore, it's safe to propagate the calling thread's locks to this one:
|
||||
LockUtil.propagateLockSetFrom(callerThread);
|
||||
try {
|
||||
LeaderboardDTO result = sailingService.computeLeaderboardByName(leaderboard,
|
||||
adjustedTimePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
waitForLatestAnalyses);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||
}
|
||||
/*
|
||||
* Waiting for latest analyzes results largely regards wind estimation and maneuver cache; see SmartFutureCache.
|
||||
* Even if waitForLatestAnalysis is requested, it is OK to cache. The cache would be invalidated when the race
|
||||
* changes, forcing a new re-calculation based on the latest analysis results. Once the race stabilizes, the
|
||||
* latest analysis results for maneuvers and wind estimation will no longer change and can quickly be obtained
|
||||
* from the respective SmartFutureCache. At the same time, if a LeaderboardDTOCache entry is found, that was
|
||||
* based on the latest analysis results at the time. If new evidence is received, that would also invalidate the
|
||||
* LeaderboardDTOCache. Therefore, it's okay to re-use the LeaderboardDTOCache match even if the latest analysis
|
||||
* results are requested.
|
||||
*/
|
||||
future = leaderboardCache.get(key);
|
||||
if (future == null) {
|
||||
final Thread callerThread = Thread.currentThread();
|
||||
future = new FutureTask<LeaderboardDTO>(new Callable<LeaderboardDTO>() {
|
||||
@Override
|
||||
public LeaderboardDTO call() throws Exception {
|
||||
// The outer getLeaderboardByName(...) method will always wait for this future's completion.
|
||||
// Therefore, it's safe to propagate the calling thread's locks to this one:
|
||||
LockUtil.propagateLockSetFrom(callerThread);
|
||||
try {
|
||||
LeaderboardDTO result = leaderboard.computeDTO(adjustedTimePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, waitForLatestAnalyses,
|
||||
trackedRegattaRegistry, baseDomainFactory);
|
||||
return result;
|
||||
} finally {
|
||||
LockUtil.unpropagateLockSetFrom(callerThread);
|
||||
}
|
||||
});
|
||||
computeLeadearboardByNameExecutor.execute(future);
|
||||
map.put(key, future);
|
||||
} else {
|
||||
cacheHit = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
computeLeadearboardByNameExecutor.execute(future);
|
||||
leaderboardCache.put(key, future);
|
||||
} else {
|
||||
cacheHit = true;
|
||||
}
|
||||
if (cacheHit) {
|
||||
leaderboardByNameCacheHitCount++;
|
||||
+14
-8
@@ -1,4 +1,4 @@
|
||||
package com.sap.sailing.gwt.ui.server;
|
||||
package com.sap.sailing.domain.leaderboard.caching;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -8,11 +8,13 @@ import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
|
||||
/**
|
||||
* Computing a leaderboard live, particularly when the fleet tracked is large, requires considerable resources.
|
||||
@@ -54,8 +56,6 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
|
||||
private final Leaderboard leaderboard;
|
||||
|
||||
private final SailingServiceImpl sailingService;
|
||||
|
||||
/**
|
||||
* Live leaderboard results containing column details for all column names in
|
||||
* {@link #columnNamesForWhichCurrentLiveLeaderboardHasTheDetails}. May be <code>null</code> if
|
||||
@@ -100,10 +100,15 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
* takes longer than {@link #UPDATE_TIMEOUT_IN_MILLIS} milliseconds.
|
||||
*/
|
||||
private Thread thread;
|
||||
|
||||
private TrackedRegattaRegistry trackedRegattaRegistry;
|
||||
|
||||
private DomainFactory baseDomainFactory;
|
||||
|
||||
public LiveLeaderboardUpdater(Leaderboard leaderboard, SailingServiceImpl sailingService) {
|
||||
public LiveLeaderboardUpdater(Leaderboard leaderboard, TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) {
|
||||
this.leaderboard = leaderboard;
|
||||
this.sailingService = sailingService;
|
||||
this.trackedRegattaRegistry = trackedRegattaRegistry;
|
||||
this.baseDomainFactory = baseDomainFactory;
|
||||
this.timePointOfLastRequestForColumnDetails = new HashMap<String, TimePoint>();
|
||||
this.columnNamesForWhichCurrentLiveLeaderboardHasTheDetails = new HashSet<String>();
|
||||
}
|
||||
@@ -248,8 +253,9 @@ public class LiveLeaderboardUpdater implements Runnable {
|
||||
TimePoint timeLastUpdateWasStarted = now;
|
||||
try {
|
||||
final Set<String> namesOfRaceColumnsForWhichToLoadLegDetails = getColumnNamesForWhichToFetchDetails(timePoint);
|
||||
LeaderboardDTO newCacheValue = sailingService.computeLeaderboardByName(leaderboard, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false);
|
||||
LeaderboardDTO newCacheValue = leaderboard.computeDTO(timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails, /* waitForLatestAnalyses */false,
|
||||
trackedRegattaRegistry, baseDomainFactory);
|
||||
updateCacheContents(namesOfRaceColumnsForWhichToLoadLegDetails, newCacheValue);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Unable to update cached leaderboard results for leaderboard " + leaderboard.getName() + ": "
|
||||
+759
-4
@@ -1,41 +1,85 @@
|
||||
package com.sap.sailing.domain.leaderboard.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Set;
|
||||
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;
|
||||
import java.util.concurrent.RunnableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Course;
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.RaceColumnListener;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearing;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.LegType;
|
||||
import com.sap.sailing.domain.common.ManeuverType;
|
||||
import com.sap.sailing.domain.common.MaxPointsReason;
|
||||
import com.sap.sailing.domain.common.NoWindError;
|
||||
import com.sap.sailing.domain.common.NoWindException;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.LegEntryDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
|
||||
import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher;
|
||||
import com.sap.sailing.domain.leaderboard.ScoreCorrection.Result;
|
||||
import com.sap.sailing.domain.leaderboard.SettableScoreCorrection;
|
||||
import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCache;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEvent;
|
||||
import com.sap.sailing.domain.racelog.RaceLogIdentifier;
|
||||
import com.sap.sailing.domain.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.tracking.GPSFixMoving;
|
||||
import com.sap.sailing.domain.tracking.Maneuver;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
|
||||
import com.sap.sailing.domain.tracking.RaceChangeListener;
|
||||
import com.sap.sailing.domain.tracking.TrackedLeg;
|
||||
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRaceStatus;
|
||||
import com.sap.sailing.domain.tracking.TrackedRegattaRegistry;
|
||||
import com.sap.sailing.domain.tracking.Wind;
|
||||
import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
|
||||
/**
|
||||
@@ -49,6 +93,8 @@ import com.sap.sailing.util.impl.RaceColumnListeners;
|
||||
*/
|
||||
public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, RaceColumnListener {
|
||||
private static final long serialVersionUID = 330156778603279333L;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(AbstractSimpleLeaderboardImpl.class.getName());
|
||||
|
||||
static final Double DOUBLE_0 = new Double(0);
|
||||
|
||||
@@ -78,6 +124,27 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
* A synchronized set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}.
|
||||
*/
|
||||
private final Set<Competitor> suppressedCompetitors;
|
||||
|
||||
private final Map<Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>> raceDetailsAtEndOfTrackingCache;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
private transient LeaderboardDTOCache leaderboardDTOCache;
|
||||
|
||||
/**
|
||||
* A leaderboard entry representing a snapshot of a cell at a given time point for a single race/competitor.
|
||||
@@ -170,14 +237,129 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractSimpleLeaderboardImpl(SettableScoreCorrection scoreCorrection,
|
||||
ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
||||
/**
|
||||
* Handles the invalidation of the {@link SailingServiceImpl#raceDetailsAtEndOfTrackingCache} entries if the tracked race
|
||||
* changes in any way.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private class CacheInvalidationListener implements RaceChangeListener {
|
||||
private final TrackedRace trackedRace;
|
||||
private final Competitor competitor;
|
||||
|
||||
public CacheInvalidationListener(TrackedRace trackedRace, Competitor competitor) {
|
||||
this.trackedRace = trackedRace;
|
||||
this.competitor = competitor;
|
||||
}
|
||||
|
||||
public TrackedRace getTrackedRace() {
|
||||
return trackedRace;
|
||||
}
|
||||
|
||||
public void removeFromTrackedRace() {
|
||||
trackedRace.removeListener(this);
|
||||
}
|
||||
|
||||
private void invalidateCacheAndRemoveThisListenerFromTrackedRace() {
|
||||
synchronized (raceDetailsAtEndOfTrackingCache) {
|
||||
raceDetailsAtEndOfTrackingCache.remove(new Pair<TrackedRace, Competitor>(trackedRace, competitor));
|
||||
removeFromTrackedRace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorPositionChanged(GPSFixMoving fix, Competitor competitor) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(TrackedRaceStatus newStatus) {
|
||||
// when the status changes away from LOADING, calculations may start or resume, making it necessary to clear the cache
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPositionChanged(GPSFix fix, Mark mark) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
|
||||
Iterable<MarkPassing> markPassings) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windDataReceived(Wind wind, WindSource windSource) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windDataRemoved(Wind wind, WindSource windSource) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void raceTimesChanged(TimePoint startOfTracking, TimePoint endOfTracking, TimePoint startTimeReceived) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delayToLiveChanged(long delayToLiveInMillis) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windSourcesToExcludeChanged(Iterable<? extends WindSource> windSourcesToExclude) {
|
||||
invalidateCacheAndRemoveThisListenerFromTrackedRace();
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractSimpleLeaderboardImpl(SettableScoreCorrection scoreCorrection, ThresholdBasedResultDiscardingRule resultDiscardingRule) {
|
||||
this.carriedPoints = new HashMap<Competitor, Double>();
|
||||
this.scoreCorrection = scoreCorrection;
|
||||
this.displayNames = new HashMap<Competitor, String>();
|
||||
this.resultDiscardingRule = resultDiscardingRule;
|
||||
this.suppressedCompetitors = Collections.synchronizedSet(new HashSet<Competitor>());
|
||||
raceColumnListeners = new RaceColumnListeners();
|
||||
this.raceColumnListeners = new RaceColumnListeners();
|
||||
this.raceDetailsAtEndOfTrackingCache = new HashMap<Pair<TrackedRace, Competitor>, RunnableFuture<RaceDetails>>();
|
||||
initTransientFields();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
initTransientFields();
|
||||
}
|
||||
|
||||
private void initTransientFields() {
|
||||
this.raceDetailsExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
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>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
for (CacheInvalidationListener cacheInvalidationListener : cacheInvalidationListeners) {
|
||||
cacheInvalidationListener.removeFromTrackedRace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -490,6 +672,21 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
@Override
|
||||
public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) {
|
||||
getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace);
|
||||
// It's generally possible that a leaderboard links to the same tracked race in multiple columns / fleets;
|
||||
// only if it no longer references the trackedRace currently unlinked from one column/fleet, also unlink
|
||||
// all cache invalidation listeners for said trackedRace
|
||||
if (!Util.contains(getTrackedRaces(), trackedRace)) {
|
||||
synchronized (cacheInvalidationListeners) {
|
||||
for (Iterator<CacheInvalidationListener> cacheInvalidationListenerIter=cacheInvalidationListeners.iterator();
|
||||
cacheInvalidationListenerIter.hasNext(); ) {
|
||||
CacheInvalidationListener cacheInvalidationListener = cacheInvalidationListenerIter.next();
|
||||
if (cacheInvalidationListener.getTrackedRace() == trackedRace) {
|
||||
cacheInvalidationListener.removeFromTrackedRace();
|
||||
cacheInvalidationListenerIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -769,4 +966,562 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) {
|
||||
getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderboardDTO computeDTO(final TimePoint timePoint,
|
||||
final Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails, final boolean waitForLatestAnalyses,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory)
|
||||
throws NoWindException {
|
||||
long startOfRequestHandling = System.currentTimeMillis();
|
||||
LeaderboardDTO result = null;
|
||||
result = new LeaderboardDTO(this.getScoreCorrection().getTimePointOfLastCorrectionsValidity()==null ?
|
||||
null : this.getScoreCorrection().getTimePointOfLastCorrectionsValidity().asDate(),
|
||||
this.getScoreCorrection()==null?null:this.getScoreCorrection().getComment(),
|
||||
this.getScoringScheme().isHigherBetter());
|
||||
result.competitors = new ArrayList<CompetitorDTO>();
|
||||
result.name = this.getName();
|
||||
result.competitorDisplayNames = new HashMap<CompetitorDTO, String>();
|
||||
for (Competitor suppressedCompetitor : this.getSuppressedCompetitors()) {
|
||||
result.setSuppressed(baseDomainFactory.convertToCompetitorDTO(suppressedCompetitor), true);
|
||||
}
|
||||
for (RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
RaceColumnDTO raceColumnDTO = result.createEmptyRaceColumn(raceColumn.getName(), raceColumn.isMedalRace(),
|
||||
this.getScoringScheme().isValidInTotalScore(this, raceColumn, timePoint));
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
TimePoint latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = null;
|
||||
RegattaAndRaceIdentifier raceIdentifier = null;
|
||||
RaceDTO race = null;
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
|
||||
final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(raceColumn, fleet);
|
||||
if (trackedRace != null) {
|
||||
raceIdentifier = new RegattaNameAndRaceName(trackedRace.getTrackedRegatta().getRegatta()
|
||||
.getName(), trackedRace.getRace().getName());
|
||||
race = baseDomainFactory.createRaceDTO(trackedRegattaRegistry, false, raceIdentifier, trackedRace);
|
||||
if (trackedRace.hasStarted(timePoint) && trackedRace.hasGPSData() && trackedRace.hasWindData()) {
|
||||
TimePoint liveTimePointForTrackedRace = timePoint;
|
||||
final TimePoint endOfRace = trackedRace.getEndOfRace();
|
||||
if (endOfRace != null) {
|
||||
liveTimePointForTrackedRace = endOfRace;
|
||||
}
|
||||
latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive = liveTimePointForTrackedRace;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: the RaceColumnDTO won't be created by the following addRace call because it has been created
|
||||
// above by the result.createEmptyRaceColumn call
|
||||
result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(),
|
||||
fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race);
|
||||
if (latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive != null) {
|
||||
raceColumnDTO.setWhenLastTrackedRaceWasLive(fleetDTO, latestTimePointAfterQueryTimePointWhenATrackedRaceWasLive.asDate());
|
||||
}
|
||||
}
|
||||
result.setCompetitorsFromBestToWorst(raceColumnDTO, baseDomainFactory.getCompetitorDTOList(this.getCompetitorsFromBestToWorst(raceColumn, timePoint)));
|
||||
}
|
||||
result.setDelayToLiveInMillisForLatestRace(this.getDelayToLiveInMillis());
|
||||
result.rows = new HashMap<CompetitorDTO, LeaderboardRowDTO>();
|
||||
result.hasCarriedPoints = this.hasCarriedPoints();
|
||||
result.discardThresholds = this.getResultDiscardingRule().getDiscardIndexResultsStartingWithHowManyRaces();
|
||||
// Computing the competitor leg ranks is expensive, especially in live mode, in case new events keep invalidating
|
||||
// the ranks cache in TrackedLegImpl. Then problem then is that the sorting based on wind data is repeated for each
|
||||
// competitor, leading to square effort. We therefore need to compute the leg ranks for those race where leg details
|
||||
// are requested only once and pass them into getLeaderboardEntryDTO
|
||||
final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache = new HashMap<Leg, LinkedHashMap<Competitor,Integer>>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
// if details for the column are requested, cache the leg's ranks
|
||||
if (namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName())) {
|
||||
for (Fleet fleet : raceColumn.getFleets()) {
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(fleet);
|
||||
if (trackedRace != null) {
|
||||
trackedRace.getRace().getCourse().lockForRead();
|
||||
try {
|
||||
for (TrackedLeg trackedLeg : trackedRace.getTrackedLegs()) {
|
||||
legRanksCache.put(trackedLeg.getLeg(), trackedLeg.getRanks(timePoint));
|
||||
}
|
||||
} finally {
|
||||
trackedRace.getRace().getCourse().unlockAfterRead();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final Competitor competitor : this.getCompetitorsFromBestToWorst(timePoint)) {
|
||||
CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
|
||||
LeaderboardRowDTO row = new LeaderboardRowDTO();
|
||||
row.competitor = competitorDTO;
|
||||
row.fieldsByRaceColumnName = new HashMap<String, LeaderboardEntryDTO>();
|
||||
row.carriedPoints = this.hasCarriedPoints(competitor) ? this.getCarriedPoints(competitor) : null;
|
||||
addOverallDetailsToRow(timePoint, competitor, row);
|
||||
result.competitors.add(competitorDTO);
|
||||
Map<String, Future<LeaderboardEntryDTO>> futuresForColumnName = new HashMap<String, Future<LeaderboardEntryDTO>>();
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(new Callable<LeaderboardEntryDTO>() {
|
||||
@Override
|
||||
public LeaderboardEntryDTO call() {
|
||||
try {
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn.getName()),
|
||||
waitForLatestAnalyses, legRanksCache, baseDomainFactory);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Exception trying to compute leaderboard entry for competitor "+competitor.getName()+
|
||||
" in race column "+raceColumn.getName()+": "+e.getMessage());
|
||||
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(), "computeLeaderboardByName.future.call()", e);
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
executor.execute(future);
|
||||
futuresForColumnName.put(raceColumn.getName(), future);
|
||||
}
|
||||
for (Map.Entry<String, Future<LeaderboardEntryDTO>> raceColumnNameAndFuture : futuresForColumnName.entrySet()) {
|
||||
try {
|
||||
row.fieldsByRaceColumnName.put(raceColumnNameAndFuture.getKey(), raceColumnNameAndFuture.getValue().get());
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
// See also bug 1371: for stability reasons, don't let the exception percolate but rather accept null values.
|
||||
// If new evidence is provided, a re-calculation of the leaderboard will be triggered anyway. So this helps
|
||||
// robustness from a user's perspective.
|
||||
logger.log(Level.SEVERE, AbstractSimpleLeaderboardImpl.class.getName()+".computeDTO("
|
||||
+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+
|
||||
"): exception during computing leaderboard entry for competitor "+competitor.getName()+
|
||||
" in race column "+raceColumnNameAndFuture.getKey()+". Leaving empty.", e);
|
||||
}
|
||||
}
|
||||
result.rows.put(competitorDTO, row);
|
||||
String displayName = this.getDisplayName(competitor);
|
||||
if (displayName != null) {
|
||||
result.competitorDisplayNames.put(competitorDTO, displayName);
|
||||
}
|
||||
}
|
||||
// set race ranks for all LeaderboardEntryDTO's
|
||||
for(RaceColumnDTO raceColumnDTO: result.getRaceList()) {
|
||||
int raceRank = 1;
|
||||
for(CompetitorDTO competitorDTO: result.getCompetitorsFromBestToWorst(raceColumnDTO)) {
|
||||
LeaderboardRowDTO leaderboardRowDTO = result.rows.get(competitorDTO);
|
||||
LeaderboardEntryDTO leaderboardEntryDTO = leaderboardRowDTO.fieldsByRaceColumnName.get(raceColumnDTO.name);
|
||||
leaderboardEntryDTO.rank = raceRank++;
|
||||
}
|
||||
}
|
||||
logger.info("computeLeaderboardByName("+this.getName()+", "+timePoint+", "+namesOfRaceColumnsForWhichToLoadLegDetails+") took "+
|
||||
(System.currentTimeMillis()-startOfRequestHandling)+"ms");
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addOverallDetailsToRow(final TimePoint timePoint,
|
||||
final Competitor competitor, LeaderboardRowDTO row) throws NoWindException {
|
||||
final Pair<GPSFixMoving, Speed> maximumSpeedOverGround = this.getMaximumSpeedOverGround(competitor, timePoint);
|
||||
if (maximumSpeedOverGround != null && maximumSpeedOverGround.getB() != null) {
|
||||
row.maximumSpeedOverGroundInKnots = maximumSpeedOverGround.getB().getKnots();
|
||||
row.whenMaximumSpeedOverGroundWasAchieved = maximumSpeedOverGround.getA().getTimePoint().asDate();
|
||||
}
|
||||
final Long totalTimeSailedDownwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.DOWNWIND, timePoint);
|
||||
row.totalTimeSailedDownwindInSeconds = totalTimeSailedDownwindInMilliseconds==null?null:1./1000.*totalTimeSailedDownwindInMilliseconds;
|
||||
final Long totalTimeSailedUpwindInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.UPWIND, timePoint);
|
||||
row.totalTimeSailedUpwindInSeconds = totalTimeSailedUpwindInMilliseconds==null?null:1./1000.*totalTimeSailedUpwindInMilliseconds;
|
||||
final Long totalTimeSailedReachingInMilliseconds = this.getTotalTimeSailedInLegTypeInMilliseconds(competitor, LegType.REACHING, timePoint);
|
||||
row.totalTimeSailedReachingInSeconds = totalTimeSailedReachingInMilliseconds==null?null:1./1000.*totalTimeSailedReachingInMilliseconds;
|
||||
final Long totalTimeSailedInMilliseconds = this.getTotalTimeSailedInMilliseconds(competitor, timePoint);
|
||||
row.totalTimeSailedInSeconds = totalTimeSailedInMilliseconds==null?null:1./1000.*totalTimeSailedInMilliseconds;
|
||||
final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint);
|
||||
row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param waitForLatestAnalyses
|
||||
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
|
||||
* may not reflect all data already received; otherwise, the method will always block for the latest
|
||||
* cache updates to have happened before returning.
|
||||
* @param baseDomainFactory TODO
|
||||
*/
|
||||
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
|
||||
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
|
||||
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory) throws NoWindException {
|
||||
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
||||
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
||||
entryDTO.netPoints = entry.getNetPoints();
|
||||
entryDTO.netPointsCorrected = entry.isNetPointsCorrected();
|
||||
entryDTO.totalPoints = entry.getTotalPoints();
|
||||
entryDTO.reasonForMaxPoints = entry.getMaxPointsReason();
|
||||
entryDTO.discarded = entry.isDiscarded();
|
||||
if (addLegDetails && trackedRace != null) {
|
||||
try {
|
||||
RaceDetails raceDetails = getRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
|
||||
entryDTO.legDetails = raceDetails.getLegDetails();
|
||||
entryDTO.windwardDistanceToOverallLeaderInMeters = raceDetails.getWindwardDistanceToOverallLeader() == null ? null
|
||||
: raceDetails.getWindwardDistanceToOverallLeader().getMeters();
|
||||
entryDTO.averageCrossTrackErrorInMeters = raceDetails.getAverageCrossTrackError() == null ? null
|
||||
: raceDetails.getAverageCrossTrackError().getMeters();
|
||||
final TimePoint startOfRace = trackedRace.getStartOfRace();
|
||||
if (startOfRace != null) {
|
||||
Distance distanceToStartLineAtStartOfRace = trackedRace.getDistanceToStartLine(competitor,
|
||||
startOfRace);
|
||||
entryDTO.distanceToStartLineAtStartOfRaceInMeters = distanceToStartLineAtStartOfRace == null ? null
|
||||
: distanceToStartLineAtStartOfRace.getMeters();
|
||||
Speed speedAtStartTime = trackedRace.getTrack(competitor).getEstimatedSpeed(startOfRace);
|
||||
entryDTO.speedOverGroundAtStartOfRaceInKnots = speedAtStartTime == null ? null : speedAtStartTime.getKnots();
|
||||
NavigableSet<MarkPassing> competitorMarkPassings = trackedRace.getMarkPassings(competitor);
|
||||
trackedRace.lockForRead(competitorMarkPassings);
|
||||
try {
|
||||
if (!competitorMarkPassings.isEmpty()) {
|
||||
TimePoint competitorStartTime = competitorMarkPassings.iterator().next().getTimePoint();
|
||||
Speed competitorSpeedWhenPassingStart = trackedRace.getTrack(competitor).getEstimatedSpeed(
|
||||
competitorStartTime);
|
||||
entryDTO.speedOverGroundAtPassingStartWaypointInKnots = competitorSpeedWhenPassingStart == null ? null
|
||||
: competitorSpeedWhenPassingStart.getKnots();
|
||||
entryDTO.startTack = trackedRace.getTack(competitor, competitorStartTime);
|
||||
Distance distanceFromStarboardSideOfStartLineWhenPassingStart = trackedRace.getDistanceFromStarboardSideOfStartLineWhenPassingStart(competitor);
|
||||
entryDTO.distanceToStarboardSideOfStartLineInMeters = distanceFromStarboardSideOfStartLineWhenPassingStart == null ? null :
|
||||
distanceFromStarboardSideOfStartLineWhenPassingStart.getMeters();
|
||||
}
|
||||
} finally {
|
||||
trackedRace.unlockAfterRead(competitorMarkPassings);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e); // the future used to calculate the leg details was interrupted; escalate as runtime exception
|
||||
}
|
||||
}
|
||||
final Fleet fleet = entry.getFleet();
|
||||
entryDTO.fleet = fleet == null ? null : baseDomainFactory.convertToFleetDTO(raceColumn, fleet);
|
||||
return entryDTO;
|
||||
}
|
||||
|
||||
private static class RaceDetails {
|
||||
private final List<LegEntryDTO> legDetails;
|
||||
private final Distance windwardDistanceToOverallLeader;
|
||||
private final Distance averageCrossTrackError;
|
||||
public RaceDetails(List<LegEntryDTO> legDetails, Distance windwardDistanceToOverallLeader,
|
||||
Distance averageCrossTrackError) {
|
||||
super();
|
||||
this.legDetails = legDetails;
|
||||
this.windwardDistanceToOverallLeader = windwardDistanceToOverallLeader;
|
||||
this.averageCrossTrackError = averageCrossTrackError;
|
||||
}
|
||||
public List<LegEntryDTO> getLegDetails() {
|
||||
return legDetails;
|
||||
}
|
||||
public Distance getWindwardDistanceToOverallLeader() {
|
||||
return windwardDistanceToOverallLeader;
|
||||
}
|
||||
public Distance getAverageCrossTrackError() {
|
||||
return averageCrossTrackError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>timePoint</code> is after the end of the race's tracking the query will be adjusted to obtain the values
|
||||
* at the end of the {@link TrackedRace#getEndOfTracking() race's tracking time}. If the time point adjusted this
|
||||
* way equals the end of the tracking time, the query results will be looked up in a cache first and if not found,
|
||||
* they will be stored to the cache after calculating them. A cache invalidation {@link RaceChangeListener listener}
|
||||
* will be registered with the race which will be triggered for any event received by the race.
|
||||
* @param waitForLatestAnalyses
|
||||
* if <code>false</code>, this method is allowed to read the maneuver analysis results from a cache that
|
||||
* may not reflect all data already received; otherwise, the method will always block for the latest
|
||||
* cache updates to have happened before returning.
|
||||
*/
|
||||
private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException, InterruptedException, ExecutionException {
|
||||
RaceDetails raceDetails;
|
||||
if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
|
||||
raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache);
|
||||
} else {
|
||||
raceDetails = calculateRaceDetails(trackedRace, competitor, timePoint, waitForLatestAnalyses, legRanksCache);
|
||||
}
|
||||
return raceDetails;
|
||||
}
|
||||
|
||||
private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
|
||||
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache)
|
||||
throws NoWindException, InterruptedException, ExecutionException {
|
||||
final Pair<TrackedRace, Competitor> key = new Pair<TrackedRace, Competitor>(trackedRace, competitor);
|
||||
RunnableFuture<RaceDetails> raceDetails;
|
||||
synchronized (raceDetailsAtEndOfTrackingCache) {
|
||||
raceDetails = raceDetailsAtEndOfTrackingCache.get(key);
|
||||
if (raceDetails == null) {
|
||||
raceDetails = new FutureTask<RaceDetails>(new Callable<RaceDetails>() {
|
||||
@Override
|
||||
public RaceDetails call() throws Exception {
|
||||
TimePoint end = trackedRace.getEndOfRace();
|
||||
if (end == null) {
|
||||
end = trackedRace.getEndOfTracking();
|
||||
}
|
||||
return calculateRaceDetails(trackedRace, competitor, end,
|
||||
// TODO see bug 1358: for now, use waitForLatest==false until we've switched to optimistic locking for the course read lock
|
||||
/* TODO old comment when it was still true: "because this is done only once after end of tracking" */
|
||||
/* waitForLatestAnalyses (maneuver and cross track error) */ false,
|
||||
legRanksCache);
|
||||
}
|
||||
});
|
||||
raceDetailsExecutor.execute(raceDetails);
|
||||
raceDetailsAtEndOfTrackingCache.put(key, raceDetails);
|
||||
final CacheInvalidationListener cacheInvalidationListener = new CacheInvalidationListener(trackedRace, competitor);
|
||||
trackedRace.addListener(cacheInvalidationListener);
|
||||
cacheInvalidationListeners.add(cacheInvalidationListener);
|
||||
}
|
||||
}
|
||||
return raceDetails.get();
|
||||
}
|
||||
|
||||
private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
|
||||
List<LegEntryDTO> legDetails;
|
||||
legDetails = new ArrayList<LegEntryDTO>();
|
||||
final Course course = trackedRace.getRace().getCourse();
|
||||
course.lockForRead(); // hold back any course re-configurations while looping over the legs
|
||||
try {
|
||||
for (Leg leg : course.getLegs()) {
|
||||
LegEntryDTO legEntry;
|
||||
// We loop over a copy of the course's legs; during a course change, legs may become "stale," even with
|
||||
// regard to the leg/trackedLeg structures inside the tracked race which is updated by the course change
|
||||
// immediately. That's why we've acquired a read lock for the course above.
|
||||
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, leg);
|
||||
if (trackedLeg != null && trackedLeg.hasStartedLeg(timePoint)) {
|
||||
legEntry = createLegEntry(trackedLeg, timePoint, waitForLatestAnalyses, legRanksCache);
|
||||
} else {
|
||||
legEntry = null;
|
||||
}
|
||||
legDetails.add(legEntry);
|
||||
}
|
||||
final Distance windwardDistanceToOverallLeader = trackedRace == null ? null : trackedRace
|
||||
.getWindwardDistanceToOverallLeader(competitor, timePoint);
|
||||
final Distance averageCrossTrackError = trackedRace == null ? null : trackedRace.getAverageCrossTrackError(
|
||||
competitor, timePoint, waitForLatestAnalyses);
|
||||
return new RaceDetails(legDetails, windwardDistanceToOverallLeader, averageCrossTrackError);
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
|
||||
private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache) throws NoWindException {
|
||||
LegEntryDTO result;
|
||||
if (trackedLeg == null) {
|
||||
result = null;
|
||||
} else {
|
||||
result = new LegEntryDTO();
|
||||
final Speed averageSpeedOverGround = trackedLeg.getAverageSpeedOverGround(timePoint);
|
||||
result.averageSpeedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
|
||||
final Distance averageCrossTrackError = trackedLeg.getAverageCrossTrackError(timePoint, waitForLatestAnalyses);
|
||||
result.averageCrossTrackErrorInMeters = averageCrossTrackError == null ? null : averageCrossTrackError.getMeters();
|
||||
Double speedOverGroundInKnots;
|
||||
if (trackedLeg.hasFinishedLeg(timePoint)) {
|
||||
speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
|
||||
} else {
|
||||
final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
|
||||
speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
|
||||
}
|
||||
result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
|
||||
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
|
||||
result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters();
|
||||
result.estimatedTimeToNextWaypointInSeconds = trackedLeg.getEstimatedTimeToNextMarkInSeconds(timePoint);
|
||||
result.timeInMilliseconds = trackedLeg.getTimeInMilliSeconds(timePoint);
|
||||
result.finished = trackedLeg.hasFinishedLeg(timePoint);
|
||||
result.gapToLeaderInSeconds = trackedLeg.getGapToLeaderInSeconds(timePoint,
|
||||
legRanksCache.get(trackedLeg.getLeg()).entrySet().iterator().next().getKey());
|
||||
if (result.gapToLeaderInSeconds != null) {
|
||||
// FIXME problem: asking just after the beginning of the leg yields very different values from asking for the end of the previous leg.
|
||||
// This is because for the previous leg it's decided based on the mark passings; for the next (current) leg it's decided based on
|
||||
// windward distance and VMG
|
||||
Double gapAtEndOfPreviousLegInSeconds = getGapAtEndOfPreviousLegInSeconds(trackedLeg);
|
||||
if (gapAtEndOfPreviousLegInSeconds != null) {
|
||||
result.gapChangeSinceLegStartInSeconds = result.gapToLeaderInSeconds - gapAtEndOfPreviousLegInSeconds;
|
||||
}
|
||||
}
|
||||
LinkedHashMap<Competitor, Integer> legRanks = legRanksCache.get(trackedLeg.getLeg());
|
||||
if (legRanks != null) {
|
||||
result.rank = legRanks.get(trackedLeg.getCompetitor());
|
||||
} else {
|
||||
result.rank = trackedLeg.getRank(timePoint);
|
||||
}
|
||||
result.started = trackedLeg.hasStartedLeg(timePoint);
|
||||
Speed velocityMadeGood;
|
||||
if (trackedLeg.hasFinishedLeg(timePoint)) {
|
||||
velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint);
|
||||
} else {
|
||||
velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint);
|
||||
}
|
||||
result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots();
|
||||
Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint);
|
||||
result.windwardDistanceToGoInMeters = windwardDistanceToGo == null ? null : windwardDistanceToGo
|
||||
.getMeters();
|
||||
final TimePoint startOfRace = trackedLeg.getTrackedLeg().getTrackedRace().getStartOfRace();
|
||||
if (startOfRace != null && trackedLeg.hasStartedLeg(timePoint)) {
|
||||
// not using trackedLeg.getManeuvers(...) because it may not catch the mark passing maneuver starting this leg
|
||||
// because that may have been detected as slightly before the mark passing time, hence associated with the previous leg
|
||||
List<Maneuver> maneuvers = trackedLeg.getTrackedLeg().getTrackedRace()
|
||||
.getManeuvers(trackedLeg.getCompetitor(), startOfRace, timePoint, waitForLatestAnalyses);
|
||||
if (maneuvers != null) {
|
||||
result.numberOfManeuvers = new HashMap<ManeuverType, Integer>();
|
||||
result.numberOfManeuvers.put(ManeuverType.TACK, 0);
|
||||
result.numberOfManeuvers.put(ManeuverType.JIBE, 0);
|
||||
result.numberOfManeuvers.put(ManeuverType.PENALTY_CIRCLE, 0);
|
||||
Map<ManeuverType, Double> totalManeuverLossInMeters = new HashMap<ManeuverType, Double>();
|
||||
totalManeuverLossInMeters.put(ManeuverType.TACK, 0.0);
|
||||
totalManeuverLossInMeters.put(ManeuverType.JIBE, 0.0);
|
||||
totalManeuverLossInMeters.put(ManeuverType.PENALTY_CIRCLE, 0.0);
|
||||
TimePoint startOfLeg = trackedLeg.getStartTime();
|
||||
for (Maneuver maneuver : maneuvers) {
|
||||
// don't count maneuvers that were in previous legs
|
||||
switch (maneuver.getType()) {
|
||||
case TACK:
|
||||
case JIBE:
|
||||
case PENALTY_CIRCLE:
|
||||
if (!maneuver.getTimePoint().before(startOfLeg) && (!trackedLeg.hasFinishedLeg(timePoint) ||
|
||||
maneuver.getTimePoint().before(trackedLeg.getFinishTime()))) {
|
||||
if (maneuver.getManeuverLoss() != null) {
|
||||
result.numberOfManeuvers.put(maneuver.getType(),
|
||||
result.numberOfManeuvers.get(maneuver.getType()) + 1);
|
||||
totalManeuverLossInMeters.put(maneuver.getType(),
|
||||
totalManeuverLossInMeters.get(maneuver.getType())
|
||||
+ maneuver.getManeuverLoss().getMeters());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MARK_PASSING:
|
||||
// analyze all mark passings, not only those after this leg's start, to catch the mark passing
|
||||
// maneuver starting this leg, even if its time point is slightly before the mark passing starting this leg
|
||||
MarkPassingManeuver mpm = (MarkPassingManeuver) maneuver;
|
||||
if (mpm.getWaypointPassed() == trackedLeg.getLeg().getFrom()) {
|
||||
result.sideToWhichMarkAtLegStartWasRounded = mpm.getSide();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* Do nothing here.
|
||||
* Throwing an exception destroys the toggling (and maybe other behaviour) of the leaderboard.
|
||||
*/
|
||||
}
|
||||
}
|
||||
result.averageManeuverLossInMeters = new HashMap<ManeuverType, Double>();
|
||||
for (ManeuverType maneuverType : new ManeuverType[] { ManeuverType.TACK, ManeuverType.JIBE,
|
||||
ManeuverType.PENALTY_CIRCLE }) {
|
||||
if (result.numberOfManeuvers.get(maneuverType) != 0) {
|
||||
result.averageManeuverLossInMeters.put(
|
||||
maneuverType,
|
||||
totalManeuverLossInMeters.get(maneuverType)
|
||||
/ result.numberOfManeuvers.get(maneuverType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Double getGapAtEndOfPreviousLegInSeconds(TrackedLegOfCompetitor trackedLeg) throws NoWindException {
|
||||
final Double result;
|
||||
final Course course = trackedLeg.getTrackedLeg().getTrackedRace().getRace().getCourse();
|
||||
course.lockForRead();
|
||||
try {
|
||||
int indexOfStartWaypoint = course.getIndexOfWaypoint(trackedLeg.getLeg().getFrom());
|
||||
if (indexOfStartWaypoint == 0) {
|
||||
// trackedLeg was the first leg; gap is determined by gap of start line passing time points
|
||||
Iterable<MarkPassing> markPassingsForLegStart = trackedLeg.getTrackedLeg().getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
|
||||
trackedLeg.getTrackedLeg().getTrackedRace().lockForRead(markPassingsForLegStart);
|
||||
try {
|
||||
final Iterator<MarkPassing> markPassingsIter = markPassingsForLegStart.iterator();
|
||||
if (markPassingsIter.hasNext()) {
|
||||
TimePoint firstStart = markPassingsIter.next().getTimePoint();
|
||||
final MarkPassing markPassingForFrom = trackedLeg.getTrackedLeg().getTrackedRace()
|
||||
.getMarkPassing(trackedLeg.getCompetitor(), trackedLeg.getLeg().getFrom());
|
||||
if (markPassingForFrom != null) {
|
||||
TimePoint competitorStart = markPassingForFrom.getTimePoint();
|
||||
result = (double) (competitorStart.asMillis() - firstStart.asMillis()) / 1000.;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
} finally {
|
||||
trackedLeg.getTrackedLeg().getTrackedRace().unlockAfterRead(markPassingsForLegStart);
|
||||
}
|
||||
} else {
|
||||
TrackedLeg previousTrackedLeg = trackedLeg.getTrackedLeg().getTrackedRace().getTrackedLeg(course.getLegs().get(indexOfStartWaypoint-1));
|
||||
TrackedLegOfCompetitor previousTrackedLegOfCompetitor = previousTrackedLeg.getTrackedLeg(trackedLeg.getCompetitor());
|
||||
result = previousTrackedLegOfCompetitor.getGapToLeaderInSeconds(previousTrackedLegOfCompetitor.getFinishTime());
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
}
|
||||
|
||||
private LeaderboardDTO getLiveLeaderboard(Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException {
|
||||
LiveLeaderboardUpdater liveLeaderboardUpdater = getLiveLeaderboardUpdater(trackedRegattaRegistry,
|
||||
baseDomainFactory);
|
||||
return liveLeaderboardUpdater.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails);
|
||||
}
|
||||
|
||||
private LiveLeaderboardUpdater getLiveLeaderboardUpdater(TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
DomainFactory baseDomainFactory) {
|
||||
LiveLeaderboardUpdater result = this.liveLeaderboardUpdater;
|
||||
if (result == null) {
|
||||
synchronized (this) {
|
||||
result = this.liveLeaderboardUpdater;
|
||||
if (result == null) {
|
||||
this.liveLeaderboardUpdater = new LiveLeaderboardUpdater(this, trackedRegattaRegistry, baseDomainFactory);
|
||||
result = this.liveLeaderboardUpdater;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LeaderboardDTOCache getLeaderboardDTOCache() {
|
||||
LeaderboardDTOCache result = this.leaderboardDTOCache;
|
||||
if (result == null) {
|
||||
synchronized (this) {
|
||||
result = this.leaderboardDTOCache;
|
||||
if (result == null) {
|
||||
// The leaderboard cache is invalidated upon all competitor and mark position changes; some analyzes
|
||||
// are pretty expensive, such as the maneuver re-calculation. Waiting for the latest analysis after only a
|
||||
// single fix was updated is too expensive if users use the replay feature while a race is still running.
|
||||
// Therefore, using waitForLatestAnalyses==false seems appropriate here.
|
||||
this.leaderboardDTOCache = new LeaderboardDTOCache(/* waitForLatestAnalyses */false, this);
|
||||
result = this.leaderboardDTOCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderboardDTO getLeaderboardDTO(TimePoint timePoint, Collection<String> namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry, DomainFactory baseDomainFactory) throws NoWindException, InterruptedException, ExecutionException {
|
||||
LeaderboardDTO result = null;
|
||||
if (timePoint == null) {
|
||||
// date==null means live mode; however, if we're after the end of all races and after all score
|
||||
// corrections, don't use the live leaderboard updater which would keep re-calculating over and over again, but map
|
||||
// this to a usual non-live call which uses the regular LeaderboardDTOCache which is invalidated properly
|
||||
// when the tracked race associations or score corrections or tracked race contents changes:
|
||||
final TimePoint nowMinusDelay = this.getNowMinusDelay();
|
||||
final TimePoint timePointOfLatestModification = this.getTimePointOfLatestModification();
|
||||
if (timePointOfLatestModification != null && !nowMinusDelay.before(timePointOfLatestModification)) {
|
||||
// if there hasn't been any modification to the leaderboard since nowMinusDelay, use non-live mode
|
||||
// and pull the result from the regular leaderboard cache:
|
||||
timePoint = timePointOfLatestModification;
|
||||
} else {
|
||||
// don't use the regular leaderboard cache; the race still seems to be on; use the live leaderboard updater instead:
|
||||
timePoint = null;
|
||||
result = this.getLiveLeaderboard(namesOfRaceColumnsForWhichToLoadLegDetails, trackedRegattaRegistry, baseDomainFactory);
|
||||
}
|
||||
}
|
||||
if (timePoint != null) {
|
||||
// in replay we'd like up-to-date results; they are still cached
|
||||
// which is OK because the cache is invalidated whenever any of the tracked races attached to the
|
||||
// leaderboard changes.
|
||||
result = getLeaderboardDTOCache().getLeaderboardByName(timePoint, namesOfRaceColumnsForWhichToLoadLegDetails,
|
||||
baseDomainFactory, trackedRegattaRegistry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -41,4 +41,7 @@ public interface TrackedRegattaRegistry {
|
||||
* @param raceID the ID as obtained from {@link RaceDefinition#getId()}
|
||||
*/
|
||||
Regatta getRememberedRegattaForRace(Serializable raceID);
|
||||
|
||||
boolean isRaceBeingTracked(RaceDefinition r);
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
bin/
|
||||
@@ -169,6 +169,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
|
||||
version="0.0.0"
|
||||
unpack="false"/>
|
||||
|
||||
<plugin
|
||||
id="com.sap.sailing.simulator"
|
||||
download-size="0"
|
||||
install-size="0"
|
||||
version="0.0.0"
|
||||
unpack="false"/>
|
||||
|
||||
<plugin
|
||||
id="com.google.gwt.osgi"
|
||||
download-size="0"
|
||||
|
||||
+3
-3
@@ -11,13 +11,13 @@ import com.google.gwt.user.cellview.client.Column;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.ExpandableSortableColumn;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.TracTracRaceRecordDTO;
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.test;
|
||||
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
@@ -8,9 +11,6 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.SortableColumn;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.SortedCellTable;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardRowDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
|
||||
public class LeaderboardPanelMock extends LeaderboardPanel {
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.sap.sailing.gwt.ui.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.gwt.ui.simulator.util.LineSegment;
|
||||
import com.sap.sailing.gwt.ui.simulator.util.LineSegment.Point;
|
||||
|
||||
public class LineSegmentTest {
|
||||
|
||||
@Test
|
||||
public void test1() {
|
||||
LineSegment s1 = new LineSegment(0,0,10,10);
|
||||
LineSegment s2 = new LineSegment(10,0,0,10);
|
||||
|
||||
Point intersectionPoint = s1.intersect(s2);
|
||||
if (intersectionPoint != null) {
|
||||
System.out.println("The lines interect at " + intersectionPoint);
|
||||
}
|
||||
assertEquals("Lines do intersect", 5.0, intersectionPoint.getX(),1e-6);
|
||||
assertEquals("Lines do intersect", 5.0, intersectionPoint.getY(),1e-6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() {
|
||||
LineSegment s1 = new LineSegment(0,0,0,10);
|
||||
LineSegment s2 = new LineSegment(10,0,10,10);
|
||||
|
||||
Point intersectionPoint = s1.intersect(s2);
|
||||
|
||||
assertEquals("Lines do not intersect", null, intersectionPoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test3() {
|
||||
|
||||
LineSegment s1 = new LineSegment(53.987292187570304, 10.944955638570837,53.991202136256135, 10.951666848382969);
|
||||
|
||||
LineSegment b1 = new LineSegment(53.977189863832564, 10.886260271572164,53.96560719314814, 10.925463437534281);
|
||||
LineSegment b2 = new LineSegment(53.96560719314814, 10.925463437534281,53.991220287799436, 10.947350263095805);
|
||||
LineSegment b3 = new LineSegment(53.991220287799436, 10.947350263095805,54.00280295848386, 10.90814709713369);
|
||||
LineSegment b4 = new LineSegment(54.00280295848386, 10.90814709713369,53.977189863832564, 10.886260271572164);
|
||||
|
||||
s1.intersect(b1);
|
||||
s1.intersect(b2);
|
||||
s1.intersect(b3);
|
||||
s1.intersect(b4);
|
||||
Point intersectionPoint = s1.intersect(b1);
|
||||
assertEquals("Lines s1 and b1 do not intersect", null, intersectionPoint);
|
||||
intersectionPoint = s1.intersect(b2);
|
||||
assertEquals("Lines s and b2 do not intersect", null, intersectionPoint);
|
||||
intersectionPoint = s1.intersect(b3);
|
||||
assertEquals("Lines s3 and b3 do not intersect", null, intersectionPoint);
|
||||
intersectionPoint = s1.intersect(b4);
|
||||
assertEquals("Lines s4 and b4 do not intersect", null, intersectionPoint);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -14,10 +14,10 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.server.SailingServiceImpl;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
|
||||
public class TestColumnSwapping {
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.sap.sailing.gwt.ui.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import com.sap.sailing.gwt.ui.simulator.util.*;
|
||||
|
||||
public class WindGridColorPaletteTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
int min = 10;
|
||||
int max = 100;
|
||||
WindGridColorPalette cp = new WindGridColorPalette(min,max);
|
||||
|
||||
//assertEquals("Lightest color", "#FFFFFF", cp.getColor(min));
|
||||
//assertEquals("Darkest color", "#0000FF", cp.getColor(max));
|
||||
|
||||
//assertEquals("In between color", "#7F7FFF", cp.getColor((max+min)/2));
|
||||
|
||||
assertEquals("Lightest color", "#FFFFFF", cp.getColor(min));
|
||||
assertEquals("Darkest color", "#4B7FBB", cp.getColor(max));
|
||||
|
||||
assertEquals("In between color", "#A5BFDD", cp.getColor((max+min)/2));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +1,43 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Sailing App GWT UI
|
||||
Bundle-SymbolicName: com.sap.sailing.gwt.ui
|
||||
Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
|
||||
com.sap.sailing.server.gateway,
|
||||
com.sap.sailing.server,
|
||||
com.sap.sailing.domain.tractracadapter,
|
||||
com.sap.sailing.mongodb,
|
||||
com.mongodb.driver;bundle-version="2.6.2",
|
||||
com.sap.sailing.domain.swisstimingadapter,
|
||||
com.sap.sailing.domain.persistence,
|
||||
com.sap.sailing.domain.swisstimingadapter.persistence,
|
||||
com.sap.sailing.domain.tractracadapter.persistence,
|
||||
com.google.gwt.osgi;bundle-version="2.4.0",
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.resultimport,
|
||||
org.moxieapps.gwt.highcharts;bundle-version="1.5.0",
|
||||
com.sap.sailing.geocoding,
|
||||
com.sap.sailing.server.replication,
|
||||
com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.freg.resultimport,
|
||||
com.sap.sailing.domain.swisstimingreplayadapter
|
||||
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: com.sap.sailing.gwt.ui.client.shared.media
|
||||
Import-Package: javax.jms;version="1.1.0",
|
||||
javax.servlet;version="2.5.0",
|
||||
javax.servlet.http;version="2.5.0",
|
||||
org.json.simple,
|
||||
org.json.simple.parser,
|
||||
org.osgi.framework,
|
||||
org.osgi.util.tracker
|
||||
Web-ContextPath: /gwt
|
||||
Bundle-ClassPath: WEB-INF/classes/,
|
||||
WEB-INF/lib/gwt-maps.jar,
|
||||
WEB-INF/lib/gwt-servlet.jar
|
||||
TrackLifecycle: true
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Sailing App GWT UI
|
||||
Bundle-SymbolicName: com.sap.sailing.gwt.ui
|
||||
Bundle-Version: 1.0.0.qualifier
|
||||
Bundle-Vendor: SAP
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
|
||||
Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.domain.shared.android;bundle-version="1.0.0",
|
||||
com.sap.sailing.server.gateway,
|
||||
com.sap.sailing.server,
|
||||
com.sap.sailing.domain.tractracadapter,
|
||||
com.sap.sailing.mongodb,
|
||||
com.mongodb.driver;bundle-version="2.6.2",
|
||||
com.sap.sailing.domain.swisstimingadapter,
|
||||
com.sap.sailing.domain.persistence,
|
||||
com.sap.sailing.domain.swisstimingadapter.persistence,
|
||||
com.sap.sailing.domain.tractracadapter.persistence,
|
||||
com.google.gwt.osgi;bundle-version="2.4.0",
|
||||
com.sap.sailing.domain.common,
|
||||
com.sap.sailing.resultimport,
|
||||
org.moxieapps.gwt.highcharts;bundle-version="1.5.0",
|
||||
com.sap.sailing.geocoding,
|
||||
com.sap.sailing.server.replication,
|
||||
com.googlecode.java-diff-utils;bundle-version="1.3.0",
|
||||
com.sap.sailing.freg.resultimport,
|
||||
com.sap.sailing.domain.swisstimingreplayadapter,
|
||||
com.sap.sailing.simulator;bundle-version="1.0.0"
|
||||
Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: com.sap.sailing.gwt.ui.client.shared.media
|
||||
Import-Package: javax.jms;version="1.1.0",
|
||||
javax.servlet;version="2.5.0",
|
||||
javax.servlet.http;version="2.5.0",
|
||||
org.json.simple,
|
||||
org.json.simple.parser,
|
||||
org.osgi.framework,
|
||||
org.osgi.util.tracker
|
||||
Web-ContextPath: /gwt
|
||||
Bundle-ClassPath: WEB-INF/classes/,
|
||||
WEB-INF/lib/gwt-maps.jar,
|
||||
WEB-INF/lib/gwt-servlet.jar
|
||||
TrackLifecycle: true
|
||||
Regular → Executable
+24
@@ -124,6 +124,12 @@ img.openColumn {
|
||||
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.raceBoardControls {
|
||||
float: right;
|
||||
margin: 9px 0 0 12px;
|
||||
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-navigationitem {
|
||||
color: #dddcdc;
|
||||
text-transform: uppercase;
|
||||
@@ -190,6 +196,24 @@ img.openColumn {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-filterButton {
|
||||
background: url(images/filter_white.png), url(images/btn-cta-orangeleft.png);
|
||||
background-position: center, center 0;
|
||||
background-repeat: no-repeat, repeat-x;
|
||||
width: 27px;
|
||||
height: 23px;
|
||||
margin: 0 12px 0 1px;
|
||||
-webkit-border-radius: 0px 3px 3px 0px;
|
||||
border-radius: 0px 3px 3px 0px;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-filterButton:hover {
|
||||
background-image: url(images/filter_white.png), url(images/btn-cta-orangeleft.png);
|
||||
background-position: center, center -30px;
|
||||
background-repeat: no-repeat, repeat-x;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-innerElement {
|
||||
background: url("images/btn-cta-orangeleft.png") repeat-x scroll center 0 transparent;
|
||||
border-bottom-left-radius: 3px;
|
||||
|
||||
@@ -1,53 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<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"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.gwt.ui.RegattaOverview"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.gwt.ui"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" javaProject="com.sap.sailing.gwt.ui" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.googlecode.java-diff-utils/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.json.simple/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.tractracadapter/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.tractrac.clientmodule/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.expeditionconnector/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.declination/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.declination/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.udpconnector/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.mongodb/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.swisstimingadapter/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.swisstimingadapter.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.operationaltransformation/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.tractracadapter.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.geocoding/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server.replication/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.gwt.ui"/> </runtimeClasspathEntry> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/>
|
||||
</launchConfiguration>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<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"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.gwt.ui.RegattaOverview"/>
|
||||
<listEntry value="com.sap.sailing.gwt.ui.Simulator"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/AdminConsole.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.gwt.ui"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" javaProject="com.sap.sailing.gwt.ui" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.googlecode.java-diff-utils/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.json.simple/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.tractracadapter/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.tractrac.clientmodule/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.expeditionconnector/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.declination/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.declination/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.udpconnector/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.mongodb/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.swisstimingadapter/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.swisstimingadapter.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.operationaltransformation/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.tractracadapter.persistence/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.geocoding/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server.replication/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.gwt.ui"/> </runtimeClasspathEntry> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9997 -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/UserManagement.html com.sap.sailing.gwt.ui.UserManagement -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/TvView.html com.sap.sailing.gwt.ui.TvView -startupUrl /gwt/PolarSheets.html com.sap.sailing.gwt.ui.PolarSheets -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.ui.RegattaOverview -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.RegattaOverview"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -XX:MaxPermSize=512m"/>
|
||||
</launchConfiguration>
|
||||
@@ -0,0 +1,385 @@
|
||||
@import url(fontface.css);
|
||||
//@import url(CommonControls.css);
|
||||
|
||||
/** Add css rules here for your application. */
|
||||
/*
|
||||
body #calendar:after
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
*/
|
||||
.clear {
|
||||
clear: both;
|
||||
font-size: 1px;
|
||||
height: 0px;
|
||||
line-height: 0px;
|
||||
margin: -1px 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cover {
|
||||
left: -1000px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: -1000px;
|
||||
}
|
||||
|
||||
.titleLabelWrapper {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.titleLabel {
|
||||
text-align: center;
|
||||
font-family: 'UbuntuMedium', Arial, Verdana, sans-serif;
|
||||
font-size: 18px;
|
||||
color: #fff;
|
||||
margin: 23px 0 0 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #ebebeb;
|
||||
font-family: 'UbuntuRegular', Arial, Verdana, sans-serif;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
padding: 80px 0 160px 0;
|
||||
margin: 0;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
table td,select,button {
|
||||
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.contentOuterPanel {
|
||||
background: #dddcdc;
|
||||
margin: 0 12px 0 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
/** Example rules used by the template application (remove for your app) */
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
|
||||
color: #777777;
|
||||
margin: 40px 0px 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
/*.RaceBoardHeaderPanel {
|
||||
margin: 0 12px 14px 12px;
|
||||
}*/
|
||||
.sapLogo {
|
||||
float: left;
|
||||
margin: 13px 0 0 12px;
|
||||
}
|
||||
|
||||
.pioLogo {
|
||||
float: right;
|
||||
margin: 11px 20px 0 12px;
|
||||
}
|
||||
|
||||
.sailingAnalyticsLabelPanel {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
.sailingAnalyticsLabel {
|
||||
float: left;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin: 15px 0 0 0;
|
||||
}
|
||||
|
||||
.timePanelSettings {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*.leaderboardLabel {
|
||||
border-bottom: 2px solid #007dc0;
|
||||
padding: 0 0 6px 0;
|
||||
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
|
||||
}*/
|
||||
.refreshAndSettings {
|
||||
background: #f2f2f2;
|
||||
border-bottom: 2px solid #fff;
|
||||
border-top: 2px solid #fff;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.refreshAndSettings table td {
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.refreshAndSettings table table td {
|
||||
padding: 0 0 0 0;
|
||||
}
|
||||
|
||||
.race-name {
|
||||
line-height: 1.0;
|
||||
}
|
||||
|
||||
input.magnifier {
|
||||
display: block;
|
||||
margin: 3px auto 0 auto !important;
|
||||
}
|
||||
|
||||
.breadcrumbPanel {
|
||||
height: 30px;
|
||||
margin: 0 12px 13px 12px;
|
||||
}
|
||||
|
||||
.mainPanel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*.raceBoardNavigation {
|
||||
float: right;
|
||||
margin: 26px 0 0 0;
|
||||
font-family: 'UbuntuLight', Arial, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-navigationitem {
|
||||
color: #dddcdc;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin: 0 12px 0 0;
|
||||
padding: 0 0 5px 0;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-navigationitem:hover {
|
||||
border-bottom: 4px solid #f0ab00;
|
||||
color: #dddcdc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.raceBoardNavigation-navigationitem:active {
|
||||
border-bottom: 4px solid #f0ab00;
|
||||
color: #dddcdc;
|
||||
text-decoration: none;
|
||||
}*/
|
||||
.abstractChartPanel-importantMessageOfChart {
|
||||
font-family: 'UbuntuBold', Arial, Verdana, sans-serif;
|
||||
font-size: 20px;
|
||||
height: 70px;
|
||||
margin: 0 auto;
|
||||
padding: 49px 0 0 80px;
|
||||
width: 300px;
|
||||
color: #7c7c7c;
|
||||
background: url(images/important-message.png) left center no-repeat;
|
||||
}
|
||||
|
||||
/*.breadcrumbPanel-NextArrow {
|
||||
display: none;
|
||||
}*/
|
||||
.replayPanel {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.innerHeadline {
|
||||
font-family: UbuntuMedium, Arial, Verdana, sans-serif;
|
||||
font-size: 16px;
|
||||
color: #7c7c7c;
|
||||
margin: 15px 0
|
||||
}
|
||||
|
||||
.sectorHeadline {
|
||||
font-family: UbuntuLight, Arial, Verdana, sans-serif;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.controlPanelInnerWrapper {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.windPanel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choosePattern {
|
||||
width: 100%;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.choosePattern td:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.optionsPanel {
|
||||
background: url(images/simulator/panel_bg.png) 0 0 repeat-x;
|
||||
border-bottom: 4px solid #7c7b7c;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.mapOptions {
|
||||
background: url(images/simulator/panel_bg.png) 0 0 repeat-x;
|
||||
border-bottom: 4px solid #7c7b7c;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.timePanelButton {
|
||||
float: left;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.playPauseButton {
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
background: url(images/timeslider-play-active-icon.png) 0 0 no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.playPauseButton:hover{
|
||||
background: url(images/playbutton-hover.png) 0 0 no-repeat !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.playPauseButtonPause {
|
||||
background: url(images/timeslider-pause-icon.png) 0 0 no-repeat;
|
||||
}
|
||||
|
||||
.playPauseButtonPause:hover {
|
||||
background: url(images/playbuttonPause-hover.png) 0 0 no-repeat !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.backToLivePlayButton {
|
||||
float: left;
|
||||
padding: 3px 10px 4px;
|
||||
}
|
||||
|
||||
|
||||
.sliderWrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 0 0 7px 0;
|
||||
}
|
||||
|
||||
.sliderLabel {
|
||||
width: 200px;
|
||||
padding: 12px 0 14px 12px;
|
||||
}
|
||||
|
||||
/* styles for the masterPanel */
|
||||
#masterPanelLeft .gwt-SliderBar-shell {
|
||||
background: url(images/simulator/slider_bg.png) 0 0 repeat-x;
|
||||
border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
border: 1px solid #ccc;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line {
|
||||
border: 1px solid #919191;
|
||||
background-color: #818181;
|
||||
height: 4px;
|
||||
width: 90%;
|
||||
top: 23px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-disabled {
|
||||
border: 1px solid gray;
|
||||
}
|
||||
|
||||
/*#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-knob {
|
||||
top: 14px;
|
||||
width: 11px;
|
||||
height: 21px;
|
||||
z-index: 1;
|
||||
cursor: pointer;
|
||||
}*/
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick {
|
||||
top: 18px;
|
||||
width: 1px;
|
||||
height: 6pt;
|
||||
background: #919191;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-mark {
|
||||
top: 21pt;
|
||||
width: 6px;
|
||||
height: 6pt;
|
||||
background: #f0ab00;
|
||||
overflow: hidden;
|
||||
border-left: 4px solid #fff;
|
||||
border-right: 4px solid #fff;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-tick-disabled {
|
||||
background: gray;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-ticklabel {
|
||||
font-size: 12px;
|
||||
cursor: default;
|
||||
font-family: 'UbuntuBold',Arial,Verdana,sans-serif;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-markerlabel {
|
||||
top: 30pt;
|
||||
font-size: 12px;
|
||||
color: #bc9434;
|
||||
cursor: default;
|
||||
font-family: 'UbuntuBold',Arial,Verdana,sans-serif;
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell-focused {
|
||||
|
||||
}
|
||||
|
||||
#masterPanelLeft .gwt-SliderBar-shell .gwt-SliderBar-line-sliding {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.boatClassLabel {
|
||||
width: 235px;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.sailingPanel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dockLayoutPanel div[style="position: absolute; overflow: hidden; left: 0px; right: 0px; bottom: 0px; height: 90px;"] {
|
||||
box-shadow: 0 0 8px #424242;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.MapDisplayOptions {
|
||||
margin: 0 15px 0 0;
|
||||
}
|
||||
|
||||
.MapDisplayOptions label,.gwt-RadioButton label {
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
.MapDisplayOptions {
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.timeSliderWrapperPanel{
|
||||
-webkit-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* WebKit */
|
||||
-moz-box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Firefox */
|
||||
box-shadow: inset 0px 5px 10px 0px rgba(0, 0, 0, 0.15); /* Standard */
|
||||
}
|
||||
.polarDiv
|
||||
{background-color: white;width:100%;height:100px;border-radius: 5px; -moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px; border: 1px solid #177980;}
|
||||
@@ -0,0 +1,42 @@
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>SAP Sailing Analytics Race Simulator</title>
|
||||
<meta content='width = device-width, initial-scale = 1.0, user-scalable = yes' name='viewport'>
|
||||
<meta content='yes' name='apple-mobile-web-app-capable'>
|
||||
<meta content='black' name='apple-mobile-web-app-status-bar-style'>
|
||||
<!-- <link href='iphone-splash-screen.png' rel='apple-touch-startup-image'> -->
|
||||
<link href='images/sap-sailing-app-icon.png' rel='apple-touch-icon'>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="images/sap.ico" />
|
||||
<link href="CommonControls.css" rel="stylesheet" type="text/css">
|
||||
<!--<link href='http://fonts.googleapis.com/css?family=Ubuntu:300,400,500,700,300italic,400italic,500italic,700italic' rel='stylesheet' type='text/css'>-->
|
||||
<!-- <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300,300italic' rel='stylesheet' type='text/css'>-->
|
||||
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
|
||||
|
||||
<script type="text/javascript" src="highcharts/js/highcharts2_3.js"></script>
|
||||
<script type="text/javascript" src="highcharts/js/highcharts-more.js"></script>
|
||||
<script type="text/javascript" src="highcharts/js/exporting.js"></script>
|
||||
<!-- <script type="text/javascript" src="highcharts/js/highcharts.js"></script>-->
|
||||
|
||||
<!-- Optionally, add a highcharts theme file -->
|
||||
<script type="text/javascript" src="highcharts/js/themes/grid.js"></script>
|
||||
<script type="text/javascript" language="javascript" src="com.sap.sailing.gwt.ui.Simulator/com.sap.sailing.gwt.ui.Simulator.nocache.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>
|
||||
<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
|
||||
<noscript>
|
||||
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
|
||||
Your web browser must have JavaScript enabled
|
||||
in order for this application to display correctly.
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -33,6 +33,16 @@
|
||||
<url-pattern>/media</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<display-name>Simulator Service</display-name>
|
||||
<servlet-name>SimulatorService</servlet-name>
|
||||
<servlet-class>com.sap.sailing.gwt.ui.server.SimulatorServiceImpl</servlet-class>
|
||||
</servlet>
|
||||
<servlet-mapping>
|
||||
<servlet-name>SimulatorService</servlet-name>
|
||||
<url-pattern>com.sap.sailing.gwt.ui.Simulator/simulator</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet>
|
||||
<display-name>User Management Service</display-name>
|
||||
<servlet-name>UserManagementService</servlet-name>
|
||||
@@ -52,6 +62,7 @@
|
||||
<servlet-mapping>
|
||||
<servlet-name>remoteLoggingServlet</servlet-name>
|
||||
<url-pattern>/com.sap.sailing.gwt.ui.AdminConsole/remote_logging</url-pattern>
|
||||
<url-pattern>/com.sap.sailing.gwt.ui.Simulator/remote_logging</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<security-constraint>
|
||||
|
||||
@@ -28,8 +28,11 @@ bin.includes = META-INF/,\
|
||||
com.sap.sailing.gwt.ui.RaceBoard/,\
|
||||
highcharts/,\
|
||||
Spectator.css,\
|
||||
fontface.css,\
|
||||
fontface.css,\
|
||||
webfonts/,\
|
||||
com.sap.sailing.gwt.ui.Simulator/,\
|
||||
Simulator.html,\
|
||||
Simulator.css,\
|
||||
RegattaOverview.css,\
|
||||
RegattaOverview.html,\
|
||||
com.sap.sailing.gwt.ui.RegattaOverview/,\
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
/**
|
||||
* @license @product.name@ JS v@product.version@ (@product.date@)
|
||||
* Exporting module
|
||||
*
|
||||
* (c) 2010-2011 Torstein Hønsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*/
|
||||
|
||||
// JSLint options:
|
||||
/*global Highcharts, document, window, Math, setTimeout */
|
||||
|
||||
(function () { // encapsulate
|
||||
|
||||
// create shortcuts
|
||||
var HC = Highcharts,
|
||||
Chart = HC.Chart,
|
||||
addEvent = HC.addEvent,
|
||||
removeEvent = HC.removeEvent,
|
||||
createElement = HC.createElement,
|
||||
discardElement = HC.discardElement,
|
||||
css = HC.css,
|
||||
merge = HC.merge,
|
||||
each = HC.each,
|
||||
extend = HC.extend,
|
||||
math = Math,
|
||||
mathMax = math.max,
|
||||
doc = document,
|
||||
win = window,
|
||||
hasTouch = doc.documentElement.ontouchstart !== undefined,
|
||||
M = 'M',
|
||||
L = 'L',
|
||||
DIV = 'div',
|
||||
HIDDEN = 'hidden',
|
||||
NONE = 'none',
|
||||
PREFIX = 'highcharts-',
|
||||
ABSOLUTE = 'absolute',
|
||||
PX = 'px',
|
||||
UNDEFINED,
|
||||
defaultOptions = HC.getOptions();
|
||||
|
||||
// Add language
|
||||
extend(defaultOptions.lang, {
|
||||
downloadPNG: 'Download PNG image',
|
||||
downloadJPEG: 'Download JPEG image',
|
||||
downloadPDF: 'Download PDF document',
|
||||
downloadSVG: 'Download SVG vector image',
|
||||
exportButtonTitle: 'Export to raster or vector image',
|
||||
printButtonTitle: 'Print the chart'
|
||||
});
|
||||
|
||||
// Buttons and menus are collected in a separate config option set called 'navigation'.
|
||||
// This can be extended later to add control buttons like zoom and pan right click menus.
|
||||
defaultOptions.navigation = {
|
||||
menuStyle: {
|
||||
border: '1px solid #A0A0A0',
|
||||
background: '#FFFFFF'
|
||||
},
|
||||
menuItemStyle: {
|
||||
padding: '0 5px',
|
||||
background: NONE,
|
||||
color: '#303030',
|
||||
fontSize: hasTouch ? '14px' : '11px'
|
||||
},
|
||||
menuItemHoverStyle: {
|
||||
background: '#4572A5',
|
||||
color: '#FFFFFF'
|
||||
},
|
||||
|
||||
buttonOptions: {
|
||||
align: 'right',
|
||||
backgroundColor: {
|
||||
linearGradient: [0, 0, 0, 20],
|
||||
stops: [
|
||||
[0.4, '#F7F7F7'],
|
||||
[0.6, '#E3E3E3']
|
||||
]
|
||||
},
|
||||
borderColor: '#B0B0B0',
|
||||
borderRadius: 3,
|
||||
borderWidth: 1,
|
||||
//enabled: true,
|
||||
height: 20,
|
||||
hoverBorderColor: '#909090',
|
||||
hoverSymbolFill: '#81A7CF',
|
||||
hoverSymbolStroke: '#4572A5',
|
||||
symbolFill: '#E0E0E0',
|
||||
//symbolSize: 12,
|
||||
symbolStroke: '#A0A0A0',
|
||||
//symbolStrokeWidth: 1,
|
||||
symbolX: 11.5,
|
||||
symbolY: 10.5,
|
||||
verticalAlign: 'top',
|
||||
width: 24,
|
||||
y: 10
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Add the export related options
|
||||
defaultOptions.exporting = {
|
||||
//enabled: true,
|
||||
//filename: 'chart',
|
||||
type: 'image/png',
|
||||
url: 'http://export.highcharts.com/',
|
||||
width: 800,
|
||||
buttons: {
|
||||
exportButton: {
|
||||
//enabled: true,
|
||||
symbol: 'exportIcon',
|
||||
x: -10,
|
||||
symbolFill: '#A8BF77',
|
||||
hoverSymbolFill: '#768F3E',
|
||||
_id: 'exportButton',
|
||||
_titleKey: 'exportButtonTitle',
|
||||
menuItems: [{
|
||||
textKey: 'downloadPNG',
|
||||
onclick: function () {
|
||||
this.exportChart();
|
||||
}
|
||||
}, {
|
||||
textKey: 'downloadJPEG',
|
||||
onclick: function () {
|
||||
this.exportChart({
|
||||
type: 'image/jpeg'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
textKey: 'downloadPDF',
|
||||
onclick: function () {
|
||||
this.exportChart({
|
||||
type: 'application/pdf'
|
||||
});
|
||||
}
|
||||
}, {
|
||||
textKey: 'downloadSVG',
|
||||
onclick: function () {
|
||||
this.exportChart({
|
||||
type: 'image/svg+xml'
|
||||
});
|
||||
}
|
||||
}
|
||||
// Enable this block to add "View SVG" to the dropdown menu
|
||||
/*
|
||||
,{
|
||||
|
||||
text: 'View SVG',
|
||||
onclick: function () {
|
||||
var svg = this.getSVG()
|
||||
.replace(/</g, '\n<')
|
||||
.replace(/>/g, '>');
|
||||
|
||||
doc.body.innerHTML = '<pre>' + svg + '</pre>';
|
||||
}
|
||||
} // */
|
||||
]
|
||||
|
||||
},
|
||||
printButton: {
|
||||
//enabled: true,
|
||||
symbol: 'printIcon',
|
||||
x: -36,
|
||||
symbolFill: '#B5C9DF',
|
||||
hoverSymbolFill: '#779ABF',
|
||||
_id: 'printButton',
|
||||
_titleKey: 'printButtonTitle',
|
||||
onclick: function () {
|
||||
this.print();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
extend(Chart.prototype, {
|
||||
/**
|
||||
* Return an SVG representation of the chart
|
||||
*
|
||||
* @param additionalOptions {Object} Additional chart options for the generated SVG representation
|
||||
*/
|
||||
getSVG: function (additionalOptions) {
|
||||
var chart = this,
|
||||
chartCopy,
|
||||
sandbox,
|
||||
svg,
|
||||
seriesOptions,
|
||||
options = merge(chart.options, additionalOptions); // copy the options and add extra options
|
||||
|
||||
// IE compatibility hack for generating SVG content that it doesn't really understand
|
||||
if (!doc.createElementNS) {
|
||||
/*jslint unparam: true*//* allow unused parameter ns in function below */
|
||||
doc.createElementNS = function (ns, tagName) {
|
||||
return doc.createElement(tagName);
|
||||
};
|
||||
/*jslint unparam: false*/
|
||||
}
|
||||
|
||||
// create a sandbox where a new chart will be generated
|
||||
sandbox = createElement(DIV, null, {
|
||||
position: ABSOLUTE,
|
||||
top: '-9999em',
|
||||
width: chart.chartWidth + PX,
|
||||
height: chart.chartHeight + PX
|
||||
}, doc.body);
|
||||
|
||||
// override some options
|
||||
extend(options.chart, {
|
||||
renderTo: sandbox,
|
||||
forExport: true
|
||||
});
|
||||
options.exporting.enabled = false; // hide buttons in print
|
||||
options.chart.plotBackgroundImage = null; // the converter doesn't handle images
|
||||
|
||||
// prepare for replicating the chart
|
||||
options.series = [];
|
||||
each(chart.series, function (serie) {
|
||||
seriesOptions = merge(serie.options, {
|
||||
animation: false, // turn off animation
|
||||
showCheckbox: false,
|
||||
visible: serie.visible
|
||||
});
|
||||
|
||||
if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
|
||||
|
||||
// remove image markers
|
||||
if (seriesOptions && seriesOptions.marker && /^url\(/.test(seriesOptions.marker.symbol)) {
|
||||
seriesOptions.marker.symbol = 'circle';
|
||||
}
|
||||
|
||||
options.series.push(seriesOptions);
|
||||
}
|
||||
});
|
||||
|
||||
// generate the chart copy
|
||||
chartCopy = new Highcharts.Chart(options);
|
||||
|
||||
// reflect axis extremes in the export
|
||||
each(['xAxis', 'yAxis'], function (axisType) {
|
||||
each(chart[axisType], function (axis, i) {
|
||||
var axisCopy = chartCopy[axisType][i],
|
||||
extremes = axis.getExtremes(),
|
||||
userMin = extremes.userMin,
|
||||
userMax = extremes.userMax;
|
||||
|
||||
if (userMin !== UNDEFINED || userMax !== UNDEFINED) {
|
||||
axisCopy.setExtremes(userMin, userMax, true, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// get the SVG from the container's innerHTML
|
||||
svg = chartCopy.container.innerHTML;
|
||||
|
||||
// free up memory
|
||||
options = null;
|
||||
chartCopy.destroy();
|
||||
discardElement(sandbox);
|
||||
|
||||
// sanitize
|
||||
svg = svg
|
||||
.replace(/zIndex="[^"]+"/g, '')
|
||||
.replace(/isShadow="[^"]+"/g, '')
|
||||
.replace(/symbolName="[^"]+"/g, '')
|
||||
.replace(/jQuery[0-9]+="[^"]+"/g, '')
|
||||
.replace(/isTracker="[^"]+"/g, '')
|
||||
.replace(/url\([^#]+#/g, 'url(#')
|
||||
.replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
|
||||
.replace(/ href=/g, ' xlink:href=')
|
||||
.replace(/\n/, ' ')
|
||||
.replace(/<\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)
|
||||
/* This fails in IE < 8
|
||||
.replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
|
||||
return s2 +'.'+ s3[0];
|
||||
})*/
|
||||
|
||||
// Replace HTML entities, issue #347
|
||||
.replace(/ /g, '\u00A0') // no-break space
|
||||
.replace(/­/g, '\u00AD') // soft hyphen
|
||||
|
||||
// IE specific
|
||||
.replace(/<IMG /g, '<image ')
|
||||
.replace(/height=([^" ]+)/g, 'height="$1"')
|
||||
.replace(/width=([^" ]+)/g, 'width="$1"')
|
||||
.replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
|
||||
.replace(/id=([^" >]+)/g, 'id="$1"')
|
||||
.replace(/class=([^" ]+)/g, 'class="$1"')
|
||||
.replace(/ transform /g, ' ')
|
||||
.replace(/:(path|rect)/g, '$1')
|
||||
.replace(/style="([^"]+)"/g, function (s) {
|
||||
return s.toLowerCase();
|
||||
});
|
||||
|
||||
// IE9 beta bugs with innerHTML. Test again with final IE9.
|
||||
svg = svg.replace(/(url\(#highcharts-[0-9]+)"/g, '$1')
|
||||
.replace(/"/g, "'");
|
||||
if (svg.match(/ xmlns="/g).length === 2) {
|
||||
svg = svg.replace(/xmlns="[^"]+"/, '');
|
||||
}
|
||||
|
||||
return svg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Submit the SVG representation of the chart to the server
|
||||
* @param {Object} options Exporting options. Possible members are url, type and width.
|
||||
* @param {Object} chartOptions Additional chart options for the SVG representation of the chart
|
||||
*/
|
||||
exportChart: function (options, chartOptions) {
|
||||
var form,
|
||||
chart = this,
|
||||
svg = chart.getSVG(merge(chart.options.exporting.chartOptions, chartOptions)); // docs
|
||||
|
||||
// merge the options
|
||||
options = merge(chart.options.exporting, options);
|
||||
|
||||
// create the form
|
||||
form = createElement('form', {
|
||||
method: 'post',
|
||||
action: options.url,
|
||||
enctype: 'multipart/form-data'
|
||||
}, {
|
||||
display: NONE
|
||||
}, doc.body);
|
||||
|
||||
// add the values
|
||||
each(['filename', 'type', 'width', 'svg'], function (name) {
|
||||
createElement('input', {
|
||||
type: HIDDEN,
|
||||
name: name,
|
||||
value: {
|
||||
filename: options.filename || 'chart',
|
||||
type: options.type,
|
||||
width: options.width,
|
||||
svg: svg
|
||||
}[name]
|
||||
}, null, form);
|
||||
});
|
||||
|
||||
// submit
|
||||
form.submit();
|
||||
|
||||
// clean up
|
||||
discardElement(form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Print the chart
|
||||
*/
|
||||
print: function () {
|
||||
|
||||
var chart = this,
|
||||
container = chart.container,
|
||||
origDisplay = [],
|
||||
origParent = container.parentNode,
|
||||
body = doc.body,
|
||||
childNodes = body.childNodes;
|
||||
|
||||
if (chart.isPrinting) { // block the button while in printing mode
|
||||
return;
|
||||
}
|
||||
|
||||
chart.isPrinting = true;
|
||||
|
||||
// hide all body content
|
||||
each(childNodes, function (node, i) {
|
||||
if (node.nodeType === 1) {
|
||||
origDisplay[i] = node.style.display;
|
||||
node.style.display = NONE;
|
||||
}
|
||||
});
|
||||
|
||||
// pull out the chart
|
||||
body.appendChild(container);
|
||||
|
||||
// print
|
||||
win.print();
|
||||
|
||||
// allow the browser to prepare before reverting
|
||||
setTimeout(function () {
|
||||
|
||||
// put the chart back in
|
||||
origParent.appendChild(container);
|
||||
|
||||
// restore all body content
|
||||
each(childNodes, function (node, i) {
|
||||
if (node.nodeType === 1) {
|
||||
node.style.display = origDisplay[i];
|
||||
}
|
||||
});
|
||||
|
||||
chart.isPrinting = false;
|
||||
|
||||
}, 1000);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Display a popup menu for choosing the export type
|
||||
*
|
||||
* @param {String} name An identifier for the menu
|
||||
* @param {Array} items A collection with text and onclicks for the items
|
||||
* @param {Number} x The x position of the opener button
|
||||
* @param {Number} y The y position of the opener button
|
||||
* @param {Number} width The width of the opener button
|
||||
* @param {Number} height The height of the opener button
|
||||
*/
|
||||
contextMenu: function (name, items, x, y, width, height) {
|
||||
var chart = this,
|
||||
navOptions = chart.options.navigation,
|
||||
menuItemStyle = navOptions.menuItemStyle,
|
||||
chartWidth = chart.chartWidth,
|
||||
chartHeight = chart.chartHeight,
|
||||
cacheName = 'cache-' + name,
|
||||
menu = chart[cacheName],
|
||||
menuPadding = mathMax(width, height), // for mouse leave detection
|
||||
boxShadow = '3px 3px 10px #888',
|
||||
innerMenu,
|
||||
hide,
|
||||
menuStyle;
|
||||
|
||||
// create the menu only the first time
|
||||
if (!menu) {
|
||||
|
||||
// create a HTML element above the SVG
|
||||
chart[cacheName] = menu = createElement(DIV, {
|
||||
className: PREFIX + name
|
||||
}, {
|
||||
position: ABSOLUTE,
|
||||
zIndex: 1000,
|
||||
padding: menuPadding + PX
|
||||
}, chart.container);
|
||||
|
||||
innerMenu = createElement(DIV, null,
|
||||
extend({
|
||||
MozBoxShadow: boxShadow,
|
||||
WebkitBoxShadow: boxShadow,
|
||||
boxShadow: boxShadow
|
||||
}, navOptions.menuStyle), menu);
|
||||
|
||||
// hide on mouse out
|
||||
hide = function () {
|
||||
css(menu, { display: NONE });
|
||||
};
|
||||
|
||||
addEvent(menu, 'mouseleave', hide);
|
||||
|
||||
|
||||
// create the items
|
||||
each(items, function (item) {
|
||||
if (item) {
|
||||
var div = createElement(DIV, {
|
||||
onmouseover: function () {
|
||||
css(this, navOptions.menuItemHoverStyle);
|
||||
},
|
||||
onmouseout: function () {
|
||||
css(this, menuItemStyle);
|
||||
},
|
||||
innerHTML: item.text || chart.options.lang[item.textKey]
|
||||
}, extend({
|
||||
cursor: 'pointer'
|
||||
}, menuItemStyle), innerMenu);
|
||||
|
||||
div[hasTouch ? 'ontouchstart' : 'onclick'] = function () {
|
||||
hide();
|
||||
item.onclick.apply(chart, arguments);
|
||||
};
|
||||
|
||||
// Keep references to menu divs to be able to destroy them
|
||||
chart.exportDivElements.push(div);
|
||||
}
|
||||
});
|
||||
|
||||
// Keep references to menu and innerMenu div to be able to destroy them
|
||||
chart.exportDivElements.push(innerMenu, menu);
|
||||
|
||||
chart.exportMenuWidth = menu.offsetWidth;
|
||||
chart.exportMenuHeight = menu.offsetHeight;
|
||||
}
|
||||
|
||||
menuStyle = { display: 'block' };
|
||||
|
||||
// if outside right, right align it
|
||||
if (x + chart.exportMenuWidth > chartWidth) {
|
||||
menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
|
||||
} else {
|
||||
menuStyle.left = (x - menuPadding) + PX;
|
||||
}
|
||||
// if outside bottom, bottom align it
|
||||
if (y + height + chart.exportMenuHeight > chartHeight) {
|
||||
menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
|
||||
} else {
|
||||
menuStyle.top = (y + height - menuPadding) + PX;
|
||||
}
|
||||
|
||||
css(menu, menuStyle);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the export button to the chart
|
||||
*/
|
||||
addButton: function (options) {
|
||||
var chart = this,
|
||||
renderer = chart.renderer,
|
||||
btnOptions = merge(chart.options.navigation.buttonOptions, options),
|
||||
onclick = btnOptions.onclick,
|
||||
menuItems = btnOptions.menuItems,
|
||||
buttonWidth = btnOptions.width,
|
||||
buttonHeight = btnOptions.height,
|
||||
box,
|
||||
symbol,
|
||||
button,
|
||||
borderWidth = btnOptions.borderWidth,
|
||||
boxAttr = {
|
||||
stroke: btnOptions.borderColor
|
||||
|
||||
},
|
||||
symbolAttr = {
|
||||
stroke: btnOptions.symbolStroke,
|
||||
fill: btnOptions.symbolFill
|
||||
},
|
||||
symbolSize = btnOptions.symbolSize || 12;
|
||||
|
||||
// Keeps references to the button elements
|
||||
if (!chart.exportDivElements) {
|
||||
chart.exportDivElements = [];
|
||||
chart.exportSVGElements = [];
|
||||
}
|
||||
|
||||
if (btnOptions.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// element to capture the click
|
||||
function revert() {
|
||||
symbol.attr(symbolAttr);
|
||||
box.attr(boxAttr);
|
||||
}
|
||||
|
||||
// the box border
|
||||
box = renderer.rect(
|
||||
0,
|
||||
0,
|
||||
buttonWidth,
|
||||
buttonHeight,
|
||||
btnOptions.borderRadius,
|
||||
borderWidth
|
||||
)
|
||||
//.translate(buttonLeft, buttonTop) // to allow gradients
|
||||
.align(btnOptions, true)
|
||||
.attr(extend({
|
||||
fill: btnOptions.backgroundColor,
|
||||
'stroke-width': borderWidth,
|
||||
zIndex: 19
|
||||
}, boxAttr)).add();
|
||||
|
||||
// the invisible element to track the clicks
|
||||
button = renderer.rect(
|
||||
0,
|
||||
0,
|
||||
buttonWidth,
|
||||
buttonHeight,
|
||||
0
|
||||
)
|
||||
.align(btnOptions)
|
||||
.attr({
|
||||
id: btnOptions._id,
|
||||
fill: 'rgba(255, 255, 255, 0.001)',
|
||||
title: chart.options.lang[btnOptions._titleKey],
|
||||
zIndex: 21
|
||||
}).css({
|
||||
cursor: 'pointer'
|
||||
})
|
||||
.on('mouseover', function () {
|
||||
symbol.attr({
|
||||
stroke: btnOptions.hoverSymbolStroke,
|
||||
fill: btnOptions.hoverSymbolFill
|
||||
});
|
||||
box.attr({
|
||||
stroke: btnOptions.hoverBorderColor
|
||||
});
|
||||
})
|
||||
.on('mouseout', revert)
|
||||
.on('click', revert)
|
||||
.add();
|
||||
|
||||
// add the click event
|
||||
if (menuItems) {
|
||||
onclick = function () {
|
||||
revert();
|
||||
var bBox = button.getBBox();
|
||||
chart.contextMenu('export-menu', menuItems, bBox.x, bBox.y, buttonWidth, buttonHeight);
|
||||
};
|
||||
}
|
||||
/*addEvent(button.element, 'click', function() {
|
||||
onclick.apply(chart, arguments);
|
||||
});*/
|
||||
button.on('click', function () {
|
||||
onclick.apply(chart, arguments);
|
||||
});
|
||||
|
||||
// the icon
|
||||
symbol = renderer.symbol(
|
||||
btnOptions.symbol,
|
||||
btnOptions.symbolX - (symbolSize / 2),
|
||||
btnOptions.symbolY - (symbolSize / 2),
|
||||
symbolSize,
|
||||
symbolSize
|
||||
)
|
||||
.align(btnOptions, true)
|
||||
.attr(extend(symbolAttr, {
|
||||
'stroke-width': btnOptions.symbolStrokeWidth || 1,
|
||||
zIndex: 20
|
||||
})).add();
|
||||
|
||||
// Keep references to the renderer element so to be able to destroy them later.
|
||||
chart.exportSVGElements.push(box, button, symbol);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the buttons.
|
||||
*/
|
||||
destroyExport: function () {
|
||||
var i,
|
||||
chart = this,
|
||||
elem;
|
||||
|
||||
// Destroy the extra buttons added
|
||||
for (i = 0; i < chart.exportSVGElements.length; i++) {
|
||||
elem = chart.exportSVGElements[i];
|
||||
// Destroy and null the svg/vml elements
|
||||
elem.onclick = elem.ontouchstart = null;
|
||||
chart.exportSVGElements[i] = elem.destroy();
|
||||
}
|
||||
|
||||
// Destroy the divs for the menu
|
||||
for (i = 0; i < chart.exportDivElements.length; i++) {
|
||||
elem = chart.exportDivElements[i];
|
||||
|
||||
// Remove the event handler
|
||||
removeEvent(elem, 'mouseleave');
|
||||
|
||||
// Remove inline events
|
||||
chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
|
||||
|
||||
// Destroy the div by moving to garbage bin
|
||||
discardElement(elem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Crisp for 1px stroke width, which is default. In the future, consider a smarter,
|
||||
* global function.
|
||||
*/
|
||||
function crisp(arr) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (typeof arr[i] === 'number') {
|
||||
arr[i] = Math.round(arr[i]) - 0.5;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// Create the export icon
|
||||
HC.Renderer.prototype.symbols.exportIcon = function (x, y, width, height) {
|
||||
return crisp([
|
||||
M, // the disk
|
||||
x, y + width,
|
||||
L,
|
||||
x + width, y + height,
|
||||
x + width, y + height * 0.8,
|
||||
x, y + height * 0.8,
|
||||
'Z',
|
||||
M, // the arrow
|
||||
x + width * 0.5, y + height * 0.8,
|
||||
L,
|
||||
x + width * 0.8, y + height * 0.4,
|
||||
x + width * 0.4, y + height * 0.4,
|
||||
x + width * 0.4, y,
|
||||
x + width * 0.6, y,
|
||||
x + width * 0.6, y + height * 0.4,
|
||||
x + width * 0.2, y + height * 0.4,
|
||||
'Z'
|
||||
]);
|
||||
};
|
||||
// Create the print icon
|
||||
HC.Renderer.prototype.symbols.printIcon = function (x, y, width, height) {
|
||||
return crisp([
|
||||
M, // the printer
|
||||
x, y + height * 0.7,
|
||||
L,
|
||||
x + width, y + height * 0.7,
|
||||
x + width, y + height * 0.4,
|
||||
x, y + height * 0.4,
|
||||
'Z',
|
||||
M, // the upper sheet
|
||||
x + width * 0.2, y + height * 0.4,
|
||||
L,
|
||||
x + width * 0.2, y,
|
||||
x + width * 0.8, y,
|
||||
x + width * 0.8, y + height * 0.4,
|
||||
'Z',
|
||||
M, // the lower sheet
|
||||
x + width * 0.2, y + height * 0.7,
|
||||
L,
|
||||
x, y + height,
|
||||
x + width, y + height,
|
||||
x + width * 0.8, y + height * 0.7,
|
||||
'Z'
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
// Add the buttons on chart load
|
||||
Chart.prototype.callbacks.push(function (chart) {
|
||||
var n,
|
||||
exportingOptions = chart.options.exporting,
|
||||
buttons = exportingOptions.buttons;
|
||||
|
||||
if (exportingOptions.enabled !== false) {
|
||||
|
||||
for (n in buttons) {
|
||||
chart.addButton(buttons[n]);
|
||||
}
|
||||
|
||||
// Destroy the export elements at chart destroy
|
||||
addEvent(chart, 'destroy', chart.destroyExport);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
}());
|
||||
window.console && console.log('--- Running modules/exporting.src.js from GitHub ---');
|
||||
File diff suppressed because it is too large.
Load diff
Binary file not shown.
|
After Width: | Height: | Size: 255 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 743 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1015 B |
@@ -53,6 +53,7 @@
|
||||
<module>com.sap.sailing.gwt.ui.UserManagement</module>
|
||||
<module>com.sap.sailing.gwt.ui.VideoPopup</module>
|
||||
<module>com.sap.sailing.gwt.ui.YoutubePopup</module>
|
||||
<module>com.sap.sailing.gwt.ui.Simulator</module>
|
||||
<module>com.sap.sailing.gwt.ui.RegattaOverview</module>
|
||||
<module>com.sap.sailing.gwt.ui.PolarSheets</module>
|
||||
</modules>
|
||||
|
||||
+1
-1
@@ -6,8 +6,8 @@ import java.util.List;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.DetailType;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
|
||||
|
||||
public class GetCompetitorsRaceDataAction extends DefaultAsyncAction<CompetitorsRaceDataDTO> {
|
||||
|
||||
+1
-1
@@ -4,8 +4,8 @@ import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
|
||||
public class GetLeaderboardByNameAction extends DefaultAsyncAction<LeaderboardDTO> {
|
||||
private final SailingServiceAsync sailingService;
|
||||
|
||||
+1
-1
@@ -6,9 +6,9 @@ import java.util.Map;
|
||||
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
|
||||
|
||||
public class GetRaceMapDataAction extends DefaultAsyncAction<RaceMapDataDTO> {
|
||||
|
||||
+1
-1
@@ -5,13 +5,13 @@ import java.util.List;
|
||||
import com.google.gwt.user.client.ui.CaptionPanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionModel;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
|
||||
public abstract class AbstractRaceManagementPanel extends AbstractEventManagementPanel implements RaceSelectionChangeListener {
|
||||
|
||||
+1
-1
@@ -41,6 +41,7 @@ import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.TrackedRaceStatusEnum;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
|
||||
@@ -50,7 +51,6 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.shared.components.Component;
|
||||
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
|
||||
public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component<TrackedRacesSettings>,
|
||||
|
||||
+2
-2
@@ -8,11 +8,11 @@ import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.google.gwt.user.datepicker.client.DateBox;
|
||||
import com.sap.sailing.domain.common.dto.PositionDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.PositionDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindDTO;
|
||||
|
||||
public class AddWindFixDialog extends DataEntryDialog<WindDTO> {
|
||||
|
||||
+3
-3
@@ -47,6 +47,9 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.DisablableCheckboxCell.IsEnabled;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.RaceColumnInLeaderboardDialog.RaceColumnDescriptor;
|
||||
@@ -65,10 +68,7 @@ import com.sap.sailing.gwt.ui.client.URLEncoder;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
|
||||
import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
|
||||
import com.sap.sailing.gwt.ui.shared.AbstractLeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
|
||||
|
||||
+1
-1
@@ -35,6 +35,7 @@ import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.google.gwt.view.client.MultiSelectionModel;
|
||||
import com.google.gwt.view.client.SelectionChangeEvent;
|
||||
import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardConfigPanel.AnchorCell;
|
||||
import com.sap.sailing.gwt.ui.adminconsole.LeaderboardGroupDialog.LeaderboardGroupDescriptor;
|
||||
import com.sap.sailing.gwt.ui.client.AbstractRegattaPanel;
|
||||
@@ -47,7 +48,6 @@ import com.sap.sailing.gwt.ui.client.URLEncoder;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
|
||||
import com.sap.sailing.gwt.ui.raceboard.RaceBoardViewConfiguration;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
|
||||
|
||||
|
||||
+1
-1
@@ -8,9 +8,9 @@ import com.google.gwt.user.client.ui.Label;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
|
||||
/**
|
||||
* Fills a {@link RaceColumnDTO} object by setting its {@link RaceColumnDTO#setMedalRace(boolean) medal race} property,
|
||||
|
||||
+1
-1
@@ -16,10 +16,10 @@ import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
|
||||
|
||||
|
||||
+1
-1
@@ -16,9 +16,9 @@ import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
|
||||
public class RaceColumnsInLeaderboardDialog extends DataEntryDialog<List<RaceColumnDTO>> {
|
||||
private final ListBox addRacesListBox;
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@ import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.dom.client.Style.FontWeight;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.i18n.client.NumberFormat;
|
||||
import com.google.gwt.safehtml.shared.SafeHtml;
|
||||
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
|
||||
import com.google.gwt.user.cellview.client.CellTable;
|
||||
@@ -416,7 +417,8 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel {
|
||||
public SafeHtml getValue(MarkDTO mark) {
|
||||
SafeHtmlBuilder builder = new SafeHtmlBuilder();
|
||||
if(mark.position != null) {
|
||||
builder.appendEscaped(mark.position.toFormattedString());
|
||||
NumberFormat fmt = NumberFormat.getFormat("#.###");
|
||||
builder.appendEscaped(fmt.format(mark.position.latDeg)+", "+fmt.format(mark.position.lngDeg));
|
||||
}
|
||||
return builder.toSafeHtml();
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,12 +17,12 @@ import com.google.gwt.user.client.ui.ListBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
|
||||
public class RacesListBoxPanel extends FormPanel implements RegattaDisplayer, RaceSelectionChangeListener {
|
||||
|
||||
+3
-3
@@ -21,6 +21,9 @@ import com.google.gwt.view.client.SingleSelectionModel;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaName;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
@@ -28,9 +31,6 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
|
||||
|
||||
|
||||
+1
-1
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.SimplePanel;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
|
||||
@@ -29,7 +30,6 @@ import com.sap.sailing.gwt.ui.client.RegattaSelectionProvider;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
|
||||
|
||||
|
||||
+1
-1
@@ -19,10 +19,10 @@ import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.dto.BoatClassDTO;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter;
|
||||
import com.sap.sailing.gwt.ui.shared.BoatClassDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
|
||||
+1
-1
@@ -19,9 +19,9 @@ import com.google.gwt.user.client.ui.TextBox;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.sap.sailing.domain.common.Color;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
|
||||
|
||||
public class SeriesWithFleetsCreateDialog extends DataEntryDialog<SeriesDTO> {
|
||||
|
||||
+1
-1
@@ -12,6 +12,7 @@ import com.google.gwt.user.client.ui.Button;
|
||||
import com.google.gwt.user.client.ui.HorizontalPanel;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.client.ErrorReporter;
|
||||
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
|
||||
import com.sap.sailing.gwt.ui.client.RegattaDisplayer;
|
||||
@@ -19,7 +20,6 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -34,6 +34,7 @@ import com.google.gwt.view.client.ListDataProvider;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.WindSourceType;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.WindSourceImpl;
|
||||
import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor;
|
||||
import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback;
|
||||
@@ -46,7 +47,6 @@ import com.sap.sailing.gwt.ui.client.RegattaRefresher;
|
||||
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
|
||||
import com.sap.sailing.gwt.ui.client.StringMessages;
|
||||
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
|
||||
|
||||
+2
-4
@@ -62,7 +62,7 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W
|
||||
|
||||
@Override
|
||||
public final void onModuleLoad() {
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
if (DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallBundle bundle = GWT.create(PendingAjaxCallBundle.class);
|
||||
TextResource script = bundle.ajaxSemaphoreJS();
|
||||
JavaScriptInjector.inject(script.getText());
|
||||
@@ -70,10 +70,8 @@ public abstract class AbstractEntryPoint implements EntryPoint, ErrorReporter, W
|
||||
DebugInfo.setDebugIdAttribute(DEBUG_ID_ATTRIBUTE, false);
|
||||
DebugInfo.setDebugIdPrefix(DEBUG_ID_PREFIX);
|
||||
}
|
||||
|
||||
doOnModuleLoad();
|
||||
|
||||
if(DebugInfo.isDebugIdEnabled()) {
|
||||
if (DebugInfo.isDebugIdEnabled()) {
|
||||
PendingAjaxCallMarker.decrementPendingAjaxCalls(MarkedAsyncCallback.CATEGORY_GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
|
||||
|
||||
/**
|
||||
@@ -9,6 +9,7 @@ import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
public interface CompetitorSelectionChangeListener {
|
||||
|
||||
void competitorsListChanged(Iterable<CompetitorDTO> competitors);
|
||||
void filteredCompetitorsListChanged(Iterable<CompetitorDTO> filteredCompetitors);
|
||||
|
||||
void addedToSelection(CompetitorDTO competitor);
|
||||
void removedFromSelection(CompetitorDTO competitor);
|
||||
|
||||
+238
-200
@@ -1,200 +1,238 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
|
||||
public class CompetitorSelectionModel implements CompetitorSelectionProvider {
|
||||
private final Set<CompetitorDTO> allCompetitors;
|
||||
|
||||
private final LinkedHashSet<CompetitorDTO> selectedCompetitors;
|
||||
|
||||
private final Set<CompetitorSelectionChangeListener> listeners;
|
||||
|
||||
private final boolean hasMultiSelection;
|
||||
|
||||
private final ColorMap<CompetitorDTO> competitorsColorMap;
|
||||
|
||||
public CompetitorSelectionModel(boolean hasMultiSelection) {
|
||||
super();
|
||||
this.hasMultiSelection = hasMultiSelection;
|
||||
this.allCompetitors = new HashSet<CompetitorDTO>();
|
||||
this.selectedCompetitors = new LinkedHashSet<CompetitorDTO>();
|
||||
this.listeners = new HashSet<CompetitorSelectionChangeListener>();
|
||||
this.competitorsColorMap = new ColorMap<CompetitorDTO>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a competitor to the {@link #getAllCompetitors() set of all competitors}. If the competitor was not yet
|
||||
* contained, it will be deselected.
|
||||
*/
|
||||
public void add(CompetitorDTO competitor) {
|
||||
add(competitor, true);
|
||||
}
|
||||
|
||||
private void add(CompetitorDTO competitor, boolean notifyListeners) {
|
||||
boolean changed = allCompetitors.add(competitor);
|
||||
if (notifyListeners && changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMultiSelection() {
|
||||
return hasMultiSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all competitors. Afterwards, this model is empty. Deselection events are issued for all competitors that
|
||||
* were selected so far.
|
||||
*/
|
||||
public void clear() {
|
||||
Iterator<CompetitorDTO> selIter = getSelectedCompetitors().iterator();
|
||||
while (selIter.hasNext()) {
|
||||
CompetitorDTO selected = selIter.next();
|
||||
setSelected(selected, false);
|
||||
selIter = getSelectedCompetitors().iterator();
|
||||
}
|
||||
assert selectedCompetitors.isEmpty();
|
||||
allCompetitors.clear();
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
|
||||
public void addAll(Iterable<CompetitorDTO> competitors) {
|
||||
boolean changed = false;
|
||||
for (CompetitorDTO competitor : competitors) {
|
||||
add(competitor, false);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the competitor from the {@link #getAllCompetitors() set of all competitor}. If the competitor was previously
|
||||
* contained and selected, it is deselected first.
|
||||
*/
|
||||
public void remove(CompetitorDTO competitor) {
|
||||
if (isSelected(competitor)) {
|
||||
setSelected(competitor, false);
|
||||
}
|
||||
boolean changed = allCompetitors.remove(competitor);
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<CompetitorDTO> getSelectedCompetitors() {
|
||||
return Collections.unmodifiableCollection(selectedCompetitors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<CompetitorDTO> getAllCompetitors() {
|
||||
return Collections.unmodifiableCollection(allCompetitors);
|
||||
}
|
||||
|
||||
public void setSelected(CompetitorDTO competitor, boolean selected, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
if (selected) {
|
||||
if (allCompetitors.contains(competitor) && !selectedCompetitors.contains(competitor)) {
|
||||
selectedCompetitors.add(competitor);
|
||||
fireAddedToSelection(competitor, listenersNotToNotify);
|
||||
}
|
||||
} else {
|
||||
if (selectedCompetitors.contains(competitor)) {
|
||||
selectedCompetitors.remove(competitor);
|
||||
fireRemovedFromSelection(competitor, listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireAddedToSelection(CompetitorDTO competitor, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.addedToSelection(competitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireRemovedFromSelection(CompetitorDTO competitor, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.removedFromSelection(competitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireListChanged(Iterable<CompetitorDTO> competitors, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.competitorsListChanged(competitors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected(CompetitorDTO competitor) {
|
||||
return selectedCompetitors.contains(competitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(Iterable<CompetitorDTO> newSelection, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
Set<CompetitorDTO> competitorsToRemoveFromSelection = new HashSet<CompetitorDTO>(selectedCompetitors);
|
||||
for (CompetitorDTO newSelectedCompetitor : newSelection) {
|
||||
setSelected(newSelectedCompetitor, true, listenersNotToNotify);
|
||||
competitorsToRemoveFromSelection.remove(newSelectedCompetitor);
|
||||
}
|
||||
for (CompetitorDTO competitorToRemoveFromSelection : competitorsToRemoveFromSelection) {
|
||||
setSelected(competitorToRemoveFromSelection, false, listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompetitors(Iterable<CompetitorDTO> newCompetitors, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
boolean changed = false;
|
||||
|
||||
Set<CompetitorDTO> oldCompetitorsToRemove = new HashSet<CompetitorDTO>(allCompetitors);
|
||||
for (CompetitorDTO newCompetitor : newCompetitors) {
|
||||
if (allCompetitors.contains(newCompetitor)) {
|
||||
oldCompetitorsToRemove.remove(newCompetitor);
|
||||
} else {
|
||||
add(newCompetitor, false);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove) {
|
||||
remove(oldCompetitorToRemove);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors(), listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColor(CompetitorDTO competitor) {
|
||||
if (allCompetitors.contains(competitor)) {
|
||||
return competitorsColorMap.getColorByID(competitor);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import com.sap.sailing.domain.common.filter.Filter;
|
||||
import com.sap.sailing.domain.common.filter.FilterSet;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
|
||||
public class CompetitorSelectionModel implements CompetitorSelectionProvider {
|
||||
private final Set<CompetitorDTO> allCompetitors;
|
||||
|
||||
private final LinkedHashSet<CompetitorDTO> selectedCompetitors;
|
||||
|
||||
private final Set<CompetitorSelectionChangeListener> listeners;
|
||||
|
||||
private final boolean hasMultiSelection;
|
||||
|
||||
private final ColorMap<CompetitorDTO> competitorsColorMap;
|
||||
|
||||
private FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> competitorsFilterSet;
|
||||
|
||||
public CompetitorSelectionModel(boolean hasMultiSelection) {
|
||||
this(hasMultiSelection, null);
|
||||
}
|
||||
|
||||
public CompetitorSelectionModel(boolean hasMultiSelection, FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> competitorsFilterSet) {
|
||||
super();
|
||||
this.hasMultiSelection = hasMultiSelection;
|
||||
this.competitorsFilterSet = competitorsFilterSet;
|
||||
this.allCompetitors = new HashSet<CompetitorDTO>();
|
||||
this.selectedCompetitors = new LinkedHashSet<CompetitorDTO>();
|
||||
this.listeners = new HashSet<CompetitorSelectionChangeListener>();
|
||||
this.competitorsColorMap = new ColorMap<CompetitorDTO>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a competitor to the {@link #getAllCompetitors() set of all competitors}. If the competitor was not yet
|
||||
* contained, it will be deselected.
|
||||
*/
|
||||
public void add(CompetitorDTO competitor) {
|
||||
add(competitor, true);
|
||||
}
|
||||
|
||||
private void add(CompetitorDTO competitor, boolean notifyListeners) {
|
||||
boolean changed = allCompetitors.add(competitor);
|
||||
if (notifyListeners && changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMultiSelection() {
|
||||
return hasMultiSelection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all competitors. Afterwards, this model is empty. Deselection events are issued for all competitors that
|
||||
* were selected so far.
|
||||
*/
|
||||
public void clear() {
|
||||
Iterator<CompetitorDTO> selIter = getSelectedCompetitors().iterator();
|
||||
while (selIter.hasNext()) {
|
||||
CompetitorDTO selected = selIter.next();
|
||||
setSelected(selected, false);
|
||||
selIter = getSelectedCompetitors().iterator();
|
||||
}
|
||||
assert selectedCompetitors.isEmpty();
|
||||
allCompetitors.clear();
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
|
||||
public void addAll(Iterable<CompetitorDTO> competitors) {
|
||||
boolean changed = false;
|
||||
for (CompetitorDTO competitor : competitors) {
|
||||
add(competitor, false);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the competitor from the {@link #getAllCompetitors() set of all competitor}. If the competitor was previously
|
||||
* contained and selected, it is deselected first.
|
||||
*/
|
||||
public void remove(CompetitorDTO competitor) {
|
||||
if (isSelected(competitor)) {
|
||||
setSelected(competitor, false);
|
||||
}
|
||||
boolean changed = allCompetitors.remove(competitor);
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<CompetitorDTO> getSelectedCompetitors() {
|
||||
return Collections.unmodifiableCollection(selectedCompetitors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<CompetitorDTO> getAllCompetitors() {
|
||||
return Collections.unmodifiableCollection(allCompetitors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CompetitorDTO> getFilteredCompetitors() {
|
||||
Set<CompetitorDTO> currentFilteredList = new LinkedHashSet<CompetitorDTO>(allCompetitors);
|
||||
if (competitorsFilterSet != null) {
|
||||
for (Filter<CompetitorDTO> filter : competitorsFilterSet.getFilters()) {
|
||||
for (Iterator<CompetitorDTO> i=currentFilteredList.iterator(); i.hasNext(); ) {
|
||||
CompetitorDTO competitorDTO = i.next();
|
||||
if (!filter.matches(competitorDTO)) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentFilteredList;
|
||||
}
|
||||
|
||||
public void setSelected(CompetitorDTO competitor, boolean selected, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
if (selected) {
|
||||
if (allCompetitors.contains(competitor) && !selectedCompetitors.contains(competitor)) {
|
||||
selectedCompetitors.add(competitor);
|
||||
fireAddedToSelection(competitor, listenersNotToNotify);
|
||||
}
|
||||
} else {
|
||||
if (selectedCompetitors.contains(competitor)) {
|
||||
selectedCompetitors.remove(competitor);
|
||||
fireRemovedFromSelection(competitor, listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireAddedToSelection(CompetitorDTO competitor, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.addedToSelection(competitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireRemovedFromSelection(CompetitorDTO competitor, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.removedFromSelection(competitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fireListChanged(Iterable<CompetitorDTO> competitors, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
if (listenersNotToNotify == null || !Arrays.asList(listenersNotToNotify).contains(listener)) {
|
||||
listener.competitorsListChanged(competitors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSelected(CompetitorDTO competitor) {
|
||||
return selectedCompetitors.contains(competitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelection(Iterable<CompetitorDTO> newSelection, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
Set<CompetitorDTO> competitorsToRemoveFromSelection = new HashSet<CompetitorDTO>(selectedCompetitors);
|
||||
for (CompetitorDTO newSelectedCompetitor : newSelection) {
|
||||
setSelected(newSelectedCompetitor, true, listenersNotToNotify);
|
||||
competitorsToRemoveFromSelection.remove(newSelectedCompetitor);
|
||||
}
|
||||
for (CompetitorDTO competitorToRemoveFromSelection : competitorsToRemoveFromSelection) {
|
||||
setSelected(competitorToRemoveFromSelection, false, listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompetitors(Iterable<CompetitorDTO> newCompetitors, CompetitorSelectionChangeListener... listenersNotToNotify) {
|
||||
boolean changed = false;
|
||||
|
||||
Set<CompetitorDTO> oldCompetitorsToRemove = new HashSet<CompetitorDTO>(allCompetitors);
|
||||
for (CompetitorDTO newCompetitor : newCompetitors) {
|
||||
if (allCompetitors.contains(newCompetitor)) {
|
||||
oldCompetitorsToRemove.remove(newCompetitor);
|
||||
} else {
|
||||
add(newCompetitor, false);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
for (CompetitorDTO oldCompetitorToRemove : oldCompetitorsToRemove) {
|
||||
remove(oldCompetitorToRemove);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
fireListChanged(getAllCompetitors(), listenersNotToNotify);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColor(CompetitorDTO competitor) {
|
||||
if (allCompetitors.contains(competitor)) {
|
||||
return competitorsColorMap.getColorByID(competitor);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> getCompetitorsFilterSet() {
|
||||
return competitorsFilterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCompetitorsFilterSet(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> competitorsFilterSet) {
|
||||
this.competitorsFilterSet = competitorsFilterSet;
|
||||
for (CompetitorSelectionChangeListener listener : listeners) {
|
||||
listener.filteredCompetitorsListChanged(getFilteredCompetitors());
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -1,6 +1,8 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.filter.Filter;
|
||||
import com.sap.sailing.domain.common.filter.FilterSet;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
|
||||
public interface CompetitorSelectionProvider {
|
||||
|
||||
@@ -11,8 +13,17 @@ public interface CompetitorSelectionProvider {
|
||||
*/
|
||||
Iterable<CompetitorDTO> getSelectedCompetitors();
|
||||
|
||||
/**
|
||||
* @return a non-<code>null</code> sequence of all competitors which may be empty.
|
||||
*/
|
||||
Iterable<CompetitorDTO> getAllCompetitors();
|
||||
|
||||
/**
|
||||
* @return a non-<code>null</code> sequence of all competitors filtered by the applied
|
||||
* {@link #getCompetitorsFilterSet() filter set} (which may be null).
|
||||
*/
|
||||
Iterable<CompetitorDTO> getFilteredCompetitors();
|
||||
|
||||
/**
|
||||
* Updates the selection state of <code>competitor</code> if contained in {@link #getAllCompetitors()}. If this
|
||||
* means a change in <code>competitor</code>'s selection state, all listeners except for those in
|
||||
@@ -48,4 +59,8 @@ public interface CompetitorSelectionProvider {
|
||||
void addCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener);
|
||||
|
||||
void removeCompetitorSelectionChangeListener(CompetitorSelectionChangeListener listener);
|
||||
|
||||
public FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> getCompetitorsFilterSet();
|
||||
|
||||
public void setCompetitorsFilterSet(FilterSet<CompetitorDTO, ? extends Filter<CompetitorDTO>> competitorsFilterSet);
|
||||
}
|
||||
+1
-1
@@ -263,7 +263,7 @@ public abstract class DataEntryDialog<T> {
|
||||
*
|
||||
* @param initialValue initial value to show in the integer box; <code>null</code> is permissible
|
||||
*/
|
||||
public IntegerBox createIntegerBox(int initialValue, int visibleLength) {
|
||||
public IntegerBox createIntegerBox(Integer initialValue, int visibleLength) {
|
||||
IntegerBox intBox = new IntegerBox();
|
||||
intBox.setVisibleLength(visibleLength);
|
||||
intBox.setValue(initialValue);
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.sap.sailing.gwt.ui.client;
|
||||
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
|
||||
/**
|
||||
* Interface for a GWT specific serializer to Json.
|
||||
*
|
||||
* @param <T>
|
||||
* Object class to serialize.
|
||||
*/
|
||||
public interface GwtJsonDeSerializer<T> {
|
||||
/**
|
||||
* Serializes given object to a Json object.
|
||||
*
|
||||
* @param object
|
||||
* to be serialized.
|
||||
* @return serialized Json object.
|
||||
*/
|
||||
JSONObject serialize(T object);
|
||||
|
||||
T deserialize(JSONObject object);
|
||||
}
|
||||
Executable → Regular
File mode changed.
+3
-3
@@ -13,9 +13,9 @@ import com.google.gwt.core.client.Scheduler.RepeatingCommand;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.sap.sailing.domain.common.RaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.gwt.ui.shared.FleetDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnDTO;
|
||||
import com.sap.sailing.domain.common.dto.FleetDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
|
||||
public class RaceTimesInfoProvider {
|
||||
|
||||
+4
-4
@@ -22,23 +22,23 @@ import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
|
||||
import com.sap.sailing.domain.common.RegattaIdentifier;
|
||||
import com.sap.sailing.domain.common.ScoringSchemeType;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.dto.CompetitorDTO;
|
||||
import com.sap.sailing.domain.common.dto.LeaderboardDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceColumnInSeriesDTO;
|
||||
import com.sap.sailing.domain.common.dto.RaceDTO;
|
||||
import com.sap.sailing.domain.common.impl.Util.Pair;
|
||||
import com.sap.sailing.domain.common.impl.Util.Triple;
|
||||
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.EventDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceColumnInSeriesDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
|
||||
import com.sap.sailing.gwt.ui.shared.RegattaOverviewEntryDTO;
|
||||
|
||||
Loaded 100 of 583 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user