diff --git a/.gitignore b/.gitignore index c399cc09e36..0598c974e59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target build.log +reporting.log .metadata *.class *.swp @@ -28,4 +29,3 @@ java/com.sap.sailing.simulator/bin/com/sap/sailing/simulator/Simulator.gwt.xml /mobile/com.sap.sailing.racecommittee.app/res/values/keys.xml /mobile/google-play-services_lib/gen/* -/mobile/google-play-services_lib/gen/com/google/android/gms/BuildConfig.java diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index f2a9e95a442..9530c4cbcf7 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -1,6 +1,7 @@ #!/bin/bash set -o functrace + find_project_home () { if [[ $1 == '/' ]] || [[ $1 == "" ]]; then @@ -76,15 +77,19 @@ testing=1 clean="clean" offline=0 proxy=0 +android=1 +reporting=0 suppress_confirmation=0 extra='' if [ $# -eq 0 ]; then - echo "buildAndUpdateProduct [-b -u -g -t -o -c -m -n -l ] [build|install|all|hot-deploy|remote-deploy|release]" + echo "buildAndUpdateProduct [-b -u -g -t -a -o -c -m -n -l ] [build|install|all|hot-deploy|remote-deploy|release]" echo "" echo "-g Disable GWT compile, no gwt files will be generated, old ones will be preserved." echo "-b Build GWT permutation only for one browser and English language." echo "-t Disable tests" + echo "-a Disable mobile projects (RaceCommittee App)" + echo "-r Enable generating surefire test reports" echo "-o Enable offline mode (does not work for tycho surefire plugin)" echo "-c Disable cleaning (use only if you are sure that no java file has changed)" echo "-p Enable proxy mode (overwrites file specified by -m)" @@ -126,16 +131,18 @@ echo SERVERS_HOME is $SERVERS_HOME echo BRANCH is $active_branch echo VERSION is $VERSION_INFO -options=':bgtocpm:n:l:s:w:u' +options=':bgtocparm:n:l:s:w:u' while getopts $options option do case $option in g) gwtcompile=0;; t) testing=0;; - b) onegwtpermutationonly=1;; + b) onegwtpermutationonly=1;; o) offline=1;; c) clean="";; p) proxy=1;; + a) android=0;; + r) reporting=1;; m) MAVEN_SETTINGS=$OPTARG;; n) OSGI_BUNDLE_NAME=$OPTARG;; l) OSGI_TELNET_PORT=$OPTARG;; @@ -206,8 +213,7 @@ if [[ "$@" == "release" ]]; then cp -v $PROJECT_HOME/java/target/start $ACDIR/ cp -v $PROJECT_HOME/java/target/stop $ACDIR/ cp -v $PROJECT_HOME/java/target/status $ACDIR/ - cp -v $PROJECT_HOME/java/target/updateEC2Instance.sh $ACDIR/ - cp -v $PROJECT_HOME/java/target/shouldIBuildOrShouldIGo.sh $ACDIR/ + cp -v $PROJECT_HOME/java/target/refreshInstance.sh $ACDIR/ cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/ cp -v $p2PluginRepository/configuration/config.ini configuration/ @@ -417,6 +423,9 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then extra="-Pdebug.no-gwt-compile" fi + # back to root! + cd $PROJECT_HOME + if [ $testing -eq 0 ]; then echo "INFO: Skipping tests" extra="$extra -Dmaven.test.skip=true -DskipTests=true" @@ -437,6 +446,30 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then extra="$extra -P no-debug.without-proxy" fi + if [ $android -eq 1 ]; then + if [[ $ANDROID_HOME == "" ]]; then + echo "Environment variable ANDROID_HOME not found. Aborting." + echo "Deactivate mobile build with parameter -a." + exit + fi + echo "ANDROID_HOME=$ANDROID_HOME" + PATH=$PATH:$ANDROID_HOME/tools + PATH=$PATH:$ANDROID_HOME/platform-tools + + RC_APP_VERSION=`grep "android:versionCode=" mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml | cut -d "\"" -f 2` + echo "RC_APPVERSION=$RC_APP_VERSION" + extra="$extra -Drc-app-api-version=$RC_APP_VERSION" + + else + echo "INFO: Deactivating mobile modules" + extra="$extra -P !with-mobile" + fi + + if [ $reporting -eq 1 ]; then + echo "INFO: Activating reporting" + extra="$extra -Dreportsdirectory=$PROJECT_HOME/target/surefire-reports" + fi + # make sure to honour the service configuration # needed to make sure that tests use the right servers APP_PARAMETERS="-Dmongo.host=$MONGODB_HOST -Dmongo.port=$MONGODB_PORT -Dexpedition.udp.port=$EXPEDITION_PORT -Dreplication.exchangeHost=$REPLICATION_HOST -Dreplication.exchangeName=$REPLICATION_CHANNEL" @@ -445,6 +478,16 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then echo "Maven version used: `mvn --version`" mvn $extra -DargLine="$APP_PARAMETERS" -fae -s $MAVEN_SETTINGS $clean install 2>&1 | tee $START_DIR/build.log + if [ $reporting -eq 1 ]; then + echo "INFO: Generating reports" + echo "Using following command: mvn $extra -DargLine=\"$APP_PARAMETERS\" -fae -s $MAVEN_SETTINGS surefire-report:report-only" + mvn $extra -DargLine="$APP_PARAMETERS" -fae -s $MAVEN_SETTINGS surefire-report:report-only 2>&1 | tee $START_DIR/reporting.log + tar -xzf configuration/surefire-reports-resources.tar.gz + echo "INFO: Reports generated in $PROJECT_HOME/target/site/surefire-report.html" + echo "INFO: Be sure to check the result of the actual BUILD run!" + fi + + cd $PROJECT_HOME/java if [ $gwtcompile -eq 1 ]; then # Now move back the backup .gwt.xml files before they were (maybe) patched echo "INFO: restoring backup copies of .gwt.xml files after they has been patched before" @@ -506,8 +549,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then cp -v $PROJECT_HOME/java/target/start $ACDIR/ cp -v $PROJECT_HOME/java/target/stop $ACDIR/ cp -v $PROJECT_HOME/java/target/status $ACDIR/ - cp -v $PROJECT_HOME/java/target/updateEC2Instance.sh $ACDIR/ - cp -v $PROJECT_HOME/java/target/shouldIBuildOrShouldIGo.sh $ACDIR/ + cp -v $PROJECT_HOME/java/target/refreshInstance.sh $ACDIR/ if [ ! -f "$ACDIR/env.sh" ]; then cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/ diff --git a/configuration/git-du.rb b/configuration/git-du.rb new file mode 100755 index 00000000000..edb9ec43f4e --- /dev/null +++ b/configuration/git-du.rb @@ -0,0 +1,190 @@ +#!/usr/bin/env ruby -w +require 'set' + +$display_branches = ARGV + +if $display_branches.empty? + puts "You need to specify a branch name or ALL for all branches. Make sure to have at least git >=1.8 and be warned that processing one branch takes several minutes!" + exit +end + +$branches = {} +$big_files = {} +$dependable_blob_shas = Set.new +$packed_blobs = {} + +class PackedBlob + attr_accessor :sha, :type, :size, :packed_size, :offset, :depth, :base_sha, :is_shared, :branch + def initialize(sha, type, size, packed_size, offset, depth, base_sha) + @sha = sha + @type = type + @size = size + @packed_size = packed_size + @offset = offset + @depth = depth + @base_sha = base_sha + @is_shared = false + @branch = nil + end +end + +class Branch + attr_accessor :name, :blobs, :non_shared_size, :non_shared_packed_size, :shared_size, :shared_packed_size, :non_shared_dependable_size, :non_shared_dependable_packed_size + def initialize(name) + @name = name + @blobs = Set.new + @non_shared_size = 0 + @non_shared_packed_size = 0 + @shared_size = 0 + @shared_packed_size = 0 + @non_shared_dependable_size = 0 + @non_shared_dependable_packed_size = 0 + end +end + +if not File.exists?('packed_blobs.bin') + + # Collect every packed blobs information + for pack_idx in Dir[".git/objects/pack/pack-*.idx"] + puts pack_idx + pack_counter = 0 + IO.popen("git verify-pack -v #{pack_idx}", 'r') do |pack_list| + pack_list.each_line do |pack_line| + pack_counter += 1 + pack_line.chomp! + if not pack_line.include? "delta" + $stdout.write "\r #{pack_counter} packs"; $stdout.flush; + sha, type, size, packed_size, offset, depth, base_sha = pack_line.split(/\s+/, 7) + size = size.to_i + packed_size = packed_size.to_i + $packed_blobs[sha] = PackedBlob.new(sha, type, size, packed_size, offset, depth, base_sha) + $dependable_blob_shas.add(base_sha) if base_sha != nil + else + break + end + end + end + puts " " + end + + puts "Writing packed blobs..." + File.open('packed_blobs.bin','wb') do |f| + f.write Marshal.dump($packed_blobs) + end + + puts "Writing dependable blob SHAs..." + File.open('dependable_blob_shas.bin','wb') do |f| + f.write Marshal.dump($dependable_blob_shas) + end + +else + puts "Reading packed blobs from file! This means that information won't be up 2 date but process is faster!" + $packed_blobs = Marshal.load(File.open('packed_blobs.bin', 'rb')) + $dependable_blob_shas = Marshal.load(File.open('dependable_blob_shas.bin', 'rb')) + puts "Read #{$packed_blobs.size()} packed blobs and #{$dependable_blob_shas.size()} SHAs..." +end + +def compute_branch(branch_name) + if $display_branches.include?(branch_name) or $display_branches.include?("ALL") + puts "* #{branch_name}" + if not File.exists?("#{branch_name}.bin") + puts "\tFile #{branch_name}.bin does not exist - reading data from repository" + branch = Branch.new(branch_name) + $branches[branch_name] = branch + commit_counter = 0; obj_counter = 0 + IO.popen("git rev-list #{branch_name}", 'r') do |rev_list| + all_commit_lines = rev_list.readlines + all_commit_lines.each do |commit| + commit_all_count = all_commit_lines.count + old_obj_counter = obj_counter + commit_counter += 1 + obj_counter = 0; + size_count = 0 + # Look into each commit in order to collect all the blobs used + blobs_all = `git ls-tree -zrl #{commit}`.split("\0") + for object in blobs_all + obj_counter += 1 + bits, type, sha, size, path = object.split(/\s+/, 5) + if type == 'blob' + blob = $packed_blobs[sha] + if blob != nil + size_count += blob.size + branch.blobs.add(blob) + if not blob.is_shared + if blob.branch != nil and blob.branch != branch + # this blob has been used in another branch, let's set it to "shared" + blob.is_shared = true + blob.branch = nil + else + blob.branch = branch + end + end + end + else + print "F" + end + end + if old_obj_counter < obj_counter + obj_changes = "%04d" % (obj_counter - old_obj_counter) + change_mode = "++" + else + obj_changes = "%04d" % (old_obj_counter - obj_counter) + change_mode = "--" + end + size_count = "%10d" % (size_count / 1000) + $stdout.write "\r\tProcessed commit #{commit_counter} of #{commit_all_count} (Objects:\t#{obj_counter}\t#{obj_changes}\t#{change_mode}\t#{size_count} kb)"; $stdout.flush; + end + end + print "\n\tWriting branch data to file..." + File.open("#{branch_name}.bin",'wb') do |f| + f.write Marshal.dump(branch) + end + puts "OK" + else + print "\n\tLoading branch data from file #{branch_name}. Remove this file to get new data..." + $branches[branch_name] = Marshal.load(File.open("#{branch_name}.bin", 'rb')) + branch = $branches[branch_name] + puts "OK" + end + branch.blobs.each do |blob| + if blob.is_shared + branch.shared_size += blob.size + branch.shared_packed_size += blob.packed_size + else + if dependable_blob_shas.include?(blob.sha) + branch.non_shared_dependable_size += blob.size + branch.non_shared_dependable_packed_size += blob.packed_size + else + branch.non_shared_size += blob.size + branch.non_shared_packed_size += blob.packed_size + end + end + end + Mutex.new.synchronize do + puts "\tbranch: %s" % branch_name + puts "\tblobs: %s" % branch.blobs.count + puts "\tnon shared:" + puts "\t\tpacked: %s kb" % (branch.non_shared_packed_size / 1000) + puts "\t\tnon packed: %s kb" % (branch.non_shared_size / 1000) + puts "\t\tall: %s kb" % ((branch.non_shared_packed_size + branch.non_shared_size) / 1000) + puts "\tnon shared but with dependencies on it:" + puts "\t\tpacked: %s kb" % (branch.non_shared_dependable_packed_size / 1000) + puts "\t\tnon packed: %s kb" % (branch.non_shared_dependable_size / 1000) + puts "\tshared:" + puts "\t\tpacked: %s kb" % (branch.shared_packed_size / 1000) + puts "\t\tnon packed: %s kb" % (branch.shared_size / 1000), "" + end + end +end + +# Now check all blobs for every branches in order to determine whether it's shared between branches or not +threads = [] +IO.popen("git branch --list", 'r') do |branch_list| + branches_all = branch_list.readlines + puts "Loaded #{branches_all.count} branches..." + branches_all.each do |branch_line| + # For each branch + branch_name = branch_line[2..-1].chomp + compute_branch(branch_name) + end +end diff --git a/configuration/surefire-reports-resources.tar.gz b/configuration/surefire-reports-resources.tar.gz new file mode 100644 index 00000000000..508760516f8 Binary files /dev/null and b/configuration/surefire-reports-resources.tar.gz differ diff --git a/home.md b/home.md index 80fdf38898f..f569b6abc94 100644 --- a/home.md +++ b/home.md @@ -1,6 +1,6 @@ # Welcome to the SAP Sailing Wiki -This is the Wiki where useful information regarding this project can be found. +This is the Sailing Analytics Wiki where useful information regarding this project can be found. ### The Pitch @@ -46,11 +46,11 @@ Like businesses, sailors need the latest information to make strategic decisions * [Bugzilla Issue Tracking System](http://bugzilla.sapsailing.com/bugzilla/) * [GIT Repository (SAP)](ssh://git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git) -* [Maven Repository Browser](http://maven.sapsailing.com/maven/) +* [Maven Repository Browser](http://maven.sapsailing.com/maven/) (see [[how to setup repository for Android builds|wiki/racecommittee-app-environment]]) * [Main Sailing Website](http://www.sapsailing.com) * [Visitor Statistics](http://analysis.sapsailing.com/) * [Traffic and CPU for app01](http://mrtg.sapsailing.com/) ### TracTrac Workshop in Kopenhagen -* [[TracTrac Workshop in October 2013 in Kopenhagen|wiki/TracTracWorkshop2013]] +* [[TracTrac Workshop in October 2013 in Kopenhagen|wiki/TracTracWorkshop2013]] \ No newline at end of file diff --git a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/DeclinationService.java b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/DeclinationService.java index 52d9aca902c..8bd18565c10 100755 --- a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/DeclinationService.java +++ b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/DeclinationService.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.text.ParseException; import com.sap.sailing.declination.impl.DeclinationServiceImpl; +import com.sap.sailing.declination.impl.NOAAImporter; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Mile; import com.sap.sailing.domain.common.Position; @@ -20,11 +21,15 @@ public interface DeclinationService { /** * Obtains declination information with the default precision of this declination service in time and space * dimensions (e.g., one degree N/S and E/W and one year on the time axis). Particularly the time resolution may - * benefit from an extrapolation using the {@link Declination#getAnnualChange() annual change} specification. - * Note that the result is not corrected automatically by the {@link Declination#getAnnualChange() - * annual change} of the result found for the position. The caller may do that using + * benefit from an extrapolation using the {@link Declination#getAnnualChange() annual change} specification. Note + * that the result is not corrected automatically by the {@link Declination#getAnnualChange() annual + * change} of the result found for the position. The caller may do that using * {@link Declination#getBearingCorrectedTo(TimePoint)}. - * @param timeoutForOnlineFetchInMilliseconds TODO + * + * @param timeoutForOnlineFetchInMilliseconds + * see {@link NOAAImporter#getDeclination(Position, TimePoint, long)}; if 0, waits forever; only needed + * if the pre-stored declinations don't fulfill the request up to the tolerance distance configured when + * constructing this service */ Declination getDeclination(TimePoint timePoint, Position position, long timeoutForOnlineFetchInMilliseconds) throws IOException, ClassNotFoundException, ParseException; @@ -36,9 +41,13 @@ public interface DeclinationService { *

* * The time point precision is the default precision of this service. Note that the result is not corrected - * automatically by the {@link Declination#getAnnualChange() annual change} of the result found for the - * position. The caller may do that using {@link Declination#getBearingCorrectedTo(TimePoint)}. - * @param timeoutForOnlineFetchInMilliseconds TODO + * automatically by the {@link Declination#getAnnualChange() annual change} of the result found for the position. + * The caller may do that using {@link Declination#getBearingCorrectedTo(TimePoint)}. + * + * @param timeoutForOnlineFetchInMilliseconds + * see {@link NOAAImporter#getDeclination(Position, TimePoint, long)}; if 0, waits forever; only needed + * if the pre-stored declinations don't fulfill the request up to the tolerance distance provided by the + * maxDistance parameter */ Declination getDeclination(TimePoint timePoint, Position position, Distance maxDistance, long timeoutForOnlineFetchInMilliseconds) throws IOException, ClassNotFoundException, ParseException; diff --git a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF index 46cdcc19708..d4e72b72fcb 100755 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -7,6 +7,8 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Export-Package: com.sap.sailing.domain.common, + com.sap.sailing.domain.common.configuration, + com.sap.sailing.domain.common.dto, com.sap.sailing.domain.common.filter, com.sap.sailing.domain.common.impl, com.sap.sailing.domain.common.media, @@ -14,5 +16,4 @@ Export-Package: com.sap.sailing.domain.common, 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.utils, - com.sap.sailing.domain.common.dto, com.sap.sailing.domain.common.trackfiles diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java index 590249ba9f4..4508b693b6b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Bearing.java @@ -13,6 +13,10 @@ public interface Bearing extends Serializable { */ Bearing reverse(); + /** + * Adds the bearing diff to this bearing and returns the new result. The result will be between 0 and 360 (inclusive) if + * this bearing and diff are also within 0 and 360. + */ Bearing add(Bearing diff); /** diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java index b1887137d21..bcdead1f5bc 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Color.java @@ -2,9 +2,36 @@ package com.sap.sailing.domain.common; import java.io.Serializable; +import com.sap.sailing.domain.common.impl.RGBColor; import com.sap.sailing.domain.common.impl.Util; public interface Color extends Serializable { + final Color WHITE = new RGBColor(255, 255, 255); + + final Color LIGHT_GRAY = new RGBColor(192, 192, 192); + + final Color GRAY = new RGBColor(128, 128, 128); + + final Color DARK_GRAY = new RGBColor(64, 64, 64); + + final Color BLACK = new RGBColor(0, 0, 0); + + final Color RED = new RGBColor(255, 0, 0); + + final Color PINK = new RGBColor(255, 175, 175); + + final Color ORANGE = new RGBColor(255, 200, 0); + + final Color YELLOW = new RGBColor(255, 255, 0); + + final Color GREEN = new RGBColor(0, 255, 0); + + final Color MAGENTA = new RGBColor(255, 0, 255); + + final Color CYAN = new RGBColor(0, 255, 255); + + final Color BLUE = new RGBColor(0, 0, 255); + Util.Triple getAsRGB(); Util.Triple getAsHSV(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ColorMap.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ColorMap.java new file mode 100644 index 00000000000..0d3099e045b --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ColorMap.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.common; + +/** + * Manages color assignments to objects. + * + * @author Axel Uhl (d043530) + * + * @param + * the type of the objects to which a color is assigned + */ +public interface ColorMap { + Color getColorByID(T object); + + boolean addBlockedColor(Color color); + boolean removeBlockedColor(Color color); + void clearBlockedColors(); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java new file mode 100644 index 00000000000..627266e2e18 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/CourseDesignerMode.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.common; + +import java.util.ArrayList; +import java.util.List; + +/** + * Used to decided which Course Designer to pick. + * + * When modifying these values also check res/preferences.xml of racecommittee.app! + */ +public enum CourseDesignerMode { + UNKNOWN("Unknown"), + BY_NAME("By-Name Course Designer"), // O2, O3, I2,... + BY_MAP("By-Map Course Designer"), // Map-based Course Designer + BY_MARKS("By-Marks Course Designer"); // ESS + + private String displayName; + + private CourseDesignerMode(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return displayName; + } + + public static CourseDesignerMode[] validValues() { + List validValues = new ArrayList(); + for (CourseDesignerMode type : values()) { + if (type != UNKNOWN) { + validValues.add(type); + } + } + return validValues.toArray(new CourseDesignerMode[0]); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/SpeedWithBearing.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/SpeedWithBearing.java index 8fbb08cd518..1d77e1c5df6 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/SpeedWithBearing.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/SpeedWithBearing.java @@ -29,4 +29,8 @@ public interface SpeedWithBearing extends Speed { */ Speed projectTo(Position position, Bearing bearing); + /** + * Adds two directed speeds onto each other using vector addition. + */ + SpeedWithBearing add(SpeedWithBearing other); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/configuration/DeviceConfigurationMatcherType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/configuration/DeviceConfigurationMatcherType.java new file mode 100644 index 00000000000..22b65be16ea --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/configuration/DeviceConfigurationMatcherType.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.common.configuration; + +public enum DeviceConfigurationMatcherType { + + SINGLE(1), MULTI(2); + + private int matchingRank; + + private DeviceConfigurationMatcherType(int rank) { + this.matchingRank = rank; + } + + public int getRank() { + return matchingRank; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index 167c57fa9cb..e4aa52964a0 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.dto; import java.io.Serializable; +import com.sap.sailing.domain.common.Color; + public interface CompetitorDTO extends Serializable { String getTwoLetterIsoCountryCode(); @@ -17,6 +19,8 @@ public interface CompetitorDTO extends Serializable { BoatClassDTO getBoatClass(); String getName(); + + Color getColor(); /** * A regular instance will simply return this object. A compacted version may compute the result by looking it up diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java index f6ee74ec6cd..7061682af37 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTOImpl.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.dto; import java.io.Serializable; +import com.sap.sailing.domain.common.Color; + /** * Equality and hash code are based on the {@link #getIdAsString() ID}, the {@link #getSailID() sail number}, the * {@link #getBoatClass() boat class} (whose equality and hash code, in turn, depends on its name) and the @@ -13,18 +15,20 @@ import java.io.Serializable; */ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serializable { private static final long serialVersionUID = -4997852354821083154L; + private String countryName; private String twoLetterIsoCountryCode; private String threeLetterIocCountryCode; - private String countryName; + private Color color; private String sailID; private String idAsString; private BoatClassDTO boatClass; CompetitorDTOImpl() {} - public CompetitorDTOImpl(String name, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, + public CompetitorDTOImpl(String name, Color color, String twoLetterIsoCountryCode, String threeLetterIocCountryCode, String countryName, String sailID, String idAsString, BoatClassDTO boatClass) { super(name); + this.color = color; this.twoLetterIsoCountryCode = twoLetterIsoCountryCode; this.threeLetterIocCountryCode = threeLetterIocCountryCode; this.countryName = countryName; @@ -40,6 +44,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial result = prime * result + ((boatClass == null) ? 0 : boatClass.hashCode()); result = prime * result + ((idAsString == null) ? 0 : idAsString.hashCode()); result = prime * result + ((sailID == null) ? 0 : sailID.hashCode()); + result = prime * result + ((color == null) ? 0 : color.hashCode()); result = prime * result + ((threeLetterIocCountryCode == null) ? 0 : threeLetterIocCountryCode.hashCode()); return result; } @@ -73,6 +78,11 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial return false; } else if (!threeLetterIocCountryCode.equals(other.threeLetterIocCountryCode)) return false; + if (color == null) { + if (other.color != null) + return false; + } else if (!color.equals(other.color)) + return false; return true; } @@ -111,4 +121,7 @@ public class CompetitorDTOImpl extends NamedDTO implements CompetitorDTO, Serial return this; } + public Color getColor() { + return color; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java index 3ce385a3fd7..7921749a8f6 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/PreviousCompetitorDTOImpl.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.common.dto; +import com.sap.sailing.domain.common.Color; + /** * Equality and hash code are based on the index pointing into a previous leaderboard's competitors list. * @@ -67,6 +69,12 @@ public class PreviousCompetitorDTOImpl implements CompetitorDTO { " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); } + @Override + public Color getColor() { + throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ + " need to be replaced by an object of "+CompetitorDTOImpl.class.getName()+" after deserialization"); + } + @Override public String getIdAsString() { throw new RuntimeException("Internal error. Objects of type "+PreviousCompetitorDTOImpl.class.getName()+ diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java new file mode 100755 index 00000000000..ae5b6b0009c --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractColor.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.impl.Util.Triple; + +public abstract class AbstractColor implements Color { + private static final long serialVersionUID = 7758884012281863458L; + + @Override + public String getAsHtml() { + Triple asRGB = getAsRGB(); + return "#" + toBrowserHexValue(asRGB.getA()) + toBrowserHexValue(asRGB.getB()) + + toBrowserHexValue(asRGB.getC()); + } + + private static String toBrowserHexValue(int number) { + StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff)); + while (builder.length() < 2) { + builder.append("0"); + } + return builder.toString().toUpperCase(); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java index 065e62e0859..d1fd7c290ef 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java @@ -33,6 +33,11 @@ public abstract class AbstractSpeedWithAbstractBearingImpl extends AbstractSpeed && getBearing().equals(((SpeedWithBearing) object).getBearing()); } + @Override + public SpeedWithBearing add(SpeedWithBearing other) { + return AbstractSpeedWithAbstractBearingImpl.add(this, other); + } + @Override public CourseChange getCourseChangeRequiredToReach(SpeedWithBearing targetSpeedWithBearing) { return AbstractSpeedWithBearingImpl.getCourseChangeRequiredToReach(this, targetSpeedWithBearing); @@ -80,4 +85,12 @@ public abstract class AbstractSpeedWithAbstractBearingImpl extends AbstractSpeed public Speed projectTo(Position position, Bearing projectTo) { return projectTo(this, position, projectTo); } + + public static SpeedWithBearing add(SpeedWithBearing first, SpeedWithBearing other) { + double x = first.getMetersPerSecond()*Math.cos(first.getBearing().getRadians()) + other.getMetersPerSecond()*Math.cos(other.getBearing().getRadians()); + double y = first.getMetersPerSecond()*Math.sin(first.getBearing().getRadians()) + other.getMetersPerSecond()*Math.sin(other.getBearing().getRadians()); + double metersPerSecond = Math.sqrt(x*x+y*y); + double directionRad = (360.+Math.atan2(y, x))%360.; + return new MeterPerSecondSpeedWithDegreeBearingImpl(metersPerSecond, new RadianBearingImpl(directionRad)); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithBearingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithBearingImpl.java index 70507e1ac6e..2c3a0828e49 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithBearingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/AbstractSpeedWithBearingImpl.java @@ -15,5 +15,4 @@ public abstract class AbstractSpeedWithBearingImpl extends AbstractSpeedWithAbst public Bearing getBearing() { return bearing; } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ColorMap.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java similarity index 62% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ColorMap.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java index dbbd53cc3ab..0f307f87777 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/ColorMap.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ColorMapImpl.java @@ -1,8 +1,13 @@ -package com.sap.sailing.gwt.ui.client; +package com.sap.sailing.domain.common.impl; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; + +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.ColorMap; +import com.sap.sailing.domain.common.impl.Util.Triple; -import com.sap.sailing.domain.common.impl.HSVColor; /** * Manages color assignments to objects. @@ -12,14 +17,17 @@ import com.sap.sailing.domain.common.impl.HSVColor; * @param * the type of the objects to which a color is assigned */ -public class ColorMap { - private final HashMap idColor; +public class ColorMapImpl implements ColorMap { + private final HashMap idColor; private int colorCounter; private HSVColor[] baseColors; - public ColorMap() { + /** a list of already used colors which should be excluded from the automatic color assignment */ + private List blockedColors; + + public ColorMapImpl() { baseColors = new HSVColor[10]; baseColors[0] = new HSVColor(0.0f, 1.0f, 1.0f); // Red baseColors[1] = new HSVColor(30.0f, 1.0f, 1.0f); // Orange @@ -32,7 +40,8 @@ public class ColorMap { baseColors[8] = new HSVColor(300.0f, 1.0f, 1.0f); // Magenta baseColors[9] = new HSVColor(330.0f, 1.0f, 1.0f); - idColor = new HashMap(); + idColor = new HashMap(); + blockedColors = new ArrayList(); } /** @@ -43,15 +52,37 @@ public class ColorMap { * object's {@link Object#equals(Object)} and {@link Object#hashCode()}. * @return A color in hex/html-format (e.g. #ff0000) */ - public String getColorByID(T object) { - String color = idColor.get(object); - if (color == null || color.isEmpty()) { + public Color getColorByID(T object) { + Color color = idColor.get(object); + if (color == null) { color = createHexColor(colorCounter++); idColor.put(object, color); } return color; } + public boolean addBlockedColor(Color color) { + boolean result = false; + if(color != null) { + Triple asHSV = color.getAsHSV(); + result = blockedColors.add(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC())); + } + return result; + } + + public boolean removeBlockedColor(Color color) { + boolean result = false; + if(color != null) { + Triple asHSV = color.getAsHSV(); + result = blockedColors.remove(new HSVColor(asHSV.getA(), asHSV.getB(), asHSV.getC())); + } + return result; + } + + public void clearBlockedColors() { + blockedColors.clear(); + } + /** * Only use this if you don't want the color to be cached. * @@ -59,7 +90,7 @@ public class ColorMap { * The index of e.g. a competitor. Make sure, that each competitor has a unique index. * @return A color computed using the {@code index}. */ - private String createHexColor(int index) { + private Color createHexColor(int index) { int baseColorCount = baseColors.length; int baseColorsIndex = index % baseColorCount; int factor = index / (baseColorCount*3); @@ -87,6 +118,6 @@ public class ColorMap { HSVColor hsvColor = baseColors[baseColorsIndex]; HSVColor newColor = new HSVColor(hsvColor.getHue(), hsvColor.getSaturation() - saturationDecrease, hsvColor.getBrightness() - brightnessDecrease); - return newColor.getAsHtml(); + return newColor; } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java index 69e5b982a4d..519b08a64b8 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/HSVColor.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.common.impl; -import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.impl.Util.Triple; /** @@ -9,12 +8,12 @@ import com.sap.sailing.domain.common.impl.Util.Triple; * * @author Frank */ -public class HSVColor implements Color { +public class HSVColor extends AbstractColor { private static final long serialVersionUID = 7602013229606352246L; - float hue; - float saturation; - float brightness; + protected float hue; + protected float saturation; + protected float brightness; HSVColor() { } // for GWT serializability @@ -86,21 +85,6 @@ public class HSVColor implements Color { return RGBColor; } - @Override - public String getAsHtml() { - Triple asRGB = getAsRGB(); - return "#" + toBrowserHexValue(asRGB.getA()) + toBrowserHexValue(asRGB.getB()) - + toBrowserHexValue(asRGB.getC()); - } - - private static String toBrowserHexValue(int number) { - StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff)); - while (builder.length() < 2) { - builder.append("0"); - } - return builder.toString().toUpperCase(); - } - @Override public Triple getAsHSV() { return new Triple(hue, saturation, brightness); @@ -122,4 +106,32 @@ public class HSVColor implements Color { public float getBrightness() { return brightness; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Float.floatToIntBits(brightness); + result = prime * result + Float.floatToIntBits(hue); + result = prime * result + Float.floatToIntBits(saturation); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + HSVColor other = (HSVColor) obj; + if (Float.floatToIntBits(brightness) != Float.floatToIntBits(other.brightness)) + return false; + if (Float.floatToIntBits(hue) != Float.floatToIntBits(other.hue)) + return false; + if (Float.floatToIntBits(saturation) != Float.floatToIntBits(other.saturation)) + return false; + return true; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java new file mode 100644 index 00000000000..098537261a1 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/QRCodeUtils.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.impl.Util.Pair; + +public class QRCodeUtils { + + private static final String fragmentKey = "identifier"; + + public static String composeQRContent(String deviceIdentifier, String apkUrl) { + // poor man's uri fragment encoding: ' ' as '%20' + String encodedIdentifier = deviceIdentifier.replaceAll(" ", "%20"); + return apkUrl + "#" + fragmentKey + "=" + encodedIdentifier; + } + + public static Pair splitQRContent(String qrCodeContent) { + int fragmentIndex = qrCodeContent.lastIndexOf('#'); + if (fragmentIndex == -1 || fragmentIndex == 0 || qrCodeContent.length() == fragmentIndex + 1) { + throw new IllegalArgumentException("There is no server or identifier."); + } + String deviceIdentifier = qrCodeContent.substring(fragmentIndex + 1, qrCodeContent.length()); + if (!deviceIdentifier.startsWith(fragmentKey + "=")) { + throw new IllegalArgumentException("The identifier is malformed"); + } + deviceIdentifier = deviceIdentifier.substring((fragmentKey + "=").length()); + deviceIdentifier = deviceIdentifier.replaceAll("%20", " "); + + String apkUrl = qrCodeContent.substring(0, fragmentIndex); + return new Pair(deviceIdentifier, apkUrl); + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java index 6c7d6e6772b..3faa62a768f 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/RGBColor.java @@ -1,13 +1,12 @@ package com.sap.sailing.domain.common.impl; -import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.impl.Util.Triple; /** * A color defined in the RGB color schema * @author Frank */ -public class RGBColor implements Color { +public class RGBColor extends AbstractColor { private static final long serialVersionUID = -4091876840771631308L; private int red; private int green; @@ -21,6 +20,40 @@ public class RGBColor implements Color { this.green = ensureValidRange(green); this.blue = ensureValidRange(blue); } + + /** + * Constructs an RGB color from a string such as "#FF00FF" + */ + public RGBColor(String htmlColor) { + if (htmlColor.startsWith("#")) { + htmlColor = htmlColor.substring(1); + } + if (htmlColor.endsWith(";")) { + htmlColor = htmlColor.substring(0, htmlColor.length() - 1); + } + + int r, g, b; + switch (htmlColor.length()) { + case 6: + r = Integer.parseInt(htmlColor.substring(0, 2), 16); + g = Integer.parseInt(htmlColor.substring(2, 4), 16); + b = Integer.parseInt(htmlColor.substring(4, 6), 16); + break; + case 3: + r = Integer.parseInt(htmlColor.substring(0, 1), 16); + g = Integer.parseInt(htmlColor.substring(1, 2), 16); + b = Integer.parseInt(htmlColor.substring(2, 3), 16); + break; + case 1: + r = g = b = Integer.parseInt(htmlColor.substring(0, 1), 16); + break; + default: + throw new IllegalArgumentException(htmlColor); + } + this.red = ensureValidRange(r); + this.green = ensureValidRange(g); + this.blue = ensureValidRange(b); + } private int ensureValidRange(int value) { int result = value; @@ -78,19 +111,6 @@ public class RGBColor implements Color { return HSVColor; } - @Override - public String getAsHtml() { - return "#" + toBrowserHexValue(red) + toBrowserHexValue(green) + toBrowserHexValue(blue); - } - - private static String toBrowserHexValue(int number) { - StringBuilder builder = new StringBuilder(Integer.toHexString(number & 0xff)); - while (builder.length() < 2) { - builder.append("0"); - } - return builder.toString().toUpperCase(); - } - @Override public int hashCode() { final int prime = 31; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/FlagPole.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/FlagPole.java new file mode 100644 index 00000000000..6da59354803 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/FlagPole.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.common.racelog; + +public class FlagPole { + + private final Flags upperFlag; + private final Flags lowerFlag; + private final boolean isDisplayed; + + public FlagPole(Flags flag, boolean isDisplayed) { + this(flag, Flags.NONE, isDisplayed); + } + + public FlagPole(Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { + this.upperFlag = upperFlag; + this.lowerFlag = lowerFlag; + this.isDisplayed = isDisplayed; + } + + public Flags getUpperFlag() { + return upperFlag; + } + + public Flags getLowerFlag() { + return lowerFlag; + } + + public boolean isDisplayed() { + return isDisplayed; + } + + public boolean describesSame(FlagPole otherPole) { + return this.upperFlag == otherPole.upperFlag && + this.lowerFlag == otherPole.lowerFlag && + this.isDisplayed == otherPole.isDisplayed; + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/Flags.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/Flags.java index 3b2b0af452a..f4ca021f443 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/Flags.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/Flags.java @@ -1,5 +1,8 @@ package com.sap.sailing.domain.common.racelog; +import java.util.ArrayList; +import java.util.List; + public enum Flags { NONE, ALPHA, @@ -20,5 +23,15 @@ public enum Flags { AP, ESSONE, ESSTWO, - ESSTHREE + ESSTHREE; + + public static Flags[] validValues() { + List validValues = new ArrayList(); + for (Flags flag : values()) { + if (flag != NONE) { + validValues.add(flag); + } + } + return validValues.toArray(new Flags[validValues.size()]); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RacingProcedureType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RacingProcedureType.java new file mode 100644 index 00000000000..cae4906ed3e --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RacingProcedureType.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.common.racelog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Used to identify a RacingProcedure's type. + * + * When modifying these values also check res/preferences.xml of racecommittee.app! + */ +public enum RacingProcedureType { + UNKNOWN("Unknown"), + RRS26("Fix Line Start (RRS26)"), + GateStart("Gate Start"), + ESS("\"Extreme Sailing Series\"-Start"), + BASIC("Basic Countdown Start"); + + private String displayName; + + private RacingProcedureType(String displayName) { + this.displayName = displayName; + } + + @Override + public String toString() { + return displayName; + } + + public static RacingProcedureType[] validValues() { + List validValues = new ArrayList(); + for (RacingProcedureType type : values()) { + if (type != UNKNOWN) { + validValues.add(type); + } + } + return validValues.toArray(new RacingProcedureType[0]); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/StartProcedureType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/StartProcedureType.java deleted file mode 100644 index 0bd82f959ba..00000000000 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/StartProcedureType.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.sap.sailing.domain.common.racelog; - -public enum StartProcedureType { - RRS26("Fix Line Start (RRS26)"), - GateStart("Gate Start"), - ESS("\"Extreme Sailing Series\"-Start"); - - private String displayName; - - private StartProcedureType(String displayName) { - this.displayName = displayName; - } - - @Override - public String toString() { - return displayName; - } -} diff --git a/java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java index 79e4ce62821..5c7dad552b8 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter.persistence/src/com/sap/sailing/domain/igtimiadapter/persistence/impl/DomainObjectFactoryImpl.java @@ -2,7 +2,6 @@ package com.sap.sailing.domain.igtimiadapter.persistence.impl; import java.util.ArrayList; import java.util.List; - import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java index bd7e4cf02dd..1e2680d5498 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactoryTest.java @@ -58,20 +58,34 @@ public class FixFactoryTest { "},\n"+ "\"13:100\": {\n"+ "\"1\": [\n"+ - "60\n"+ + "23766\n"+ + "24619\n"+ + "],\n"+ + "\"2\": [\n"+ + "105\n"+ + "106\n"+ + "],\n"+ + "\"3\": [\n"+ + "72\n"+ + "72\n"+ "],\n"+ "\"t\": [\n"+ - "12345678000\n"+ + "1385628380500\n"+ + "1385628381000\n"+ "]\n"+ "},\n"+ "\"13:201\": {\n"+ "\"1\": [\n"+ - "119,\n"+ - "120\n"+ + "24619\n"+ + "],\n"+ + "\"2\": [\n"+ + "106\n"+ + "],\n"+ + "\"3\": [\n"+ + "119\n"+ "],\n"+ "\"t\": [\n"+ - "12345677000,\n"+ - "12345677500\n"+ + "1385628381000\n"+ "]\n"+ "}\n"+ "}\n"+ @@ -102,7 +116,7 @@ public class FixFactoryTest { assertEquals(21, satCount.getSatCount()); fixIter.next(); AntHrm hrm = (AntHrm) fixIter.next(); - assertEquals("100", hrm.getSensor().getDeviceId()); - assertEquals(60, hrm.getHeartRate()); + assertEquals(100, hrm.getSensor().getSensorId()); + assertEquals(72, hrm.getHeartRate()); } } diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java index 3bf070137ef..a24e0ae2782 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WebSocketServerListTest.java @@ -11,7 +11,6 @@ import org.json.simple.parser.ParseException; import org.junit.Test; import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory; import com.sap.sailing.mongodb.MongoDBConfiguration; import com.sap.sailing.mongodb.MongoDBService; @@ -23,7 +22,7 @@ public class WebSocketServerListTest { "aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"); MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration(); MongoDBService mongoTestService = mongoTestConfig.getService(); - final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), + final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService)); Iterable serverUris = igtimiConnectionFactory.getWebsocketServers(); assertFalse(Util.isEmpty(serverUris)); diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WindReceiverTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WindReceiverTest.java new file mode 100755 index 00000000000..9e35f6c3f84 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/impl/WindReceiverTest.java @@ -0,0 +1,69 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; +import com.sap.sailing.domain.igtimiadapter.datatypes.COG; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDG; +import com.sap.sailing.domain.igtimiadapter.datatypes.SOG; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindListener; + +public class WindReceiverTest { + @Test + public void simpleWindReceiverTest() { + final List windReceived = new ArrayList<>(); + final String deviceSerialNumber = "Non-Existing Test Device"; + IgtimiWindReceiver receiver = new IgtimiWindReceiver(deviceSerialNumber); + receiver.addListener(new WindListener() { + @Override + public void windDataReceived(Wind wind) { + windReceived.add(wind); + } + @Override + public void windDataRemoved(Wind wind) {} + @Override + public void windAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) {} + }); + TimePoint timePoint = MillisecondsTimePoint.now(); + Map awaMap = new HashMap<>(); awaMap.put(1, 123. /* degrees from */); + Map awsMap = new HashMap<>(); awsMap.put(1, 12. /* knots */); + Map hdgMap = new HashMap<>(); hdgMap.put(1, 20. /* true degrees */); + Map gpsLatLongMap = new HashMap<>(); gpsLatLongMap.put(2, 49.8 /* lat */); gpsLatLongMap.put(1, 8.93 /* lng */); + Map sogMap = new HashMap<>(); sogMap.put(1, 10. /* knots */); + Map cogMap = new HashMap<>(); cogMap.put(1, 22. /* degrees; 2 degrees drift */); + final SensorImpl sensor = new SensorImpl(deviceSerialNumber, 0); + SpeedWithBearing boatSogCog = new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(22)); + SpeedWithBearing apparentWind = new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(20).add(new DegreeBearingImpl(123).reverse())); + SpeedWithBearing expectedTrueWind = apparentWind.add(boatSogCog); + receiver.received(Arrays.asList(new AWA(timePoint, sensor, awaMap), + new AWS(timePoint, sensor, awsMap), + new HDG(timePoint, sensor, hdgMap), + new GpsLatLong(timePoint, sensor, gpsLatLongMap), + new SOG(timePoint, sensor, sogMap), + new COG(timePoint, sensor, cogMap))); + assertFalse(windReceived.isEmpty()); + assertEquals(1, windReceived.size()); + Wind wind = windReceived.get(0); + assertEquals(49.8, wind.getPosition().getLatDeg(), 0.00000001); + assertEquals(8.93, wind.getPosition().getLngDeg(), 0.00000001); + assertEquals(expectedTrueWind.getKnots(), wind.getKnots(), 0.00000001); + assertEquals(expectedTrueWind.getBearing().getDegrees(), wind.getBearing().getDegrees(), 0.00000001); + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/oauth/SignInTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/oauth/SignInTest.java index 014d7a45b8f..c159949e5ca 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/oauth/SignInTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/oauth/SignInTest.java @@ -3,12 +3,8 @@ package com.sap.sailing.domain.igtimiadapter.oauth; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; @@ -18,17 +14,9 @@ import org.json.simple.parser.ParseException; import org.junit.Test; import org.xml.sax.SAXException; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.Client; -import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; -import com.sap.sailing.domain.igtimiadapter.Permission; -import com.sap.sailing.domain.igtimiadapter.Resource; -import com.sap.sailing.domain.igtimiadapter.User; -import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; -import com.sap.sailing.domain.igtimiadapter.datatypes.Type; import com.sap.sailing.domain.igtimiadapter.impl.Activator; import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl; import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; @@ -48,7 +36,7 @@ public class SignInTest { "aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"); MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration(); MongoDBService mongoTestService = mongoTestConfig.getService(); - final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), + final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService)); final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456"); logger.info("Igtimi OAuth code is "+code); @@ -66,51 +54,35 @@ public class SignInTest { "4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4"); MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration(); MongoDBService mongoTestService = mongoTestConfig.getService(); - final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), + final IgtimiConnectionFactoryImpl igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService)); final String code = igtimiConnectionFactory.authorizeAndReturnAuthorizedCode("axel.uhl@gmx.de", "123456"); logger.info("Igtimi OAuth code is "+code); assertNotNull(code); } - + @Test + public void testAuthorize() throws ClientProtocolException, IOException, + IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException, + InstantiationException, IllegalAccessException, ClassCastException, ParseException { + // use the credentials of "Another Test App" + final Client testAppClient = new ClientImpl("a4cecd8593e12d43a03433a6db0eea243a411749f93c278dce6a26d4804eebd2", + "4d66022d1ec3e2991f8053514495b61cc076ff02d664f0dc8f3df9150c3864ef", "http://1.2.3.4"); + MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration(); + MongoDBService mongoTestService = mongoTestConfig.getService(); + final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(testAppClient, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), + PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService)); + final Account account = igtimiConnectionFactory.createAccountToAccessUserData("axel.uhl@gmx.de", "123456"); + assertNotNull(account); + logger.info("Igtimi account is "+account); + } + @Test public void testAddToken() throws ClientProtocolException, IllegalStateException, IOException, ParseException { final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); assertEquals("axel.uhl@gmx.de", account.getUser().getEmail()); - assertSame(account, connectionFactory.getAccountByEmail("axel.uhl@gmx.de")); + assertSame(account, connectionFactory.getExistingAccountByEmail("axel.uhl@gmx.de")); } - @Test - public void testGetUsers() throws ClientProtocolException, IllegalStateException, IOException, ParseException { - final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); - Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); - IgtimiConnection connection = connectionFactory.connect(account); - Iterable users = connection.getUsers(); - assertEquals(1, Util.size(users)); - assertEquals(account.getUser().getId(), users.iterator().next().getId()); - } - - @Test - public void testGetResources() throws ClientProtocolException, IllegalStateException, IOException, ParseException { - final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); - Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); - IgtimiConnection connection = connectionFactory.connect(account); - Iterable resources = connection.getResources(Permission.read, /* start time */ null, /* end time */ null, - /* serial numbers */ Collections.singleton("GA-EN-AAEJ"), /* stream IDs */ null); - assertTrue(resources.iterator().hasNext()); - } - - @Test - public void testGetResourceData() throws ClientProtocolException, IllegalStateException, IOException, ParseException { - final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); - Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); - IgtimiConnection connection = connectionFactory.connect(account); - Map typesAndCompression = new HashMap<>(); - typesAndCompression.put(Type.gps_latlong, 0.0); - Iterable data = connection.getResourceData(new MillisecondsTimePoint(1384420883000l), - new MillisecondsTimePoint(1384421639000l), Collections.singleton("GA-EN-AAEJ"), typesAndCompression); - assertTrue(data.iterator().hasNext()); - } } diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/AbstractTestWithIgtimiConnection.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/AbstractTestWithIgtimiConnection.java new file mode 100755 index 00000000000..b4518103c5b --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/AbstractTestWithIgtimiConnection.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.igtimiadapter.test; + +import java.io.IOException; + +import org.apache.http.client.ClientProtocolException; +import org.junit.Before; + +import com.sap.sailing.domain.igtimiadapter.Account; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; +import com.sap.sailing.domain.igtimiadapter.impl.Activator; + +public class AbstractTestWithIgtimiConnection { + protected IgtimiConnection connection; + + @Before + public void setUp() throws ClientProtocolException, IOException, org.json.simple.parser.ParseException { + final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); + Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); + connection = connectionFactory.connect(account); + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java new file mode 100755 index 00000000000..8dbedcc6036 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/BasicIgtimiAdapterTest.java @@ -0,0 +1,135 @@ +package com.sap.sailing.domain.igtimiadapter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.http.client.ClientProtocolException; +import org.json.simple.parser.ParseException; +import org.junit.Test; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.igtimiadapter.Account; +import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; +import com.sap.sailing.domain.igtimiadapter.Group; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; +import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.Resource; +import com.sap.sailing.domain.igtimiadapter.Session; +import com.sap.sailing.domain.igtimiadapter.User; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; +import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sailing.domain.igtimiadapter.impl.Activator; + +public class BasicIgtimiAdapterTest extends AbstractTestWithIgtimiConnection { + @Test + public void testGetUsers() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable users = connection.getUsers(); + assertEquals(1, Util.size(users)); + assertEquals(connection.getAccount().getUser().getId(), users.iterator().next().getId()); + } + + @Test + public void testGetGroups() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable groups = connection.getGroups(); + assertTrue(Util.size(groups) > 1); + } + + @Test + public void testGetDataAccessWindows() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable daws = connection.getDataAccessWindows(Permission.read, /* startTime */ null, + /* endTime */ null, /* deviceSerialNumbers */ Collections.singleton("DD-EE-AAGA")); + assertFalse(Util.isEmpty(daws)); + for (DataAccessWindow daw : daws) { + assertEquals("DD-EE-AAGA", daw.getDeviceSerialNumber()); + assertTrue(daw.getRecipient() instanceof Group); + } + } + + @Test + public void testGetAllDataAccessWindows() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable daws = connection.getDataAccessWindows(Permission.read, /* startTime */ null, + /* endTime */ null, /* deviceSerialNumbers */ null); + assertFalse(Util.isEmpty(daws)); + } + + @Test + public void testGetResources() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable resources = connection.getResources(Permission.read, /* start time */ null, /* end time */ null, + /* serial numbers */ Collections.singleton("GA-EN-AAEJ"), /* stream IDs */ null); + assertTrue(resources.iterator().hasNext()); + } + + @Test + public void testGetSessions() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Iterable sessions = connection.getSessions(Collections.singleton(4571l), /* isPublic */ null, /* limit */ 1, /* includeIncomplete */ null); + assertTrue(sessions.iterator().hasNext()); + final Session session = sessions.iterator().next(); + assertEquals("Auto Session - 01:14:02 10 Nov", session.getName()); + User sessionOwher = connection.getUser(session.getOwnerId()); + assertEquals("Uhl", sessionOwher.getSurname()); + } + + @Test + public void testGetResourceData() throws ClientProtocolException, IllegalStateException, IOException, ParseException { + Map typesAndCompression = new HashMap<>(); + typesAndCompression.put(Type.gps_latlong, 0.0); + Iterable data = connection.getResourceData(new MillisecondsTimePoint(1384420883000l), + new MillisecondsTimePoint(1384421639000l), Collections.singleton("GA-EN-AAEJ"), typesAndCompression); + assertTrue(data.iterator().hasNext()); + } + + @Test + public void testDataAccessWindowForGivenTimeFrame() throws java.text.ParseException, IllegalStateException, ClientProtocolException, IOException, ParseException { + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN); + TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-07T08:00:00Z")); + TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T18:00:00Z")); + // URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9 + Iterable daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null); + assertFalse(Util.isEmpty(daws)); + for (DataAccessWindow daw : daws) { + assertTrue((daw.getStartTime().compareTo(start)<=0 && daw.getEndTime().compareTo(start)>0) // spans start + || (daw.getStartTime().compareTo(end)<=0 && daw.getEndTime().compareTo(end)>0) // spans end + || (daw.getStartTime().compareTo(start)>=0 && daw.getEndTime().compareTo(end)<=0)); // lies within + } + } + + @Test + public void testResourceDataForGivenTimeFrame() throws java.text.ParseException, IllegalStateException, ClientProtocolException, IOException, ParseException { + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN); + TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:00:00Z")); + TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:10:00Z")); + final IgtimiConnectionFactory connectionFactory = Activator.getInstance().getConnectionFactory(); + Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); + IgtimiConnection connection = connectionFactory.connect(account); + // URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9 + Iterable daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null); + Set deviceSerialNumbers = new HashSet<>(); + for (DataAccessWindow daw : daws) { + deviceSerialNumbers.add(daw.getDeviceSerialNumber()); + } + Iterable windData = connection.getResourceData(start, end, deviceSerialNumbers, Type.gps_latlong, Type.AWA, Type.AWS, Type.HDG); + assertFalse(Util.isEmpty(windData)); + boolean foundWind = false; + for (Fix fix : windData) { + foundWind = foundWind || fix instanceof AWA || fix instanceof AWS; + assertTrue(fix.getTimePoint().compareTo(start) >= 0 && fix.getTimePoint().compareTo(end) <= 0); + } + assertTrue(foundWind); + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java new file mode 100755 index 00000000000..7a7de2cdddf --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/IgtimiFixTrackTest.java @@ -0,0 +1,75 @@ +package com.sap.sailing.domain.igtimiadapter.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.apache.http.client.ClientProtocolException; +import org.junit.Test; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; +import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; +import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sailing.domain.tracking.DynamicTrack; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.Wind; + +/** + * Igtimi fixes come as isolated single fixes from separate sensors, and even if they are attached to the same device, + * their time stamps may not be synchronized. Therefore, separate {@link Track} tracks will be used to hold the data + * coming from the different sensors attached to the same device, and several of them need to be joined to allow for the + * construction, e.g., of a single {@link Wind} of {@link GPSFixMoving} fix. + * + * @author Axel Uhl (d043530) + * + */ +public class IgtimiFixTrackTest extends AbstractTestWithIgtimiConnection { + @Test + public void testFetchFixesIntoTracks() throws ParseException, ClientProtocolException, IllegalStateException, IOException, org.json.simple.parser.ParseException { + setUp(); + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.GERMAN); + TimePoint start = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:00:00Z")); + TimePoint end = new MillisecondsTimePoint(dateFormat.parse("2013-11-09T07:10:00Z")); + // URL is https://www.igtimi.com/api/v1/devices/data_access_windows?type=read&start_time=1383811200000&end_time=1383933600000&access_token=3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9 + Iterable daws = connection.getDataAccessWindows(Permission.read, start, end, /* deviceSerialNumbers; get all devices available for that time */ null); + Set deviceSerialNumbers = new HashSet<>(); + for (DataAccessWindow daw : daws) { + deviceSerialNumbers.add(daw.getDeviceSerialNumber()); + } + Map>> data = connection.getResourceDataAsTracks(start, end, deviceSerialNumbers, Type.gps_latlong, Type.AWA, Type.AWS, Type.HDG, Type.HDGM); + assertFalse(data.isEmpty()); + Map> windSensorMap = data.get("DD-EE-AAHG"); + assertNotNull(windSensorMap); + assertTrue(windSensorMap.containsKey(Type.AWA)); + assertTrue(windSensorMap.containsKey(Type.AWS)); + assertTrue(windSensorMap.containsKey(Type.HDGM)); + final DynamicTrack awaTrack = windSensorMap.get(Type.AWA); + assertFalse(isEmpty(awaTrack)); + final DynamicTrack awsTrack = windSensorMap.get(Type.AWS); + assertFalse(isEmpty(awsTrack)); + final DynamicTrack hdgmTrack = windSensorMap.get(Type.HDGM); + assertFalse(isEmpty(hdgmTrack)); + } + + private boolean isEmpty(final DynamicTrack track) { + track.lockForRead(); + try { + return Util.isEmpty(track.getRawFixes()); + } finally { + track.unlockAfterRead(); + } + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java index a2d484e3321..108fe728882 100755 --- a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketTest.java @@ -28,9 +28,14 @@ import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.Test; +import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.igtimiadapter.Account; +import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; import com.sap.sailing.domain.igtimiadapter.Client; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; +import com.sap.sailing.domain.igtimiadapter.LiveDataConnection; +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.impl.ClientImpl; import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; import com.sap.sailing.domain.igtimiadapter.persistence.PersistenceFactory; @@ -142,6 +147,7 @@ public class WebSocketTest { @Test public void testWebSocketConnect() throws Exception { + final List allFixesReceived = new ArrayList<>(); final Client client = new ClientImpl("7fcdd217e0aa16090edb4ad55b09ec43b2021090e209541fc9b7003c2a2b70c6", "aa569cf4909bdc7b0e04b11873f3c4ea20687421e010fcc25b771cca9e6f3f9a", "http://127.0.0.1:8888/igtimi/oauth/v1/authorizationcallback"); MongoDBConfiguration mongoTestConfig = MongoDBConfiguration.getDefaultTestConfiguration(); @@ -149,9 +155,16 @@ public class WebSocketTest { final IgtimiConnectionFactory igtimiConnectionFactory = new IgtimiConnectionFactoryImpl(client, PersistenceFactory.INSTANCE.getDomainObjectFactory(mongoTestService), PersistenceFactory.INSTANCE.getMongoObjectFactory(mongoTestService)); Account account = igtimiConnectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); - WebSocketConnectionManager manager = new WebSocketConnectionManager(igtimiConnectionFactory, Collections.singleton("GA-EN-AAEJ"), account); - assertNotNull(manager); - assertTrue("Connection handshake not successful within 5s", manager.waitForConnection(5000)); - manager.disconnect(); + IgtimiConnection conn = igtimiConnectionFactory.connect(account); + LiveDataConnection liveDataConnection = conn.createLiveConnection(Collections.singleton("GA-EN-AAEJ")); + liveDataConnection.addListener(new BulkFixReceiver() { + @Override + public void received(Iterable fixes) { + Util.addAll(fixes, allFixesReceived); + } + }); + assertNotNull(liveDataConnection); + assertTrue("Connection handshake not successful within 5s", liveDataConnection.waitForConnection(5000l)); + liveDataConnection.disconnect(); } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF index b1677bb3cf3..1cd42e8f778 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF @@ -12,7 +12,10 @@ Require-Bundle: org.json.simple;bundle-version="1.1.0", org.apache.httpcomponents.httpcore;bundle-version="4.0.0", org.apache.commons.logging;bundle-version="1.1.1", com.sap.sailing.domain.common, - com.sap.sailing.domain.igtimiadapter.persistence + com.sap.sailing.domain.igtimiadapter.persistence, + com.sap.sailing.domain.shared.android, + com.sap.sailing.domain, + com.sap.sailing.declination Import-Package: javax.ws.rs;version="1.1.1", javax.ws.rs.core;version="1.1.1", javax.ws.rs.ext;version="1.1.1", @@ -27,3 +30,5 @@ Import-Package: javax.ws.rs;version="1.1.1", org.osgi.framework;version="1.7.0" Web-ContextPath: /igtimi Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator +Export-Package: com.sap.sailing.domain.igtimiadapter, + com.sap.sailing.domain.igtimiadapter.datatypes diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/BulkFixReceiver.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/BulkFixReceiver.java new file mode 100755 index 00000000000..abd172b7eca --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/BulkFixReceiver.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.igtimiadapter; + +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; + +/** + * Allows listeners to receive a batch of fixes that the connector received in one transaction. This will usually + * improve chances that sensor readings belonging together (such as an AWA and an AWS fix) will be contained in the + * same batch. + * + * @author Axel Uhl (d043530) + * + */ +public interface BulkFixReceiver { + void received(Iterable fixes); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/DataAccessWindow.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/DataAccessWindow.java new file mode 100755 index 00000000000..50144a25b49 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/DataAccessWindow.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.igtimiadapter; + +/** + * When a device is shared to you, all that happens is that a DAW (Data Access Window) is being created for the time + * interval that the device owner wants you to have access to the data from the device.

+ * + * So you’re not actually sharing a device per-se, rather you are granting access to data that it creates. This means + * that you don’t see shared devices in the devices::list call – that’s only for devices that you own at the instant in + * time the call is made. DAW’s allow granting access to data from a device for any interval in time; past, present or + * future.

+ * + * So if you need to get a complete list of serials that you can request data for, whether or not you are the owner, + * then you need to use the data_access_windows:: call.

+ * + * @author Axel Uhl (D043530) + * + */ +public interface DataAccessWindow extends HasStartAndEndTime, HasPermissions { + String getDeviceSerialNumber(); + + SecurityEntity getRecipient(); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Device.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Device.java new file mode 100755 index 00000000000..6c2eeec87c6 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Device.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.igtimiadapter; + +import java.io.IOException; + +import org.apache.http.client.ClientProtocolException; +import org.json.simple.parser.ParseException; + +public interface Device extends HasId { + /** + * @return a string identifying the device, such as "AA-AA-AAAA" + */ + String getSerialNumber(); + + String getServiceTag(); + + Long getOwnerId(); + + Long getDeviceUserGroupId(); + + Long getAdminDeviceUserGroupId(); + + Iterable getPermissions(); + + Boolean getBlob(); + + String getName(); + + User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException; +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/FixVisitor.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/FixVisitor.java new file mode 100755 index 00000000000..6d95146983d --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/FixVisitor.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.igtimiadapter; + +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; + +/** + * Notifies a {@link IgtimiFixReceiver receiver} about each fix received. + * + * @author Axel Uhl (d043530) + * + */ +public class FixVisitor implements BulkFixReceiver { + private final IgtimiFixReceiver receiver; + + public FixVisitor(IgtimiFixReceiver receiver) { + this.receiver = receiver; + } + + @Override + public void received(Iterable fixes) { + for (Fix fix : fixes) { + fix.notify(receiver); + } + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Group.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Group.java new file mode 100755 index 00000000000..319faba2c20 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Group.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.igtimiadapter; + +public interface Group extends SecurityEntity { + String getName(); + + Boolean isHidden(); + + Iterable getPermissions(); + + Boolean isBlob(); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasId.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasId.java new file mode 100755 index 00000000000..b8677c99fa2 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasId.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.igtimiadapter; + +public interface HasId { + long getId(); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasPermissions.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasPermissions.java new file mode 100755 index 00000000000..6cad087598a --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasPermissions.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.igtimiadapter; + +public interface HasPermissions { + Iterable getPermissions(); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasStartAndEndTime.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasStartAndEndTime.java new file mode 100755 index 00000000000..0d6d276a79d --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/HasStartAndEndTime.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.igtimiadapter; + +import com.sap.sailing.domain.common.TimePoint; + +public interface HasStartAndEndTime { + TimePoint getEndTime(); + + TimePoint getStartTime(); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java index 1a51438c41c..85fc359cd82 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnection.java @@ -9,6 +9,8 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sailing.domain.tracking.DynamicTrack; +import com.sap.sailing.domain.tracking.Track; /** * A connection to the Igtimi system for one {@link Client} and one {@link Account}. @@ -19,6 +21,8 @@ public interface IgtimiConnection { Iterable getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException; + User getUser(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException; + /** * @param startTime * optional; may be null @@ -36,9 +40,105 @@ public interface IgtimiConnection { /** * All arguments are mandatory. + * + * @param deviceSerialNumbers + * the serial numbers of the devices for which to return data; these numbers can be obtained, e.g., from + * {@link #getOwnedDevices()}.{@link Device#getSerialNumber() getSerialNumber()} or from + * {@link #getDataAccessWindows(Permission, TimePoint, TimePoint, Iterable)}. + * {@link DataAccessWindow#getDeviceSerialNumber() getDeviceSerialNumber()}. + * @param typeAndCompression + * for each data type to be obtained, tells the compression level; 0.0 is a good default, + * meaning "no compression". Compression is currently only supported for type {@link Type#gps_latlong} where + * the number provided represents a maximum error in degrees of latitude and longitude. */ - Iterable getResourceData(TimePoint startTime, TimePoint endTime, Iterable serialNumbers, + Iterable getResourceData(TimePoint startTime, TimePoint endTime, Iterable deviceSerialNumbers, Map typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + /** + * Shorthand for {@link #getResourceData(TimePoint, TimePoint, Iterable, Map)} where no compression is requested for + * any type. + */ + Iterable getResourceData(TimePoint startTime, TimePoint endTime, Iterable deviceSerialNumbers, + Type... types) throws IllegalStateException, ClientProtocolException, IOException, + ParseException; + /** + * Shorthand for {@link #getResourceData(TimePoint, TimePoint, Iterable, Map)} where no compression is requested for + * any type. The fixes received are forwarded to the {@link BulkFixReceiver} bulkFixReceiver in one call. + */ + Iterable getAndNotifyResourceData(TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers, BulkFixReceiver bulkFixReceiver, Type... types) + throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + + /** + * Same as {@link #getResourceData(TimePoint, TimePoint, Iterable, Type...)}, but the resulting {@link Fix}es are + * grouped into {@link Track}s per fix type and per device. + * + * @return a map whose keys are the devices' serial numbers and whose values are the fixes produced by the device + * identified by the key, grouped in a map with the fix {@link Type} as its key. Note that if a device + * didn't produce any fixes at all under the requested parameters, its serial number may not appear as a key + * in the map. Note further that should a device not have produced fixes of a given type, that type won't + * appear as a key in the map for that device. + */ + Map>> getResourceDataAsTracks(TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers, Type... types) throws IllegalStateException, ClientProtocolException, + IOException, ParseException; + + /** + * For the devices specified by deviceSerialNumbers, creates a live data connection. The account + * needs to be authorized to access the devices' data for the current time window. Fixes received through this connection + * are forwarded in the batches in which they are received to the listeners that can be added to the live connection + * using {@link LiveDataConnection#addListener(BulkFixReceiver)}. + * + * @return a connection that the caller can use to stop the live feed by calling {@link LiveDataConnection#disconnect()}. + */ + LiveDataConnection createLiveConnection(Iterable deviceSerialNumbers) throws Exception; + + /** + * @param sessionIds + * the optional IDs of the sessions for which to obtain the metadata; if null, all available + * sessions will be returned + * @param isPublic + * optional; if null or true, only public sessions will be included in the + * result + * @param limit + * optional; if not null, no more than this many session objects will be returned + * @param includeIncomplete + * optional; if not false, only completed sessions that have a start and an end time will be + * returned + */ + Iterable getSessions(Iterable sessionIds, Boolean isPublic, Integer limit, Boolean includeIncomplete) + throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + Session getSession(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + /** + * Returns the devices owned by the user to which the application client represented by this connection belongs. + */ + Iterable getOwnedDevices() throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + /** + * Returns all devices that this connection has access to with the requested permission. Note that + * these don't necessarily need to be devices owned by the user to which this connection belongs. The user only + * needs to have been authorized by the owner of the data to access the respective window of data. + * + * @param startTime + * optional; may be null. If provided, only data access windows whose time frame has a + * non-empty range after this time will be returned. + * @param endTime + * optional; may be null. If provided, only data access windows whose time frame has a + * non-empty range before this time will be returned. + * @param deviceSerialNumbers + * optional; if not null and not empty, only data access windows for the devices identified + * by these serial numbers will be returned + */ + Iterable getDataAccessWindows(Permission permission, TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers) throws IllegalStateException, ClientProtocolException, IOException, + ParseException; + + Iterable getGroups() throws IllegalStateException, ClientProtocolException, IOException, ParseException; + + Account getAccount(); } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java index cad4d8b2f9b..b2671968961 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java @@ -1,21 +1,13 @@ package com.sap.sailing.domain.igtimiadapter; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.json.simple.JSONObject; import org.json.simple.parser.ParseException; import org.xml.sax.SAXException; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.igtimiadapter.datatypes.Type; import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback; /** @@ -26,6 +18,10 @@ import com.sap.sailing.domain.igtimiadapter.oauth.AuthorizationCallback; * @author Axel Uhl (d043530) */ public interface IgtimiConnectionFactory { + Iterable getAllAccounts(); + + IgtimiConnection connect(Account account); + /** * Obtains a URL that a user agent (e.g., a web browser) can be sent to in order to allow that user to authenticate * and then authorize this factory's {@link Client} for accessing the user's Igtimi data. The URL is chosen such that @@ -47,56 +43,17 @@ public interface IgtimiConnectionFactory { * Matches eMail with the e-mail information retrieved from the "account" service earlier when an access * token was registered. */ - Account getAccountByEmail(String eMail); - - IgtimiConnection connect(Account account); + Account getExistingAccountByEmail(String eMail); /** - * Uses the /oauth/token service to obtain and {@link #registerAccountForWhichClientIsAuthorized(String) register} - * an access token for an authorization code which encodes the authorization given by a user to this factory's - * client. + * Tries to authorize our client on behalf of a user identified by e-mail and password. If successful, the + * account data and the relevant OAuth access token will be stored persistently. * - * @return the account encoding the application that is authorized for a user's account - * @throws RuntimeException in case there was an error while retrieving the token + * @return the account with which a caller can then {@link #connect obtain a connection} for the data that the user + * identified by userEmail and userPassword shares with our client. */ - Account obtainAccessTokenFromAuthorizationCode(String code) throws UnsupportedEncodingException, - ClientProtocolException, IOException, IllegalStateException, ParseException; - - String getAccountUrl(Account account); - - String getUsersUrl(Account account); - - String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime, - Iterable serialNumbers, Iterable streamIds, Account account); - - String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable serialNumbers, - Map typeAndCompression, Account account); - - Iterable getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException; - - HttpClient getHttpClient(); - - /** - * Retrieves the JSON object to send in its string-serialized form to a web socket connection in order to receive - * live data from the units whose IDs are specified by unitIds. The sending units are expected to - * belong to the user account to which this factory's application client has been granted permission. - * - * @param account - * represents this factory's client's permissions to access a user account's data - * @param deviceIds - * IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's} - */ - JSONObject getWebSocketConfigurationMessage(Account account, Iterable deviceIds); - - Iterable getAllAccounts(); - - /** - * Tries to authorize our client on behalf of a user identified by e-mail and password. - * - * @return the authorization code which can then be used to obtain a permanent access token to be used by our client - * to access data owned by the user identified by e-mail and password. - */ - String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword) throws ClientProtocolException, + Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException, IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException, - InstantiationException, IllegalAccessException, ClassCastException; + InstantiationException, IllegalAccessException, ClassCastException, ParseException; + } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiver.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiver.java new file mode 100755 index 00000000000..386146250f6 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiver.java @@ -0,0 +1,43 @@ +package com.sap.sailing.domain.igtimiadapter; + +import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; +import com.sap.sailing.domain.igtimiadapter.datatypes.AntCbst; +import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm; +import com.sap.sailing.domain.igtimiadapter.datatypes.BatteryLevel; +import com.sap.sailing.domain.igtimiadapter.datatypes.COG; +import com.sap.sailing.domain.igtimiadapter.datatypes.File; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsAltitude; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityHdop; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityIndicator; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDG; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM; +import com.sap.sailing.domain.igtimiadapter.datatypes.SOG; +import com.sap.sailing.domain.igtimiadapter.datatypes.STW; + +/** + * Callback interface for Igtimi fix receivers. + * + * @author Axel Uhl (d043530) + * + */ +public interface IgtimiFixReceiver { + void received(AntCbst fix); + void received(AntHrm fix); + void received(AWA fix); + void received(AWS fix); + void received(BatteryLevel fix); + void received(COG fix); + void received(File fix); + void received(GpsAltitude fix); + void received(GpsLatLong fix); + void received(GpsQualityHdop fix); + void received(GpsQualityIndicator fix); + void received(GpsQualitySatCount fix); + void received(HDG fix); + void received(HDGM fix); + void received(SOG fix); + void received(STW fix); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiverAdapter.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiverAdapter.java new file mode 100755 index 00000000000..4248d5d5ab6 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiFixReceiverAdapter.java @@ -0,0 +1,85 @@ +package com.sap.sailing.domain.igtimiadapter; + +import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; +import com.sap.sailing.domain.igtimiadapter.datatypes.AntCbst; +import com.sap.sailing.domain.igtimiadapter.datatypes.AntHrm; +import com.sap.sailing.domain.igtimiadapter.datatypes.BatteryLevel; +import com.sap.sailing.domain.igtimiadapter.datatypes.COG; +import com.sap.sailing.domain.igtimiadapter.datatypes.File; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsAltitude; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityHdop; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualityIndicator; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsQualitySatCount; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDG; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM; +import com.sap.sailing.domain.igtimiadapter.datatypes.SOG; +import com.sap.sailing.domain.igtimiadapter.datatypes.STW; + +public class IgtimiFixReceiverAdapter implements IgtimiFixReceiver { + @Override + public void received(AntCbst fix) { + } + + @Override + public void received(AntHrm fix) { + } + + @Override + public void received(AWA fix) { + } + + @Override + public void received(AWS fix) { + } + + @Override + public void received(BatteryLevel fix) { + } + + @Override + public void received(COG fix) { + } + + @Override + public void received(File fix) { + } + + @Override + public void received(GpsAltitude fix) { + } + + @Override + public void received(GpsLatLong fix) { + } + + @Override + public void received(GpsQualityHdop fix) { + } + + @Override + public void received(GpsQualityIndicator fix) { + } + + @Override + public void received(GpsQualitySatCount fix) { + } + + @Override + public void received(HDG fix) { + } + + @Override + public void received(HDGM fix) { + } + + @Override + public void received(SOG fix) { + } + + @Override + public void received(STW fix) { + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataConnection.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataConnection.java new file mode 100755 index 00000000000..225165d8b62 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataConnection.java @@ -0,0 +1,19 @@ +package com.sap.sailing.domain.igtimiadapter; + +public interface LiveDataConnection { + /** + * Disconnects this connection. Afterwards, the connection will no longer feed live data to its listener(s). + */ + void disconnect() throws Exception; + + /** + * Waits for a successful connection for timeoutInMillis milliseconds. If the connection is successful + * within this time, true is returned; false otherwise. + * + * @param timeoutInMillis + * use 0 to wait indefinitely + */ + boolean waitForConnection(long timeoutInMillis) throws InterruptedException; + + void addListener(BulkFixReceiver listener); +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataListener.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataListener.java deleted file mode 100755 index 6840a7f5cff..00000000000 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/LiveDataListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.sap.sailing.domain.igtimiadapter; - -import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; - -public interface LiveDataListener { - void fixesReceived(Iterable fixes); -} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Resource.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Resource.java index c2318023818..e8c74c0427d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Resource.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Resource.java @@ -1,21 +1,12 @@ package com.sap.sailing.domain.igtimiadapter; -import com.sap.sailing.domain.common.TimePoint; -public interface Resource { +public interface Resource extends HasId, HasPermissions, HasStartAndEndTime { - long getId(); + boolean isBlob(); - public abstract boolean isBlob(); + int[] getDataTypes(); - public abstract Iterable getPermissions(); - - public abstract int[] getDataTypes(); - - public abstract String getDeviceSerialNumber(); - - public abstract TimePoint getEndTime(); - - public abstract TimePoint getStartTime(); + String getDeviceSerialNumber(); } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/SecurityEntity.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/SecurityEntity.java new file mode 100755 index 00000000000..9acc7d904d0 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/SecurityEntity.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.igtimiadapter; + +/** + * Some entity to be authorized to access some data, such as a user or a group. + * + * @author Axel Uhl (d043530) + * + */ +public interface SecurityEntity extends HasId { + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Sensor.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Sensor.java index 146bb42cfb2..8b76dae97d6 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Sensor.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Sensor.java @@ -2,11 +2,16 @@ package com.sap.sailing.domain.igtimiadapter; public interface Sensor { /** - * The ID called "sensor ID" by Igtimi. This identifies the microcontroller-like device that can mange several sensors - * such as wind, GPS or heart rate. Together with the type code and the optional {@link #getDeviceId() device ID} as used + * The ID called "serial number" by Igtimi. This identifies the microcontroller-like device that can mange several sensors + * such as wind, GPS or heart rate. Together with the type code and the optional {@link #getSensorId() sub-device ID} as used * for heart rate sensors the individual sensor is uniquely defined. */ - String getTransmitterId(); + String getDeviceSerialNumber(); - String getDeviceId(); + /** + * By default, the sensor ID is 0, particularly if it represents a sensor built into the device, such as a built-in GPS. + * In case of multiple sensors of the same kind, the non-zero ID will then help to keep the individual sensors attached + * to the same device apart. Example: multiple heart rate sensors. + */ + long getSensorId(); } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Session.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Session.java new file mode 100755 index 00000000000..9dfc5612599 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/Session.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.igtimiadapter; + +import java.io.IOException; + +import org.apache.http.client.ClientProtocolException; +import org.json.simple.parser.ParseException; + +/** + * Sessions are a container that gives meaning and structure to a group of {@link Device devices} that are brought + * together at a particular time for a race etc. It has a few direct properties such as {@link #getName() name}, + * start+end time. Then it has the session logs which hold all the detail about what’s in the session, such as yachts, + * buoys, devices, names and colors. Sessions are shared by adding a {@link User user} into the session's {@link Group} + * identified by the {@link #getSessionGroupId() session group id}. + *

+ * + * Sessions handle all the magic to ensure that {@link #getPermissions() permissions} to access device data ( + * {@link DataAccessWindow)s} are handled when group membership changes, and as devices are added or removed to the + * session via the logs. So once you are familiar with using them it takes away all the load of managing users, + * permissions, and access to data. Without sessions, to give you access to an event's data would have meant manually + * making a list of devices used in the session and creating a bunch of {@link DataAccessWindow}s for them. + * + * @author Axel Uhl (d043530) + * + */ +public interface Session extends HasId, HasPermissions, HasStartAndEndTime { + boolean isBlob(); + + long getAdminSessionGroupId(); + + long getSessionGroupId(); + + long getOwnerId(); + + String getName(); + + User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException; +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/User.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/User.java index 6354e589537..353f1439e62 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/User.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/User.java @@ -1,8 +1,6 @@ package com.sap.sailing.domain.igtimiadapter; -public interface User { - long getId(); - +public interface User extends SecurityEntity { String getFirstName(); String getSurname(); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWA.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWA.java index 4ac9cb98c72..37da04adaf7 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWA.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWA.java @@ -5,15 +5,18 @@ import java.util.Map; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** - * Apparent wind angle, relative to the device's or vessel's orientation + * Apparent wind angle, relative to the device's or vessel's orientation, in degrees from 0 to 360, with values 180-360 + * meaning the port side, and values 0-180 meaning starboard side. * * @author Axel Uhl (d043530) * */ public class AWA extends Fix { + private static final long serialVersionUID = -5979975741914782196L; private final Bearing apparentWindAngle; public AWA(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -29,4 +32,9 @@ public class AWA extends Fix { protected String localToString() { return "AWA: "+getApparentWindAngle(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWS.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWS.java index bf4674047d5..176c2445e8f 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWS.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AWS.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class AWS extends Fix { + private static final long serialVersionUID = -4044229400122127582L; private final Speed apparentWindSpeed; public AWS(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -29,4 +31,9 @@ public class AWS extends Fix { protected String localToString() { return "AWS: "+getApparentWindSpeed(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntCbst.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntCbst.java index 9547d7a93d1..1d7e41c626f 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntCbst.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntCbst.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -12,6 +13,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class AntCbst extends Fix { + private static final long serialVersionUID = 7696345613317022955L; private final int cadence; public AntCbst(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -27,4 +29,9 @@ public class AntCbst extends Fix { public String localToString() { return "Cadence "+getCadence(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntHrm.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntHrm.java index ff9ea0bfae5..2feadfc9193 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntHrm.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/AntHrm.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -12,11 +13,24 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class AntHrm extends Fix { + private static final long serialVersionUID = 94260324289901500L; + private final int eventTime; + private final int heartBeatCount; private final int heartRate; public AntHrm(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { super(sensor, timePoint); - heartRate = ((Number) valuesPerSubindex.get(1)).intValue(); + eventTime = ((Number) valuesPerSubindex.get(1)).intValue(); + heartBeatCount = ((Number) valuesPerSubindex.get(2)).intValue(); + heartRate = ((Number) valuesPerSubindex.get(3)).intValue(); + } + + public int getEventTime() { + return eventTime; + } + + public int getHeartBeatCount() { + return heartBeatCount; } public int getHeartRate() { @@ -27,4 +41,9 @@ public class AntHrm extends Fix { protected String localToString() { return "heart rate "+getHeartRate()+"/min"; } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/BatteryLevel.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/BatteryLevel.java index 54ed09e509e..2aea3bb590a 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/BatteryLevel.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/BatteryLevel.java @@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class BatteryLevel extends Fix { + private static final long serialVersionUID = -8499201867081319088L; private final double percentage; public BatteryLevel(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -21,4 +23,9 @@ public class BatteryLevel extends Fix { protected String localToString() { return "Battery: "+getPercentage()+"%"; } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/COG.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/COG.java index 15e24a2743b..8227d5af21a 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/COG.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/COG.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class COG extends Fix { + private static final long serialVersionUID = 1105936972187917331L; private final Bearing courseOverGround; public COG(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -29,4 +31,9 @@ public class COG extends Fix { protected String localToString() { return "COG: "+getCourseOverGround(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/File.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/File.java index b60752dce97..b28d39dc5a5 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/File.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/File.java @@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class File extends Fix { + private static final long serialVersionUID = 7580174784400926003L; private final TimePoint start; private final TimePoint end; private final String fileName; @@ -51,4 +53,9 @@ public class File extends Fix { protected String localToString() { return "File: "+getFileName()+", MD5: "+getMd5()+" content type "+getContentType()+", "+getSize()+" bytes"; } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Fix.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Fix.java index 165ac1b5f5e..57e582e9a7d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Fix.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Fix.java @@ -1,9 +1,12 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; +import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; -public abstract class Fix { +public abstract class Fix implements Timed { + private static final long serialVersionUID = -486253194440558670L; private final TimePoint timePoint; private final Sensor sensor; @@ -12,7 +15,12 @@ public abstract class Fix { this.timePoint = timePoint; this.sensor = sensor; } + + public Type getType() { + return Type.getType(this.getClass()); + } + @Override public TimePoint getTimePoint() { return timePoint; } @@ -23,6 +31,8 @@ public abstract class Fix { abstract protected String localToString(); + abstract public void notify(IgtimiFixReceiver receiver); + @Override public String toString() { return localToString() + " at "+getTimePoint()+" from "+getSensor(); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsAltitude.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsAltitude.java index a3e302f7748..5ae71955afd 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsAltitude.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsAltitude.java @@ -5,9 +5,11 @@ import java.util.Map; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class GpsAltitude extends Fix { + private static final long serialVersionUID = -5740764665236002412L; private final Distance altitude; public GpsAltitude(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -23,4 +25,9 @@ public class GpsAltitude extends Fix { protected String localToString() { return "Altitude "+getAltitude(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsLatLong.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsLatLong.java index 325c81c96dc..f3d59b4b3ab 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsLatLong.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsLatLong.java @@ -5,9 +5,11 @@ import java.util.Map; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class GpsLatLong extends Fix { + private static final long serialVersionUID = 5056284867725893553L; private final Position position; public GpsLatLong(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -23,4 +25,9 @@ public class GpsLatLong extends Fix { protected String localToString() { return position.toString(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityHdop.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityHdop.java index ed0604a3f0a..2c14ac6687d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityHdop.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityHdop.java @@ -5,9 +5,11 @@ import java.util.Map; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class GpsQualityHdop extends Fix { + private static final long serialVersionUID = -5319766127405258823L; private final Distance hdop; public GpsQualityHdop(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -23,4 +25,9 @@ public class GpsQualityHdop extends Fix { protected String localToString() { return "HDOP: "+getHdop(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityIndicator.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityIndicator.java index 7fa40771aa9..e6e722b2f11 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityIndicator.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualityIndicator.java @@ -3,9 +3,11 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; public class GpsQualityIndicator extends Fix { + private static final long serialVersionUID = 1112428061064399194L; private final int quality; public GpsQualityIndicator(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -21,4 +23,9 @@ public class GpsQualityIndicator extends Fix { protected String localToString() { return "GPS Quality: "+getQuality(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualitySatCount.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualitySatCount.java index 6540bd6dfc9..3c1c85fb5ad 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualitySatCount.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/GpsQualitySatCount.java @@ -3,9 +3,32 @@ package com.sap.sailing.domain.igtimiadapter.datatypes; import java.util.Map; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; +/** + * For YachtBot and WindBot devices, this lists the number of satellites the device currently uses to determine its + * position. For the YachtBot smartphone app, restrictions may apply, as per the following e-mail by Brent Russell, + * Igtimi CTO, sent at 2013-12-01T21:16:00Z: + * + *

+ * You're correct, we're not reading or propagating it at the moment from mobile devices, although it works fine with YachtBot hardware. 
+ * The mobile platform is what we're giving away for free, so we're not trying to make it a direct feature match for YachtBot :)
+ * 
+ * Both mobile and YachtBot only Tx GPS coordinates when there is a valid fix, so in practice I think you should never see a zeroed
+ * SatQ or SatC that is of any true meaning anyway.  Best just to ignore both these types (or throw away if zero) and not request it
+ * with the ::data call.
+ * 
+ * We do have some plans to change the way fix quality information is transmitted.  Mostly it's so that changes only are transmitted,
+ * and we'll encourage people to use hdop or gdop rather than satellite count.  There's some changes we need to make under the hood
+ * first though, so not a near future thing.
+ * 
+ * + * @author Axel Uhl (D043530) + * + */ public class GpsQualitySatCount extends Fix { + private static final long serialVersionUID = -5507100027164944068L; private final int satCount; public GpsQualitySatCount(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -21,4 +44,9 @@ public class GpsQualitySatCount extends Fix { protected String localToString() { return ""+getSatCount()+" satellites"; } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDG.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDG.java index 129a67cd701..953d40a93a4 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDG.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDG.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,11 +15,12 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class HDG extends Fix { + private static final long serialVersionUID = -2400420107580758551L; private final Bearing trueHeading; public HDG(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { super(sensor, timePoint); - trueHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue()); + trueHeading = new DegreeBearingImpl((((Number) valuesPerSubindex.get(1)).doubleValue()+360.)%360.); } public Bearing getTrueHeading() { @@ -29,4 +31,9 @@ public class HDG extends Fix { protected String localToString() { return "HDG: "+getTrueHeading(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDGM.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDGM.java index 397e0686ef1..b739c001c66 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDGM.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/HDGM.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,11 +15,12 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class HDGM extends Fix { + private static final long serialVersionUID = -3344091983385126284L; private final Bearing magnetigHeading; public HDGM(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { super(sensor, timePoint); - magnetigHeading = new DegreeBearingImpl(((Number) valuesPerSubindex.get(1)).doubleValue()); + magnetigHeading = new DegreeBearingImpl((((Number) valuesPerSubindex.get(1)).doubleValue()+360.)%360.); } public Bearing getMagnetigHeading() { @@ -29,4 +31,9 @@ public class HDGM extends Fix { protected String localToString() { return "HDGM: "+getMagnetigHeading(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/SOG.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/SOG.java index ac98bbb9ca0..a93f9e75871 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/SOG.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/SOG.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class SOG extends Fix { + private static final long serialVersionUID = 6926121760778539823L; private final Speed speedOverGround; public SOG(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -29,4 +31,9 @@ public class SOG extends Fix { protected String localToString() { return "SOG: "+getSpeedOverGround(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/STW.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/STW.java index 5e3f8637867..3fc22de8eb5 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/STW.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/STW.java @@ -5,6 +5,7 @@ import java.util.Map; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiver; import com.sap.sailing.domain.igtimiadapter.Sensor; /** @@ -14,6 +15,7 @@ import com.sap.sailing.domain.igtimiadapter.Sensor; * */ public class STW extends Fix { + private static final long serialVersionUID = -7854740389524286036L; private final Speed speedThroughWater; public STW(TimePoint timePoint, Sensor sensor, Map valuesPerSubindex) { @@ -29,4 +31,9 @@ public class STW extends Fix { protected String localToString() { return "STW: "+getSpeedThroughWater(); } + + @Override + public void notify(IgtimiFixReceiver receiver) { + receiver.received(this); + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Type.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Type.java index 82b1ddbe313..92465d86ee5 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Type.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/datatypes/Type.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.Map; public enum Type { - gps_latlong(1, GpsLatLong.class), gps_quality_indicator(2, GpsQualityIndicator.class), gps_quality_sat_count(3, GpsQualitySatCount.class), @@ -37,15 +36,21 @@ public enum Type { private final int code; private final Class fixClass; private final static Map typeByCode = new HashMap<>(); + private final static Map, Type> typeByClass = new HashMap<>(); private Type(int code, Class fixClass) { this.code = code; this.fixClass = fixClass; } + public static Type getType(Class fixClass) { + return typeByClass.get(fixClass); + } + static { for (Type type : Type.values()) { typeByCode.put(type.getCode(), type); + typeByClass.put(type.getFixClass(), type); } } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java index 8cf9f0c81a3..141b515565d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java @@ -33,7 +33,7 @@ public class Activator implements BundleActivator { private static final String CLIENT_ID_PROPERTY_NAME = "igtimi.client.id"; private static final String CLIENT_SECRET_PROPERTY_NAME = "igtimi.client.secret"; private static final String CLIENT_REDIRECT_URI_PROPERTY_NAME = "igtimi.client.redirecturi"; - private IgtimiConnectionFactory connectionFactory; + private IgtimiConnectionFactoryImpl connectionFactory; public Activator() throws ClientProtocolException, IllegalStateException, IOException, ParseException { final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME, DEFAULT_CLIENT_ID); @@ -58,7 +58,7 @@ public class Activator implements BundleActivator { return INSTANCE; } - public IgtimiConnectionFactory getConnectionFactory() { + public IgtimiConnectionFactoryImpl getConnectionFactory() { return connectionFactory; } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowDeserializer.java new file mode 100755 index 00000000000..1b36eadb4d7 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowDeserializer.java @@ -0,0 +1,34 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import java.util.logging.Logger; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.SecurityEntity; + +public class DataAccessWindowDeserializer extends HasPermissionsDeserializer { + private static final Logger logger = Logger.getLogger(DataAccessWindowDeserializer.class.getName()); + + public DataAccessWindow createDataAccessWindowFromJson(JSONObject resourceJson, IgtimiConnection conn) { + return new DataAccessWindowImpl((Long) resourceJson.get("id"), + new MillisecondsTimePoint(((Double) resourceJson.get("start_time")).longValue()), + new MillisecondsTimePoint(((Double) resourceJson.get("end_time")).longValue()), + (String) resourceJson.get("device_serial_number"), + getPermissions((JSONObject) resourceJson.get("permissions")), + getSecurityEntity((JSONObject) resourceJson.get("recipient"))); + } + + private SecurityEntity getSecurityEntity(JSONObject securityEntity) { + if (securityEntity.containsKey("user")) { + return new UserDeserializer().createUserFromJson((JSONObject) securityEntity.get("user")); + } else if (securityEntity.containsKey("group")) { + return new GroupDeserializer().createGroupFromJson((JSONObject) securityEntity.get("group")); + } else { + logger.warning("Don't know how to deserialize a serucity entity from "+securityEntity); + return null; + } + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowImpl.java new file mode 100755 index 00000000000..9d174d745f0 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DataAccessWindowImpl.java @@ -0,0 +1,54 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; +import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.SecurityEntity; + +public class DataAccessWindowImpl extends HasIdImpl implements DataAccessWindow { + private final TimePoint startTime; + private final TimePoint endTime; + private final String deviceSerialNumber; + private final Iterable permissions; + private final SecurityEntity recipient; + + public DataAccessWindowImpl(long id, TimePoint startTime, TimePoint endTime, String deviceSerialNumber, + Iterable permissions, SecurityEntity recipient) { + super(id); + this.startTime = startTime; + this.endTime = endTime; + this.deviceSerialNumber = deviceSerialNumber; + this.permissions = permissions; + this.recipient = recipient; + } + + @Override + public TimePoint getStartTime() { + return startTime; + } + + @Override + public TimePoint getEndTime() { + return endTime; + } + + @Override + public String getDeviceSerialNumber() { + return deviceSerialNumber; + } + + @Override + public Iterable getPermissions() { + return permissions; + } + + @Override + public SecurityEntity getRecipient() { + return recipient; + } + + @Override + public String toString() { + return "DAW "+getId()+" for device "+getDeviceSerialNumber()+" from "+getStartTime()+" to "+getEndTime()+", permissions "+getPermissions(); + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceDeserializer.java new file mode 100755 index 00000000000..0fa18921007 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceDeserializer.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.igtimiadapter.Device; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; + +public class DeviceDeserializer extends HasPermissionsDeserializer { + public Device createResourceFromJson(JSONObject sessionJson, IgtimiConnection conn) { + Boolean blob = (Boolean) sessionJson.get("blob"); + return new DeviceImpl((Long) sessionJson.get("id"), (String) sessionJson.get("serial_number"), (String) sessionJson.get("name"), + (String) sessionJson.get("service_tag"), (Long) sessionJson.get("owner_id"), + (Long) sessionJson.get("device_user_group_id"), (Long) sessionJson.get("admin_device_user_group_id"), + getPermissions((JSONObject) sessionJson.get("permissions")), + blob == null ? false : blob, conn); + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceImpl.java new file mode 100755 index 00000000000..e8f88c1d1c5 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/DeviceImpl.java @@ -0,0 +1,88 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import java.io.IOException; + +import org.apache.http.client.ClientProtocolException; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.igtimiadapter.Device; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.User; + +public class DeviceImpl extends HasIdImpl implements Device { + private final String serialNumber; + private final String name; + private String serviceTag; + private Long ownerId; + private Long deviceUserGroupId; + private Long adminDeviceUserGroupId; + private Iterable permissions; + private Boolean blob; + private final IgtimiConnection conn; + + protected DeviceImpl(long id, String serialNumber, IgtimiConnection conn) { + this(id, serialNumber, /* name */ null, /* serviceTag */ null, /* ownerId */ null, /* deviceUserGroupId */ null, + /* adminDeviceUserGroupId */ null, /* permissions */ null, /* blob */ null, conn); + } + + public DeviceImpl(Long id, String serialNumber, String name, String serviceTag, Long ownerId, Long deviceUserGroupId, + Long adminDeviceUserGroupId, Iterable permissions, Boolean blob, IgtimiConnection conn) { + super(id); + this.serialNumber = serialNumber; + this.name = name; + this.serviceTag = serialNumber; + this.ownerId = ownerId; + this.deviceUserGroupId = deviceUserGroupId; + this.adminDeviceUserGroupId = adminDeviceUserGroupId; + this.permissions = permissions; + this.blob = blob; + this.conn = conn; + } + + @Override + public String getSerialNumber() { + return serialNumber; + } + + @Override + public String getServiceTag() { + return serviceTag; + } + + @Override + public Long getOwnerId() { + return ownerId; + } + + @Override + public Long getDeviceUserGroupId() { + return deviceUserGroupId; + } + + @Override + public Long getAdminDeviceUserGroupId() { + return adminDeviceUserGroupId; + } + + @Override + public User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException { + return conn.getUser(getOwnerId()); + } + + @Override + public Iterable getPermissions() { + return permissions; + } + + @Override + public Boolean getBlob() { + return blob; + } + + @Override + public String getName() { + return name; + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java index bb280c28022..edeba7e8d3c 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java @@ -25,7 +25,7 @@ public class FixFactory { public Iterable createFixes(JSONObject sensorsJson) { List result = new ArrayList<>(); for (Entry e : sensorsJson.entrySet()) { - String sensorId = (String) e.getKey(); + String deviceSerialNumber = (String) e.getKey(); JSONObject typesJson = (JSONObject) e.getValue(); for (Entry fixTypeAndFixesJson : typesJson.entrySet()) { final String[] fixTypeAndOptionalColonSeparatedSensorsSubId = ((String) fixTypeAndFixesJson.getKey()).split(":"); @@ -42,7 +42,9 @@ public class FixFactory { valuesPerSubindex.put(i, (Number) values.get(fixIndex)); i++; } - Sensor sensor = new SensorImpl(sensorId, fixTypeAndOptionalColonSeparatedSensorsSubId.length<2?null:fixTypeAndOptionalColonSeparatedSensorsSubId[1]); + Sensor sensor = new SensorImpl(deviceSerialNumber, + fixTypeAndOptionalColonSeparatedSensorsSubId.length < 2 ? 0 + : Long.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[1])); Fix fix = createFix(sensor, Type.valueOf(fixType), timePoint, valuesPerSubindex); result.add(fix); fixIndex++; diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupDeserializer.java new file mode 100755 index 00000000000..01111748238 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupDeserializer.java @@ -0,0 +1,18 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.igtimiadapter.Group; + +public class GroupDeserializer extends HasPermissionsDeserializer { + public Group createGroupFromJson(JSONObject groupJson) { + Boolean blob = (Boolean) groupJson.get("blob"); + final Long id = (Long) groupJson.get("id"); + return new GroupImpl(id, + (String) groupJson.get("name"), + getPermissions((JSONObject) groupJson.get("permissions")), + (Boolean) groupJson.get("hidden"), + blob == null ? false : blob); + } + +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupImpl.java new file mode 100755 index 00000000000..b5da35ee4b3 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/GroupImpl.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import com.sap.sailing.domain.igtimiadapter.Group; +import com.sap.sailing.domain.igtimiadapter.Permission; + +public class GroupImpl extends HasIdImpl implements Group { + private final String name; + private final Iterable permissions; + private final Boolean hidden; + private final Boolean blob; + + public GroupImpl(long id, String name, Iterable permissions, Boolean hidden, Boolean blob) { + super(id); + this.name = name; + this.permissions = permissions; + this.hidden = hidden; + this.blob = blob; + } + + @Override + public String getName() { + return name; + } + + @Override + public Iterable getPermissions() { + return permissions; + } + + @Override + public Boolean isHidden() { + return hidden; + } + + @Override + public Boolean isBlob() { + return blob; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasIdImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasIdImpl.java new file mode 100755 index 00000000000..8f35d27f3c4 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasIdImpl.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import com.sap.sailing.domain.igtimiadapter.HasId; + +public class HasIdImpl implements HasId { + private final long id; + + protected HasIdImpl(long id) { + super(); + this.id = id; + } + + @Override + public long getId() { + return id; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasPermissionsDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasPermissionsDeserializer.java new file mode 100755 index 00000000000..6b53a60f83e --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/HasPermissionsDeserializer.java @@ -0,0 +1,26 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.igtimiadapter.Permission; + +public abstract class HasPermissionsDeserializer { + protected Iterable getPermissions(JSONObject permissions) { + final List result; + if (permissions == null) { + result = null; + } else { + result = new ArrayList<>(); + for (Entry e : permissions.entrySet()) { + if ((Boolean) e.getValue()) { + result.add(Permission.valueOf((String) e.getKey())); + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java index 000b5e3271b..a5fe7a732d2 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java @@ -120,7 +120,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { } @Override - public Account getAccountByEmail(String eMail) { + public Account getExistingAccountByEmail(String eMail) { return accountsByEmail.get(eMail); } @@ -138,12 +138,20 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return accessTokensByAccount.get(account); } - @Override public String getAccountUrl(Account account) { return getApiV1BaseUrl()+"account?"+getAccessTokenUrlParameter(account); } - @Override + /** + * Retrieves the JSON object to send in its string-serialized form to a web socket connection in order to receive + * live data from the units whose IDs are specified by unitIds. The sending units are expected to + * belong to the user account to which this factory's application client has been granted permission. + * + * @param account + * represents this factory's client's permissions to access a user account's data + * @param deviceIds + * IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's} + */ public JSONObject getWebSocketConfigurationMessage(Account account, Iterable deviceIds) { JSONObject result = new JSONObject(); result.put("access_token", getAccessTokenForAccount(account)); @@ -155,12 +163,54 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return result; } - @Override public String getUsersUrl(Account account) { return getApiV1BaseUrl()+"users?"+getAccessTokenUrlParameter(account); } - @Override + public String getUserUrl(long id, Account account) { + return getApiV1BaseUrl()+"users/"+id+"?"+getAccessTokenUrlParameter(account); + } + + public String getGroupsUrl(Account account) { + return getApiV1BaseUrl()+"groups?"+getAccessTokenUrlParameter(account); + } + + public String getGroupUrl(long id, Account account) { + return getApiV1BaseUrl()+"groups/"+id+"?"+getAccessTokenUrlParameter(account); + } + + public String getSessionUrl(long id, Account account) { + return getApiV1BaseUrl()+"sessions/"+id+"?"+getAccessTokenUrlParameter(account); + } + + public String getOwnedDevicesUrl(Account account) { + return getApiV1BaseUrl()+"devices/?"+getAccessTokenUrlParameter(account); + } + + public String getDataAccessWindowsUrl(Permission permission, TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers, Account account) { + StringBuilder url = new StringBuilder(getApiV1BaseUrl()); + url.append("devices/data_access_windows?type="); + url.append(permission.name()); + if (startTime != null) { + url.append("&start_time="); + url.append(startTime.asMillis()); + } + if (endTime != null) { + url.append("&end_time="); + url.append(endTime.asMillis()); + } + if (deviceSerialNumbers != null) { + for (String serialNumber : deviceSerialNumbers) { + url.append("&serial_numbers[]="); + url.append(serialNumber); + } + } + url.append("&"); + url.append(getAccessTokenUrlParameter(account)); + return url.toString(); + } + public String getResourceDataUrl(TimePoint startTime, TimePoint endTime, Iterable serialNumbers, Map typeAndCompression, Account account) { StringBuilder url = new StringBuilder(getApiV1BaseUrl()); @@ -180,7 +230,6 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return url.toString(); } - @Override public String getResourcesUrl(Permission permission, TimePoint startTime, TimePoint endTime, Iterable serialNumbers, Iterable streamIds, Account account) { StringBuilder url = new StringBuilder(getApiV1BaseUrl()); @@ -212,6 +261,32 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return url.toString(); } + + public String getSessionsUrl(Iterable sessionIds, Boolean isPublic, Integer limit, Boolean includeIncomplete, Account account) { + StringBuilder url = new StringBuilder(getApiV1BaseUrl()); + url.append("sessions?"); + url.append(getAccessTokenUrlParameter(account)); + if (sessionIds != null) { + for (Long sessionId : sessionIds) { + url.append("&ids[]="); + url.append(sessionId); + } + } + if (isPublic != null) { + url.append("&public="); + url.append(isPublic.toString().toLowerCase()); + } + if (limit != null) { + url.append("&limit="); + url.append(limit); + } + if (includeIncomplete != null) { + url.append("&include_incomplete"); + url.append(includeIncomplete.toString().toLowerCase()); + } + return url.toString(); + } + private String getAccessTokenUrlParameter(Account account) { return "access_token="+getAccessTokenForAccount(account); } @@ -225,7 +300,14 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return client; } - @Override + /** + * Uses the /oauth/token service to obtain and {@link #registerAccountForWhichClientIsAuthorized(String) register} + * an access token for an authorization code which encodes the authorization given by a user to this factory's + * client. + * + * @return the account encoding the application that is authorized for a user's account + * @throws RuntimeException in case there was an error while retrieving the token + */ public Account obtainAccessTokenFromAuthorizationCode(String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException { HttpClient client = new SystemDefaultHttpClient(); HttpPost post = new HttpPost(getOauthTokenUrl()); @@ -248,7 +330,12 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { return result; } - @Override + /** + * Tries to authorize our client on behalf of a user identified by e-mail and password. + * + * @return the authorization code which can then be used to obtain a permanent access token to be used by our client + * to access data owned by the user identified by e-mail and password. + */ public String authorizeAndReturnAuthorizedCode(String userEmail, String userPassword) throws ClientProtocolException, IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException { @@ -261,6 +348,20 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { HttpResponse responseForAuthorize = client.execute(get); return signInAndReturnAuthorizationForm(client, responseForAuthorize, userEmail, userPassword); } + + @Override + public Account createAccountToAccessUserData(String userEmail, String userPassword) throws ClientProtocolException, + IOException, IllegalStateException, ParserConfigurationException, SAXException, ClassNotFoundException, + InstantiationException, IllegalAccessException, ClassCastException, ParseException { + final Account result; + String code = authorizeAndReturnAuthorizedCode(userEmail, userPassword); + if (code != null) { + result = obtainAccessTokenFromAuthorizationCode(code); + } else { + result = null; + } + return result; + } private String signInAndReturnAuthorizationForm(DefaultHttpClient client, HttpResponse response, final String userEmail, final String userPassword) throws ParserConfigurationException, SAXException, @@ -314,10 +415,6 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { * Parses the form in the autorizationForm response and posts it by submitting the form that contains * the commit button with the value "Authorize". If a redirect strategy is set on the client it will * see the redirect URL in the Location header. The redirection target is closed immediately. - * @throws ClassCastException - * @throws IllegalAccessException - * @throws InstantiationException - * @throws ClassNotFoundException */ private void authorizeAndGetCode(HttpResponse authorizationForm, DefaultHttpClient client) throws IllegalStateException, SAXException, IOException, ParserConfigurationException, @@ -364,13 +461,11 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { authorizationResponse.getEntity().getContent().close(); } - @Override public HttpClient getHttpClient() { HttpClient client = new SystemDefaultHttpClient(); return client; } - @Override public Iterable getWebsocketServers() throws IllegalStateException, ClientProtocolException, IOException, ParseException, URISyntaxException { HttpClient client = getHttpClient(); HttpGet getWebsocketServers = new HttpGet("https://www.igtimi.com/server_listers/web_sockets"); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java index 857b462f662..788b7c28ac1 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.igtimiadapter.impl; import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,23 +15,36 @@ import org.json.simple.parser.ParseException; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.igtimiadapter.Account; +import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; +import com.sap.sailing.domain.igtimiadapter.DataAccessWindow; +import com.sap.sailing.domain.igtimiadapter.Device; +import com.sap.sailing.domain.igtimiadapter.Group; import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; -import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; +import com.sap.sailing.domain.igtimiadapter.LiveDataConnection; import com.sap.sailing.domain.igtimiadapter.Permission; import com.sap.sailing.domain.igtimiadapter.Resource; +import com.sap.sailing.domain.igtimiadapter.Session; import com.sap.sailing.domain.igtimiadapter.User; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.datatypes.Type; +import com.sap.sailing.domain.igtimiadapter.websocket.WebSocketConnectionManager; +import com.sap.sailing.domain.tracking.DynamicTrack; +import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl; public class IgtimiConnectionImpl implements IgtimiConnection { private final Account account; - private final IgtimiConnectionFactory connectionFactory; + private final IgtimiConnectionFactoryImpl connectionFactory; - public IgtimiConnectionImpl(IgtimiConnectionFactory connectionFactory, Account account) { + public IgtimiConnectionImpl(IgtimiConnectionFactoryImpl connectionFactory, Account account) { this.connectionFactory = connectionFactory; this.account = account; } + @Override + public Account getAccount() { + return account; + } + @Override public Iterable getUsers() throws IllegalStateException, ClientProtocolException, IOException, ParseException { HttpClient client = connectionFactory.getHttpClient(); @@ -44,6 +58,28 @@ public class IgtimiConnectionImpl implements IgtimiConnection { return result; } + @Override + public User getUser(long id) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getUsers = new HttpGet(connectionFactory.getUserUrl(id, account)); + JSONObject userJson = ConnectivityUtils.getJsonFromResponse(client.execute(getUsers)); + User result = new UserDeserializer().createUserFromJson((JSONObject) ((JSONObject) userJson).get("user")); + return result; + } + + @Override + public Iterable getGroups() throws IllegalStateException, ClientProtocolException, IOException, ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getGroups = new HttpGet(connectionFactory.getGroupsUrl(account)); + JSONObject groupsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getGroups)); + final List result = new ArrayList<>(); + for (Object groupJson : (JSONArray) groupsJson.get("groups")) { + Group group = new GroupDeserializer().createGroupFromJson((JSONObject) ((JSONObject) groupJson).get("group")); + result.add(group); + } + return result; + } + @Override public Iterable getResources(Permission permission, TimePoint startTime, TimePoint endTime, Iterable deviceIds, Iterable streamIds) throws IllegalStateException, ClientProtocolException, IOException, ParseException { @@ -51,19 +87,126 @@ public class IgtimiConnectionImpl implements IgtimiConnection { HttpGet getResources = new HttpGet(connectionFactory.getResourcesUrl(permission, startTime, endTime, deviceIds, streamIds, account)); JSONObject resourcesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources)); final List result = new ArrayList<>(); - for (Object userJson : (JSONArray) resourcesJson.get("resources")) { - Resource user = new ResourceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) userJson).get("resource")); - result.add(user); + for (Object resourceJson : (JSONArray) resourcesJson.get("resources")) { + Resource resource = new ResourceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) resourceJson).get("resource"), this); + result.add(resource); + } + return result; + } + + @Override + public Iterable getSessions(Iterable sessionIds, Boolean isPublic, Integer limit, + Boolean includeIncomplete) throws IllegalStateException, ClientProtocolException, IOException, + ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getResources = new HttpGet(connectionFactory.getSessionsUrl(sessionIds, isPublic, limit, includeIncomplete, account)); + JSONObject sessionsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources)); + final List result = new ArrayList<>(); + for (Object sessionJson : (JSONArray) sessionsJson.get("sessions")) { + Session session = new SessionDeserializer().createResourceFromJson((JSONObject) ((JSONObject) sessionJson).get("session"), this); + result.add(session); } return result; } @Override - public Iterable getResourceData(TimePoint startTime, TimePoint endTime, - Iterable serialNumbers, Map typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + public Session getSession(long id) throws IllegalStateException, ClientProtocolException, IOException, + ParseException { HttpClient client = connectionFactory.getHttpClient(); - HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, serialNumbers, typeAndCompression, account)); + HttpGet getSessions = new HttpGet(connectionFactory.getSessionUrl(id, account)); + JSONObject sessionJson = ConnectivityUtils.getJsonFromResponse(client.execute(getSessions)); + Session result = new SessionDeserializer().createResourceFromJson((JSONObject) ((JSONObject) sessionJson).get("session"), this); + return result; + } + + @Override + public Iterable getResourceData(TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers, Map typeAndCompression) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getResourceData = new HttpGet(connectionFactory.getResourceDataUrl(startTime, endTime, deviceSerialNumbers, typeAndCompression, account)); JSONObject resourceDataJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResourceData)); return new FixFactory().createFixes(resourceDataJson); } + + @Override + public Iterable getResourceData(TimePoint startTime, TimePoint endTime, Iterable deviceSerialNumbers, + Type... types) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + Map typeAndCompression = new HashMap<>(); + for (Type type : types) { + typeAndCompression.put(type, 0.0); + } + return getResourceData(startTime, endTime, deviceSerialNumbers, typeAndCompression); + } + + @Override + public Iterable getAndNotifyResourceData(TimePoint startTime, TimePoint endTime, + Iterable deviceSerialNumbers, BulkFixReceiver bulkFixReceiver, Type... types) + throws IllegalStateException, ClientProtocolException, IOException, ParseException { + Iterable result = getResourceData(startTime, endTime, deviceSerialNumbers, types); + bulkFixReceiver.received(result); + return result; + } + + @Override + public Map>> getResourceDataAsTracks(TimePoint startTime, TimePoint endTime, Iterable deviceSerialNumbers, + Type... types) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + Iterable fixes = getResourceData(startTime, endTime, deviceSerialNumbers, types); + Map>> result = new HashMap<>(); + for (Fix fix : fixes) { + String deviceSerialNumber = fix.getSensor().getDeviceSerialNumber(); + Type type = fix.getType(); + DynamicTrack track = (DynamicTrack) getOrCreateTrack(result, deviceSerialNumber, type); + track.add(fix); + } + return result; + } + + + @Override + public LiveDataConnection createLiveConnection(Iterable deviceSerialNumbers) throws Exception { + return new WebSocketConnectionManager(connectionFactory, deviceSerialNumbers, getAccount()); + } + + private DynamicTrack getOrCreateTrack(Map>> result, + String deviceSerialNumber, Type type) { + Map> mapForDevice = result.get(deviceSerialNumber); + if (mapForDevice == null) { + mapForDevice = new HashMap<>(); + result.put(deviceSerialNumber, mapForDevice); + } + DynamicTrack track = mapForDevice.get(type); + if (track == null) { + track = new DynamicTrackImpl("Track for Igtimi fixes of type "+type.name()+" for "+this); + mapForDevice.put(type, track); + } + return track; + } + + @Override + public Iterable getOwnedDevices() throws IllegalStateException, ClientProtocolException, IOException, ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getResources = new HttpGet(connectionFactory.getOwnedDevicesUrl(account)); + JSONObject devicesJson = ConnectivityUtils.getJsonFromResponse(client.execute(getResources)); + final List result = new ArrayList<>(); + for (Object deviceJson : (JSONArray) devicesJson.get("devices")) { + Device device = new DeviceDeserializer().createResourceFromJson((JSONObject) ((JSONObject) deviceJson).get("device"), this); + result.add(device); + } + return result; + } + + @Override + public Iterable getDataAccessWindows(Permission permission, TimePoint startTime, + TimePoint endTime, Iterable deviceSerialNumbers) throws IllegalStateException, ClientProtocolException, IOException, ParseException { + HttpClient client = connectionFactory.getHttpClient(); + HttpGet getDataAccessWindows = new HttpGet(connectionFactory.getDataAccessWindowsUrl(permission, startTime, endTime, deviceSerialNumbers, account)); + JSONObject dataAccessWindowsJson = ConnectivityUtils.getJsonFromResponse(client.execute(getDataAccessWindows)); + final List result = new ArrayList<>(); + for (Object dataAccessWindowJson : (JSONArray) dataAccessWindowsJson.get("data_access_windows")) { + DataAccessWindow dataAccessWindow = new DataAccessWindowDeserializer().createDataAccessWindowFromJson( + (JSONObject) ((JSONObject) dataAccessWindowJson).get("data_access_window"), this); + result.add(dataAccessWindow); + } + return result; + } } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindReceiver.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindReceiver.java new file mode 100755 index 00000000000..7c373320e46 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiWindReceiver.java @@ -0,0 +1,402 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import java.io.IOException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.declination.Declination; +import com.sap.sailing.declination.DeclinationService; +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.base.impl.ScalableBearing; +import com.sap.sailing.domain.base.impl.ScalablePosition; +import com.sap.sailing.domain.base.impl.ScalableSpeed; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.confidence.ScalableValue; +import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; +import com.sap.sailing.domain.igtimiadapter.IgtimiFixReceiverAdapter; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWA; +import com.sap.sailing.domain.igtimiadapter.datatypes.AWS; +import com.sap.sailing.domain.igtimiadapter.datatypes.COG; +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; +import com.sap.sailing.domain.igtimiadapter.datatypes.GpsLatLong; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDG; +import com.sap.sailing.domain.igtimiadapter.datatypes.HDGM; +import com.sap.sailing.domain.igtimiadapter.datatypes.SOG; +import com.sap.sailing.domain.tracking.DynamicTrack; +import com.sap.sailing.domain.tracking.Track; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindListener; +import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; + +/** + * Receives Igtimi {@link Fix}es and tries to generate a {@link Wind} object from each {@link AWS} fix. For this to + * work, the time-wise adjacent {@link AWA}, {@link HDG}/{@link HDGM} and {@link GpsLatLong} fixes are used. If only + * a magnetic heading ({@link HDGM}) is available, the {@link DeclinationService} is used to map that to a true heading. + * The true wind direction is determined by adding the boat speed vector onto the apparent wind vector. + * + * @author Axel Uhl (d043530) + * + */ +public class IgtimiWindReceiver implements BulkFixReceiver { + private static final Logger logger = Logger.getLogger(IgtimiWindReceiver.class.getName()); + private final DynamicTrack awaTrack; + private final DynamicTrack awsTrack; + private final DynamicTrack gpsTrack; + private final DynamicTrack cogTrack; + private final DynamicTrack sogTrack; + private final DynamicTrack hdgTrack; + private final DynamicTrack hdgmTrack; + private final FixReceiver receiver; + private final DeclinationService declinationService; + private final ConcurrentHashMap listeners; + + private class FixReceiver extends IgtimiFixReceiverAdapter { + @Override + public void received(AWA fix) { + getAwaTrack().add(fix); + } + + @Override + public void received(AWS fix) { + getAwsTrack().add(fix); + } + + @Override + public void received(GpsLatLong fix) { + getGpsTrack().add(fix); + } + + @Override + public void received(COG fix) { + getCogTrack().add(fix); + } + + @Override + public void received(SOG fix) { + getSogTrack().add(fix); + } + + @Override + public void received(HDG fix) { + getHdgTrack().add(fix); + } + + @Override + public void received(HDGM fix) { + getHdgmTrack().add(fix); + } + } + + public IgtimiWindReceiver(String deviceSerialNumber) { + receiver = new FixReceiver(); + declinationService = DeclinationService.INSTANCE; + listeners = new ConcurrentHashMap<>(); + awaTrack = new DynamicTrackImpl<>("AWA Track for Igtimi wind track for device "+deviceSerialNumber); + awsTrack = new DynamicTrackImpl<>("AWS Track for Igtimi wind track for device "+deviceSerialNumber); + gpsTrack = new DynamicTrackImpl<>("GPS Track for Igtimi wind track for device "+deviceSerialNumber); + cogTrack = new DynamicTrackImpl<>("COG Track for Igtimi wind track for device "+deviceSerialNumber); + sogTrack = new DynamicTrackImpl<>("SOG Track for Igtimi wind track for device "+deviceSerialNumber); + hdgTrack = new DynamicTrackImpl<>("HDG Track for Igtimi wind track for device "+deviceSerialNumber); + hdgmTrack = new DynamicTrackImpl<>("HDGM Track for Igtimi wind track for device "+deviceSerialNumber); + } + + /** + * Called when a batch of fixes was received, e.g., after loading from stored data or from a live web socket receiver. + * The fixes are pumped into the tracks by the superclass implementation. Then, for all new {@link AWS} fixes, a + * call to {@link IgtimiWindReceiver#getWind} is performed. + */ + @Override + public void received(Iterable fixes) { + final List awsFixes = new ArrayList<>(); + for (Fix fix : fixes) { + fix.notify(receiver); + fix.notify(new IgtimiFixReceiverAdapter() { + @Override + public void received(AWS fix) { + awsFixes.add(fix); + } + }); + } + for (AWS aws : awsFixes) { + try { + final Wind wind = getWind(aws.getTimePoint()); + if (wind != null) { + notifyListeners(wind); + } + } catch (ClassNotFoundException | IOException | ParseException e) { + logger.log(Level.INFO, "Exception while trying to construct Wind fix from Igtimi fix " + aws, e); + } + } + } + + public void addListener(WindListener listener) { + listeners.put(listener, listener); + } + + public void notifyListeners(Wind wind) { + for (WindListener listener : listeners.keySet()) { + listener.windDataReceived(wind); + } + } + + private Wind getWind(TimePoint timePoint) throws ClassNotFoundException, IOException, ParseException { + final Wind result; + Pair awaPair = getSurroundingFixes(getAwaTrack(), timePoint); + Bearing awa = getAWA(timePoint, awaPair); + Pair awsPair = getSurroundingFixes(getAwsTrack(), timePoint); + Speed aws = getAWS(timePoint, awsPair); + Pair gpsPair = getSurroundingFixes(getGpsTrack(), timePoint); + Position pos = getPosition(timePoint, gpsPair); + Pair hdgPair = getSurroundingFixes(getHdgTrack(), timePoint); + Pair hdgmPair = getSurroundingFixes(getHdgmTrack(), timePoint); + Bearing heading = getHeading(timePoint, hdgPair, hdgmPair, pos); + if (awa != null && aws != null && pos != null && heading != null) { + Bearing apparentWindDirection = heading.add(awa); + SpeedWithBearing apparentWindSpeedWithDirection = new KnotSpeedWithBearingImpl(aws.getKnots(), apparentWindDirection); + /* + * Hint from Brent Russell from Igtimi, at 2013-12-05 on the question whether to use GpsLatLong to improve + * precision of boat speed / coarse over SOG/COG measurements: + * + * "Personally I would use COG/SOG exclusively, and if unhappy with the result add a small amount of + * smoothing and consider dropping samples as outliers if they cause a SOG discontinuity. The latter might + * happen as a satellite is dropped/acquired - and I'd expect to see a time correlated position jump as + * well. Probably not though a direction/speed correlation :) + * + * All our GPS systems are using Doppler to calculate COG/SOG and this should be the most accurate measure. + * I don't believe that delta position really adds any more "truth" to the measurement of physical reality, + * if that makes sense. I'd trust d-p even less at low speeds, where you'll see the most disagreement. Also + * there is a significant quantisation noise error in the d-p calculations from the GPS resolution too, so + * you'd have to smooth it before averaging - possibly in a speed dependent way. + * + * Remember that Doppler COG/SOG is using the same raw satellite measurements that are being used to + * calculate position, just the algorithm is different. I suspect that merging the two might be, in + * practice, just a slightly indirect way of averaging. If you like the central limit theorem in action over + * the set of algorithms! + * + * So again, my personal preference would be to work with the data that should be the most accurate + * (COG/SOG) and consider algorithms that handle smoothing of that data best." + */ + Pair sogPair = getSurroundingFixes(getSogTrack(), timePoint); + Speed sog = getSOG(timePoint, sogPair); + Pair cogPair = getSurroundingFixes(getCogTrack(), timePoint); + Bearing cog = getCOG(timePoint, cogPair); + SpeedWithBearing sogCog = new KnotSpeedWithBearingImpl(sog.getKnots(), cog); + SpeedWithBearing trueWindSpeedAndDirection = apparentWindSpeedWithDirection.add(sogCog); + result = new WindImpl(pos, timePoint, trueWindSpeedAndDirection); + } else { + result = null; + } + return result; + } + + private Bearing getAWA(TimePoint timePoint, Pair awaPair) { + final Bearing awaFrom; + if (awaPair.getA() == null) { + if (awaPair.getB() == null) { + awaFrom = null; + } else { + awaFrom = awaPair.getB().getApparentWindAngle(); + } + } else { + if (awaPair.getB() == null) { + awaFrom = awaPair.getA().getApparentWindAngle(); + } else { + awaFrom = timeBasedAgerage(timePoint, + new ScalableBearing(awaPair.getA().getApparentWindAngle()), awaPair.getA().getTimePoint(), + new ScalableBearing(awaPair.getB().getApparentWindAngle()), awaPair.getB().getTimePoint()); + } + } + return awaFrom == null ? null : awaFrom.reverse(); + } + + private Speed getAWS(TimePoint timePoint, Pair awsPair) { + final Speed aws; + if (awsPair.getA() == null) { + if (awsPair.getB() == null) { + aws = null; + } else { + aws = awsPair.getB().getApparentWindSpeed(); + } + } else { + if (awsPair.getB() == null) { + aws = awsPair.getA().getApparentWindSpeed(); + } else { + aws = timeBasedAgerage(timePoint, + new ScalableSpeed(awsPair.getA().getApparentWindSpeed()), awsPair.getA().getTimePoint(), + new ScalableSpeed(awsPair.getB().getApparentWindSpeed()), awsPair.getB().getTimePoint()); + } + } + return aws; + } + + private Speed getSOG(TimePoint timePoint, Pair sogPair) { + final Speed sog; + if (sogPair.getA() == null) { + if (sogPair.getB() == null) { + sog = null; + } else { + sog = sogPair.getB().getSpeedOverGround(); + } + } else { + if (sogPair.getB() == null) { + sog = sogPair.getA().getSpeedOverGround(); + } else { + sog = timeBasedAgerage(timePoint, + new ScalableSpeed(sogPair.getA().getSpeedOverGround()), sogPair.getA().getTimePoint(), + new ScalableSpeed(sogPair.getB().getSpeedOverGround()), sogPair.getB().getTimePoint()); + } + } + return sog; + } + + private Bearing getCOG(TimePoint timePoint, Pair cogPair) { + final Bearing sog; + if (cogPair.getA() == null) { + if (cogPair.getB() == null) { + sog = null; + } else { + sog = cogPair.getB().getCourseOverGround(); + } + } else { + if (cogPair.getB() == null) { + sog = cogPair.getA().getCourseOverGround(); + } else { + sog = timeBasedAgerage(timePoint, + new ScalableBearing(cogPair.getA().getCourseOverGround()), cogPair.getA().getTimePoint(), + new ScalableBearing(cogPair.getB().getCourseOverGround()), cogPair.getB().getTimePoint()); + } + } + return sog; + } + + private Position getPosition(TimePoint timePoint, Pair gpsPair) { + final Position pos; + if (gpsPair.getA() == null) { + if (gpsPair.getB() == null) { + pos = null; + } else { + pos = gpsPair.getB().getPosition(); + } + } else { + if (gpsPair.getB() == null) { + pos = gpsPair.getA().getPosition(); + } else { + pos = timeBasedAgerage(timePoint, new ScalablePosition(gpsPair.getA().getPosition()), gpsPair.getA() + .getTimePoint(), new ScalablePosition(gpsPair.getB().getPosition()), gpsPair.getB() + .getTimePoint()); + } + } + return pos; + } + + private Bearing getHDG(TimePoint timePoint, Pair hdgPair) { + final Bearing hdg; + if (hdgPair.getA() == null) { + if (hdgPair.getB() == null) { + hdg = null; + } else { + hdg = hdgPair.getB().getTrueHeading(); + } + } else { + if (hdgPair.getB() == null) { + hdg = hdgPair.getA().getTrueHeading(); + } else { + hdg = timeBasedAgerage(timePoint, + new ScalableBearing(hdgPair.getA().getTrueHeading()), hdgPair.getA().getTimePoint(), + new ScalableBearing(hdgPair.getB().getTrueHeading()), hdgPair.getB().getTimePoint()); + } + } + return hdg; + } + + private Bearing getHDGM(TimePoint timePoint, Pair hdgmPair) { + final Bearing hdgm; + if (hdgmPair.getA() == null) { + if (hdgmPair.getB() == null) { + hdgm = null; + } else { + hdgm = hdgmPair.getB().getMagnetigHeading(); + } + } else { + if (hdgmPair.getB() == null) { + hdgm = hdgmPair.getA().getMagnetigHeading(); + } else { + hdgm = timeBasedAgerage(timePoint, + new ScalableBearing(hdgmPair.getA().getMagnetigHeading()), hdgmPair.getA().getTimePoint(), + new ScalableBearing(hdgmPair.getB().getMagnetigHeading()), hdgmPair.getB().getTimePoint()); + } + } + return hdgm; + } + + private Bearing getHeading(TimePoint timePoint, Pair hdgPair, Pair hdgmPair, Position position) throws ClassNotFoundException, IOException, ParseException { + final Bearing trueHeading; + Bearing hdg = getHDG(timePoint, hdgPair); + if (hdg != null) { + trueHeading = hdg; + } else { + Bearing hdgm = getHDGM(timePoint, hdgmPair); + Declination declination = declinationService.getDeclination(timePoint, position, /* timeoutForOnlineFetchInMilliseconds 5s */ 5000); + trueHeading = hdgm.add(declination.getBearingCorrectedTo(timePoint)); + } + return trueHeading; + } + + private T timeBasedAgerage(TimePoint timePoint, ScalableValue value1, TimePoint timePoint1, ScalableValue value2, TimePoint timePoint2) { + final T acc; + if (timePoint1.equals(timePoint2)) { + acc = value1.add(value2).divide(2); + } else { + long timeDiff1 = Math.abs(timePoint1.asMillis() - timePoint.asMillis()); + long timeDiff2 = Math.abs(timePoint2.asMillis() - timePoint.asMillis()); + acc = value1.multiply(timeDiff2).add(value2.multiply(timeDiff1)).divide(timeDiff1 + timeDiff2); + } + return acc; + } + + private Pair getSurroundingFixes(Track track, TimePoint timePoint) { + T left = track.getLastFixAtOrBefore(timePoint); + T right = track.getFirstFixAtOrAfter(timePoint); + Pair result = new Pair<>(left, right); + return result; + } + + private DynamicTrack getAwaTrack() { + return awaTrack; + } + + private DynamicTrack getAwsTrack() { + return awsTrack; + } + + private DynamicTrack getGpsTrack() { + return gpsTrack; + } + + private DynamicTrack getCogTrack() { + return cogTrack; + } + + private DynamicTrack getSogTrack() { + return sogTrack; + } + + private DynamicTrack getHdgTrack() { + return hdgTrack; + } + + private DynamicTrack getHdgmTrack() { + return hdgmTrack; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceDeserializer.java index 4b58a0a5d23..1389a344e0e 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceDeserializer.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceDeserializer.java @@ -1,18 +1,14 @@ package com.sap.sailing.domain.igtimiadapter.impl; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; - import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.Resource; -public class ResourceDeserializer { - public Resource createResourceFromJson(JSONObject resourceJson) { +public class ResourceDeserializer extends HasPermissionsDeserializer { + public Resource createResourceFromJson(JSONObject resourceJson, IgtimiConnection conn) { Boolean blob = (Boolean) resourceJson.get("blob"); return new ResourceImpl((Long) resourceJson.get("id"), new MillisecondsTimePoint(((Double) resourceJson.get("start_time")).longValue()), @@ -20,17 +16,7 @@ public class ResourceDeserializer { (String) resourceJson.get("device_serial_number"), getDataTypes((JSONArray) resourceJson.get("data_types")), getPermissions((JSONObject) resourceJson.get("permissions")), - blob == null ? false : blob); - } - - private Iterable getPermissions(JSONObject permissions) { - final List result = new ArrayList<>(); - for (Entry e : permissions.entrySet()) { - if ((Boolean) e.getValue()) { - result.add(Permission.valueOf((String) e.getKey())); - } - } - return result; + blob == null ? false : blob, conn); } private int[] getDataTypes(JSONArray jsonArray) { diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceImpl.java index 52c74bc903b..c43fcbc47cc 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/ResourceImpl.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.igtimiadapter.impl; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.Permission; import com.sap.sailing.domain.igtimiadapter.Resource; @@ -14,7 +15,7 @@ public class ResourceImpl implements Resource { private final boolean blob; public ResourceImpl(long id, TimePoint startTime, TimePoint endTime, String deviceSerialNumber, int[] dataTypes, - Iterable permissions, boolean blob) { + Iterable permissions, boolean blob, IgtimiConnection conn) { super(); this.id = id; this.startTime = startTime; diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SensorImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SensorImpl.java index a0a734adbea..1442c9d8103 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SensorImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SensorImpl.java @@ -3,35 +3,36 @@ package com.sap.sailing.domain.igtimiadapter.impl; import com.sap.sailing.domain.igtimiadapter.Sensor; public class SensorImpl implements Sensor { - private final String transmitterId; - private final String deviceId; - protected SensorImpl(String transmitterId, String deviceId) { + private final String deviceSerialNumber; + private final long subDeviceId; + + protected SensorImpl(String deviceSerialNumber, long subDeviceId) { super(); - this.transmitterId = transmitterId; - this.deviceId = deviceId; + this.deviceSerialNumber = deviceSerialNumber; + this.subDeviceId = subDeviceId; } @Override - public String getTransmitterId() { - return transmitterId; + public String getDeviceSerialNumber() { + return deviceSerialNumber; } @Override - public String getDeviceId() { - return deviceId; + public long getSensorId() { + return subDeviceId; } @Override public String toString() { - return "Transmitter "+getTransmitterId()+(getDeviceId()==null?"":(", device "+getDeviceId())); + return "Device "+getDeviceSerialNumber()+", sensor "+getSensorId(); } @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((deviceId == null) ? 0 : deviceId.hashCode()); - result = prime * result + ((transmitterId == null) ? 0 : transmitterId.hashCode()); + result = prime * result + ((deviceSerialNumber == null) ? 0 : deviceSerialNumber.hashCode()); + result = prime * result + (int) (subDeviceId ^ (subDeviceId >>> 32)); return result; } @@ -44,15 +45,12 @@ public class SensorImpl implements Sensor { if (getClass() != obj.getClass()) return false; SensorImpl other = (SensorImpl) obj; - if (deviceId == null) { - if (other.deviceId != null) + if (deviceSerialNumber == null) { + if (other.deviceSerialNumber != null) return false; - } else if (!deviceId.equals(other.deviceId)) + } else if (!deviceSerialNumber.equals(other.deviceSerialNumber)) return false; - if (transmitterId == null) { - if (other.transmitterId != null) - return false; - } else if (!transmitterId.equals(other.transmitterId)) + if (subDeviceId != other.subDeviceId) return false; return true; } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionDeserializer.java new file mode 100755 index 00000000000..d7dee5f6e7e --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionDeserializer.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.Session; + +public class SessionDeserializer extends HasPermissionsDeserializer { + public Session createResourceFromJson(JSONObject sessionJson, IgtimiConnection conn) { + Boolean blob = (Boolean) sessionJson.get("blob"); + final Double startTime = (Double) sessionJson.get("start_time"); + final Double endTime = (Double) sessionJson.get("end_time"); + return new SessionImpl((Long) sessionJson.get("id"), (String) sessionJson.get("name"), (Long) sessionJson.get("owner_id"), + (Long) sessionJson.get("session_group_id"), (Long) sessionJson.get("admin_session_group_id"), + getPermissions((JSONObject) sessionJson.get("permissions")), + startTime==null?null:new MillisecondsTimePoint(startTime.longValue()), + endTime==null?null:new MillisecondsTimePoint(endTime.longValue()), + blob == null ? false : blob, conn); + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionImpl.java new file mode 100755 index 00000000000..1d22b0c6d8b --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/SessionImpl.java @@ -0,0 +1,83 @@ +package com.sap.sailing.domain.igtimiadapter.impl; + +import java.io.IOException; + +import org.apache.http.client.ClientProtocolException; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; +import com.sap.sailing.domain.igtimiadapter.Permission; +import com.sap.sailing.domain.igtimiadapter.Session; +import com.sap.sailing.domain.igtimiadapter.User; + +public class SessionImpl extends HasIdImpl implements Session { + private final long ownerId; + private final String name; + private final long sessionGroupId; + private final long adminSessionGroupId; + private final Iterable permissions; + private final TimePoint startTime; + private final TimePoint endTime; + private final boolean blob; + private final IgtimiConnection conn; + + protected SessionImpl(long id, String name, long ownerId, long sessionGroupId, + long adminSessionGroupId, Iterable permissions, TimePoint startTime, TimePoint endTime, boolean blob, IgtimiConnection conn) { + super(id); + this.name = name; + this.ownerId = ownerId; + this.sessionGroupId = sessionGroupId; + this.adminSessionGroupId = adminSessionGroupId; + this.permissions = permissions; + this.startTime = startTime; + this.endTime = endTime; + this.blob = blob; + this.conn = conn; + } + + @Override + public String getName() { + return name; + } + + @Override + public long getOwnerId() { + return ownerId; + } + + @Override + public long getSessionGroupId() { + return sessionGroupId; + } + + @Override + public long getAdminSessionGroupId() { + return adminSessionGroupId; + } + + @Override + public Iterable getPermissions() { + return permissions; + } + + @Override + public TimePoint getEndTime() { + return endTime; + } + + @Override + public TimePoint getStartTime() { + return startTime; + } + + @Override + public boolean isBlob() { + return blob; + } + + @Override + public User getOwner() throws IllegalStateException, ClientProtocolException, IOException, ParseException { + return conn.getUser(getOwnerId()); + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/UserDeserializer.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/UserDeserializer.java index fc561bbfd74..12ca06f588b 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/UserDeserializer.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/UserDeserializer.java @@ -7,12 +7,11 @@ import com.sap.sailing.domain.igtimiadapter.User; public class UserDeserializer { public User createUserFromJson(JSONObject userJson) { Boolean blob = (Boolean) userJson.get("blob"); - return new UserImpl((Long) userJson.get("id"), + final Long id = (Long) userJson.get("id"); + return new UserImpl(id, (String) userJson.get("first_name"), (String) userJson.get("surname"), (String) userJson.get("email"), blob == null ? false : blob); } - - } diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java index aa9a6429c2a..a31c9dfada9 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java @@ -17,8 +17,8 @@ import javax.ws.rs.core.UriInfo; import org.apache.http.client.ClientProtocolException; import org.json.simple.parser.ParseException; -import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; import com.sap.sailing.domain.igtimiadapter.impl.Activator; +import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; @Path(AuthorizationCallback.V1) public class AuthorizationCallback { @@ -27,7 +27,7 @@ public class AuthorizationCallback { private static final String AUTHORIZATIONCALLBACK = "/authorizationcallback"; static final String V1 = "/v1"; static final String V1_AUTHORIZATIONCALLBACK = V1 + AUTHORIZATIONCALLBACK; - private final IgtimiConnectionFactory connectionFactory; + private final IgtimiConnectionFactoryImpl connectionFactory; public AuthorizationCallback() throws ClientProtocolException, IllegalStateException, IOException, ParseException { connectionFactory = Activator.getInstance().getConnectionFactory(); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java index c1376cd34e1..1627c7db38d 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/websocket/WebSocketConnectionManager.java @@ -5,10 +5,9 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; -import java.util.Set; import java.util.Timer; import java.util.TimerTask; -import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,14 +25,15 @@ import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.igtimiadapter.Account; -import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; -import com.sap.sailing.domain.igtimiadapter.LiveDataListener; +import com.sap.sailing.domain.igtimiadapter.BulkFixReceiver; +import com.sap.sailing.domain.igtimiadapter.LiveDataConnection; import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; import com.sap.sailing.domain.igtimiadapter.impl.FixFactory; +import com.sap.sailing.domain.igtimiadapter.impl.IgtimiConnectionFactoryImpl; -public class WebSocketConnectionManager extends WebSocketAdapter { +public class WebSocketConnectionManager extends WebSocketAdapter implements LiveDataConnection { private static final Logger logger = Logger.getLogger(WebSocketConnectionManager.class.getName()); - private final IgtimiConnectionFactory connectionFactory; + private final IgtimiConnectionFactoryImpl connectionFactory; private static enum TargetState { OPEN, CLOSED }; private TargetState targetState; private final WebSocketClient client; @@ -44,19 +44,19 @@ public class WebSocketConnectionManager extends WebSocketAdapter { private final Account account; private final FixFactory fixFactory; private boolean receivedServerHeartbeatInInterval; - private final Set liveDataListeners; + private final ConcurrentHashMap listeners; private TimePoint igtimiServerTimepoint; private TimePoint localTimepointWhenServerTimepointWasReceived; - public WebSocketConnectionManager(IgtimiConnectionFactory connectionFactory, Iterable deviceIds, Account account) throws Exception { - this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceIds+" and account "+account); - this.deviceIds = deviceIds; + public WebSocketConnectionManager(IgtimiConnectionFactoryImpl connectionFactory, Iterable deviceSerialNumbers, Account account) throws Exception { + this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceSerialNumbers+" and account "+account); + this.deviceIds = deviceSerialNumbers; this.account = account; this.fixFactory = new FixFactory(); this.connectionFactory = connectionFactory; - this.liveDataListeners = new ConcurrentSkipListSet<>(); + this.listeners = new ConcurrentHashMap<>(); client = new WebSocketClient(); - configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceIds); + configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceSerialNumbers); request = new ClientUpgradeRequest(); client.start(); reconnect(); @@ -71,6 +71,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter { * use 0 to wait indefinitely * @return true if the connection is established before the timeout occurred */ + @Override public boolean waitForConnection(long timeoutInMillis) throws InterruptedException { long startedToWait = System.currentTimeMillis(); synchronized (this) { @@ -81,10 +82,6 @@ public class WebSocketConnectionManager extends WebSocketAdapter { } } - public void addLiveDataListener(LiveDataListener listener) { - liveDataListeners.add(listener); - } - public void disconnect() throws Exception { targetState = TargetState.CLOSED; client.stop(); @@ -141,9 +138,14 @@ public class WebSocketConnectionManager extends WebSocketAdapter { return new Pair(igtimiServerTimepoint, localTimepointWhenServerTimepointWasReceived); } + @Override + public void addListener(BulkFixReceiver listener) { + listeners.put(listener, listener); + } + private void notifyListeners(List fixes) { - for (LiveDataListener listener : liveDataListeners) { - listener.fixesReceived(fixes); + for (BulkFixReceiver listener : listeners.keySet()) { + listener.received(fixes); } } @@ -198,7 +200,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter { }, /* delay */ 0, /* send ping message every other minute; has to be at least every five minutes, so this should be safe */ 120000l); } - public void reconnect() throws IOException, IllegalStateException, ParseException, URISyntaxException { + private void reconnect() throws IOException, IllegalStateException, ParseException, URISyntaxException { IOException lastException = null; for (URI uri : connectionFactory.getWebsocketServers()) { try { diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java index 9417d71e23d..721b2a7d4ec 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.persistence; import java.util.Collection; import java.util.Map; +import java.util.Map.Entry; import com.mongodb.DBObject; import com.sap.sailing.domain.base.Competitor; @@ -10,6 +11,8 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RegattaRegistry; +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -97,4 +100,6 @@ public interface DomainObjectFactory { Collection loadAllCompetitors(); DomainFactory getBaseDomainFactory(); + + Iterable> loadAllDeviceConfigurations(); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java index 9c03167c1e0..174c4d98966 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java @@ -9,6 +9,8 @@ import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -102,4 +104,8 @@ public interface MongoObjectFactory { DB getDatabase(); + void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration); + + void removeDeviceConfiguration(DeviceConfigurationMatcher matcher); + } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java index 466851ec6d5..32e658d47db 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java @@ -51,10 +51,16 @@ public enum CollectionNames { /** * Stores the race log events for a tracked race. */ - RACE_LOGS, + /** * Stores competitors for smartphone tracking. */ COMPETITORS, + RACE_LOGS, + + /** + * Stores configurations for mobile devices. + */ + CONFIGURATIONS; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index 31610323e6e..8220d240e34 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -15,6 +16,7 @@ import java.util.logging.Logger; import org.bson.types.ObjectId; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -41,6 +43,11 @@ import com.sap.sailing.domain.base.RegattaRegistry; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationImpl; +import com.sap.sailing.domain.base.configuration.impl.RegattaConfigurationImpl; import com.sap.sailing.domain.base.impl.CourseDataImpl; import com.sap.sailing.domain.base.impl.EventImpl; import com.sap.sailing.domain.base.impl.FleetImpl; @@ -61,6 +68,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -72,7 +80,7 @@ import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections; import com.sap.sailing.domain.leaderboard.DelayedLeaderboardCorrections.LeaderboardCorrectionsResolvedListener; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; @@ -90,10 +98,12 @@ import com.sap.sailing.domain.leaderboard.impl.ThresholdBasedResultDiscardingRul import com.sap.sailing.domain.leaderboard.meta.LeaderboardGroupMetaLeaderboard; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoRaceLogStoreFactory; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventRestoreFactory; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; @@ -108,14 +118,20 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; +import com.sap.sailing.domain.racelog.impl.CompetitorResultsImpl; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.DeviceConfigurationJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +import com.sap.sailing.server.gateway.deserialization.impl.RegattaConfigurationJsonDeserializer; public class DomainObjectFactoryImpl implements DomainObjectFactory { private static final Logger logger = Logger.getLogger(DomainObjectFactoryImpl.class.getName()); @@ -839,7 +855,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private CourseArea loadCourseArea(DBObject courseAreaDBObject) { String name = (String) courseAreaDBObject.get(FieldNames.COURSE_AREA_NAME.name()); - Serializable id = (Serializable) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name()); + UUID id = (UUID) courseAreaDBObject.get(FieldNames.COURSE_AREA_ID.name()); return baseDomainFactory.getOrCreateCourseArea(id, name); } @@ -890,7 +906,18 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { courseArea = baseDomainFactory.getExistingCourseAreaById(courseAreaUuid); } + RegattaConfiguration configuration = null; + if (dbRegatta.containsField(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())) { + try { + JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(dbRegatta.get(FieldNames.REGATTA_REGATTA_CONFIGURATION.name())))); + configuration = RegattaConfigurationJsonDeserializer.create().deserialize(json); + } catch (JsonDeserializationException|ParseException e) { + logger.log(Level.WARNING, "Error loading racing procedure configration for regatta.", e); + } + } + result = new RegattaImpl(raceLogStore, baseName, boatClass, series, /* persistent */ true, loadScoringScheme(dbRegatta), id, courseArea); + result.setRegattaConfiguration(configuration); } return result; } @@ -1022,9 +1049,19 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), TripleSerializer.serialize(identifier.getIdentifier())); loadRaceLogEvents(result, query); - // query for events with deprecated identifier format + // query for events with deprecated identifier format... query.put(FieldNames.RACE_LOG_IDENTIFIER.name(), MongoUtils.escapeDollarAndDot(identifier.getDeprecatedIdentifier())); - loadRaceLogEvents(result, query); + + List eventsToMigrate = loadRaceLogEvents(result, query); + if (!eventsToMigrate.isEmpty()) { + // ... migrate them... + MongoRaceLogStoreVisitor storeVisitor = new MongoRaceLogStoreVisitor(identifier, new MongoObjectFactoryImpl(database)); + for (RaceLogEvent event : eventsToMigrate) { + event.accept(storeVisitor); + } + // ... and delete the old ones... + database.getCollection(CollectionNames.RACE_LOGS.name()).remove(query); + } } catch (Throwable t) { // something went wrong during DB access; report, then use empty new race log logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load recorded race log data. Check MongoDB settings."); @@ -1033,126 +1070,141 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return result; } - private void loadRaceLogEvents(RaceLog result, BasicDBObject query) { + private List loadRaceLogEvents(RaceLog targetRaceLog, BasicDBObject query) { + List result = new ArrayList<>(); DBCollection raceLog = database.getCollection(CollectionNames.RACE_LOGS.name()); for (DBObject o : raceLog.find(query)) { RaceLogEvent raceLogEvent = loadRaceLogEvent((DBObject) o.get(FieldNames.RACE_LOG_EVENT.name())); if (raceLogEvent != null) { - result.load(raceLogEvent); + targetRaceLog.load(raceLogEvent); + result.add(raceLogEvent); } } + return result; } public RaceLogEvent loadRaceLogEvent(DBObject dbObject) { - TimePoint timePoint = loadTimePoint(dbObject); + TimePoint logicalTimePoint = loadTimePoint(dbObject); TimePoint createdAt = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_CREATED_AT); Serializable id = (Serializable) dbObject.get(FieldNames.RACE_LOG_EVENT_ID.name()); Integer passId = (Integer) dbObject.get(FieldNames.RACE_LOG_EVENT_PASS_ID.name()); BasicDBList dbCompetitors = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name()); List competitors = loadCompetitorsForRaceLogEvent(dbCompetitors); + final RaceLogEventAuthor author; + String authorName = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name()); + Number authorPriority = (Number) dbObject.get(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name()); + if (authorName != null && authorPriority != null) { + author = new RaceLogEventAuthorImpl(authorName, authorPriority.intValue()); + } else { + author = RaceLogEventAuthorImpl.createCompatibilityAuthor(); + } String eventClass = (String) dbObject.get(FieldNames.RACE_LOG_EVENT_CLASS.name()); if (eventClass.equals(RaceLogStartTimeEvent.class.getSimpleName())) { - return loadRaceLogStartTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogRaceStatusEvent.class.getSimpleName())) { - return loadRaceLogRaceStatusEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogRaceStatusEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogFlagEvent.class.getSimpleName())) { - return loadRaceLogFlagEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogFlagEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogPassChangeEvent.class.getSimpleName())) { - return loadRaceLogPassChangeEvent(createdAt, timePoint, id, passId, competitors); + return loadRaceLogPassChangeEvent(createdAt, author, logicalTimePoint, id, passId, competitors); } else if (eventClass.equals(RaceLogCourseAreaChangedEvent.class.getSimpleName())) { - return loadRaceLogCourseAreaChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogCourseDesignChangedEvent.class.getSimpleName())) { - return loadRaceLogCourseDesignChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogFinishPositioningListChangedEvent.class.getSimpleName())) { - return loadRaceLogFinishPositioningListChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogFinishPositioningConfirmedEvent.class.getSimpleName())) { - return loadRaceLogFinishPositioningConfirmedEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogPathfinderEvent.class.getSimpleName())) { - return loadRaceLogPathfinderEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogPathfinderEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogGateLineOpeningTimeEvent.class.getSimpleName())) { - return loadRaceLogGateLineOpeningTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogStartProcedureChangedEvent.class.getSimpleName())) { - return loadRaceLogStartProcedureChangedEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogProtestStartTimeEvent.class.getSimpleName())) { - return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogRaceLogProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogWindFixEvent.class.getSimpleName())) { - return loadRaceLogRaceLogWindFixEvent(createdAt, timePoint, id, passId, competitors, dbObject); + return loadRaceLogRaceLogWindFixEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); } - private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - Integer passId, List competitors, DBObject dbObject) { + private RaceLogEvent loadRaceLogRaceLogWindFixEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { Wind wind = loadWind((DBObject) dbObject.get(FieldNames.WIND.name())); - return raceLogEventFactory.createWindFixEvent(createdAt, timePoint, id, competitors, passId, wind); + return raceLogEventFactory.createWindFixEvent(createdAt, author, logicalTimePoint, id, competitors, passId, wind); } - private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogEvent loadRaceLogRaceLogProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { TimePoint protestStartTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_PROTEST_START_TIME); - return raceLogEventFactory.createProtestStartTimeEvent(createdAt, timePoint, id, competitors, passId, protestStartTime); + return raceLogEventFactory.createProtestStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, protestStartTime); } - private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, TimePoint timePoint, + private RaceLogEvent loadRaceLogStartProcedureChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + RacingProcedureType type = RacingProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString()); + return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, type); + } + + private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - StartProcedureType type = StartProcedureType.valueOf(dbObject.get(FieldNames.RACE_LOG_START_PROCEDURE_TYPE.name()).toString()); - return raceLogEventFactory.createStartProcedureChangedEvent(createdAt, timePoint, id, competitors, passId, type); - } - - private RaceLogEvent loadRaceLogGateLineOpeningTimeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - Integer passId, List competitors, DBObject dbObject) { - Number gateLineOpeningTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name()); - return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, timePoint, id, competitors, passId, - gateLineOpeningTime == null ? null : gateLineOpeningTime.longValue()); + Number gateLaunchStopTime = (Number) dbObject.get(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name()); + Number golfDownTime = 0; + if (dbObject.containsField(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name())) { + golfDownTime = (Number) dbObject.get(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name()); + } + return raceLogEventFactory.createGateLineOpeningTimeEvent(createdAt, author, logicalTimePoint, id, competitors, + passId, gateLaunchStopTime == null ? null : gateLaunchStopTime.longValue(), golfDownTime.longValue()); } - private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - Integer passId, List competitors, DBObject dbObject) { + private RaceLogEvent loadRaceLogPathfinderEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { String pathfinderId = dbObject.get(FieldNames.RACE_LOG_PATHFINDER_ID.name()).toString(); - return raceLogEventFactory.createPathfinderEvent(createdAt, timePoint, id, competitors, passId, pathfinderId); + return raceLogEventFactory.createPathfinderEvent(createdAt, author, logicalTimePoint, id, competitors, passId, pathfinderId); } - private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogEvent loadRaceLogFinishPositioningConfirmedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); - List> positionedCompetitors = null; + CompetitorResults positionedCompetitors = null; //When a confirmation event is loaded that does not contain the positioned competitors (this is the case for the ESS events in //Singapore and Quingdao) then null should be set for the positionedCompetitors, which is evaluated later on. if (dbPositionedCompetitorList != null) { positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); } - return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, timePoint, id, competitors, passId, positionedCompetitors); + return raceLogEventFactory.createFinishPositioningConfirmedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); } - private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogEvent loadRaceLogFinishPositioningListChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { BasicDBList dbPositionedCompetitorList = (BasicDBList) dbObject.get(FieldNames.RACE_LOG_POSITIONED_COMPETITORS.name()); - List> positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); + CompetitorResults positionedCompetitors = loadPositionedCompetitors(dbPositionedCompetitorList); - return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, timePoint, id, competitors, passId, positionedCompetitors); + return raceLogEventFactory.createFinishPositioningListChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); } - private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - Integer passId, List competitors) { - return raceLogEventFactory.createPassChangeEvent(createdAt, timePoint, id, competitors, passId); + private RaceLogEvent loadRaceLogPassChangeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors) { + return raceLogEventFactory.createPassChangeEvent(createdAt, author, logicalTimePoint, id, competitors, passId); } - private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogCourseDesignChangedEvent loadRaceLogCourseDesignChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { String courseName = (String) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN_NAME.name()); CourseBase courseData = loadCourseData((BasicDBList) dbObject.get(FieldNames.RACE_LOG_COURSE_DESIGN.name()), courseName); - return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, timePoint, id, competitors, passId, courseData); + return raceLogEventFactory.createCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseData); } - private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { Serializable courseAreaId = (Serializable) dbObject.get(FieldNames.COURSE_AREA_ID.name()); - return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, timePoint, id, competitors, passId, courseAreaId); + return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId); } - private List> loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) { - List> positionedCompetitors = new ArrayList>(); + private CompetitorResults loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) { + CompetitorResults positionedCompetitors = new CompetitorResultsImpl(); for (Object object : dbPositionedCompetitorList) { DBObject dbObject = (DBObject) object; @@ -1181,13 +1233,13 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { List competitors = new ArrayList(); for (Object object : dbCompetitorList) { Serializable competitorId = (Serializable) object; - Competitor competitor = baseDomainFactory.getExistingCompetitorById(competitorId); + Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorById(competitorId); competitors.add(competitor); } return competitors; } - private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogFlagEvent loadRaceLogFlagEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { Flags upperFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name())); Flags lowerFlag = Flags.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name())); Boolean displayed = Boolean.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name())); @@ -1196,17 +1248,17 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return null; } - return raceLogEventFactory.createFlagEvent(createdAt, timePoint, id, competitors, passId, upperFlag, lowerFlag, displayed); + return raceLogEventFactory.createFlagEvent(createdAt, author, logicalTimePoint, id, competitors, passId, upperFlag, lowerFlag, displayed); } - private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogStartTimeEvent loadRaceLogStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { TimePoint startTime = loadTimePoint(dbObject, FieldNames.RACE_LOG_EVENT_START_TIME); - return raceLogEventFactory.createStartTimeEvent(createdAt, timePoint, id, competitors, passId, startTime); + return raceLogEventFactory.createStartTimeEvent(createdAt, author, logicalTimePoint, id, competitors, passId, startTime); } - private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { + private RaceLogRaceStatusEvent loadRaceLogRaceStatusEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { RaceLogRaceStatus nextStatus = RaceLogRaceStatus.valueOf((String) dbObject.get(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name())); - return raceLogEventFactory.createRaceStatusEvent(createdAt, timePoint, id, competitors, passId, nextStatus); + return raceLogEventFactory.createRaceStatusEvent(createdAt, author, logicalTimePoint, id, competitors, passId, nextStatus); } private CourseBase loadCourseData(BasicDBList dbCourseList, String courseName) { @@ -1287,4 +1339,56 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } return result; } + + @Override + public Iterable> loadAllDeviceConfigurations() { + Map result = new HashMap<>(); + DBCollection configurationCollection = database.getCollection(CollectionNames.CONFIGURATIONS.name()); + + try { + for (DBObject dbObject : configurationCollection.find()) { + Pair entry = loadConfigurationEntry(dbObject); + result.put(entry.getA(), entry.getB()); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error connecting to MongoDB, unable to load configurations."); + logger.log(Level.SEVERE, "loadAllDeviceConfigurations", e); + } + + return result.entrySet(); + } + + private Pair loadConfigurationEntry(DBObject dbObject) { + DBObject matcherObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_MATCHER.name()); + DBObject configObject = (DBObject) dbObject.get(FieldNames.CONFIGURATION_CONFIG.name()); + return new Pair(loadConfigurationMatcher(matcherObject), + loadConfiguration(configObject)); + } + + private DeviceConfigurationMatcher loadConfigurationMatcher(DBObject matcherObject) { + DeviceConfigurationMatcherType type = DeviceConfigurationMatcherType.valueOf( + matcherObject.get(FieldNames.CONFIGURATION_MATCHER_TYPE.name()).toString()); + List clientIdentifiers = new ArrayList(); + BasicDBList clientIdentifiersObject = (BasicDBList) matcherObject.get(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name()); + if (clientIdentifiersObject != null) { + for (Object clientIdentifier : clientIdentifiersObject) { + clientIdentifiers.add(clientIdentifier.toString()); + } + } + return baseDomainFactory.getOrCreateDeviceConfigurationMatcher(type, clientIdentifiers); + } + + private DeviceConfiguration loadConfiguration(DBObject configObject) { + DeviceConfiguration configuration = null; + try { + JsonDeserializer deserializer = DeviceConfigurationJsonDeserializer.create(); + JSONObject json = Helpers.toJSONObjectSafe(new JSONParser().parse(JSON.serialize(configObject))); + configuration = deserializer.deserialize(json); + } catch (JsonDeserializationException | ParseException e) { + logger.log(Level.SEVERE, "Error parsing configuration object from MongoDB, falling back to empty configuration."); + logger.log(Level.SEVERE, "loadConfiguration", e); + configuration = new DeviceConfigurationImpl(new RegattaConfigurationImpl()); + } + return configuration; + } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 1ccaf2b5a3d..cd6832a6858 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -18,6 +18,7 @@ public enum FieldNames { REGATTA_ID, REGATTA_BASE_NAME, REGATTA_SERIES, + REGATTA_REGATTA_CONFIGURATION, SERIES_NAME, SERIES_IS_MEDAL, SERIES_STARTS_WITH_ZERO_SCORE, @@ -69,9 +70,16 @@ public enum FieldNames { // race log RACE_LOG_IDENTIFIER, - RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER, + RACE_LOG_EVENT, RACE_LOG_EVENT_PASS_ID, RACE_LOG_EVENT_ID, RACE_LOG_EVENT_CREATED_AT, RACE_LOG_EVENT_AUTHOR_NAME, RACE_LOG_EVENT_AUTHOR_PRIORITY, + RACE_LOG_EVENT_INVOLVED_BOATS, RACE_LOG_EVENT_FLAG_UPPER, RACE_LOG_EVENT_FLAG_LOWER, RACE_LOG_EVENT_FLAG_DISPLAYED, RACE_LOG_EVENT_START_TIME, RACE_LOG_EVENT_CLASS, RACE_LOG_EVENT_NEXT_STATUS, RACE_LOG_COURSE_DESIGN, WAYPOINT_ID, WAYPOINT_NAME, WAYPOINT_PASSINGSIDE, CONTROLPOINT, CONTROLPOINT_CLASS, MARK_ID, MARK_COLOR, MARK_NAME, MARK_PATTERN, MARK_SHAPE, MARK_TYPE, - CONTROLPOINT_VALUE, GATE_ID, GATE_NAME, GATE_LEFT, GATE_RIGHT, RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, - RACE_LOG_START_PROCEDURE_TYPE, RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME; + CONTROLPOINT_VALUE, GATE_ID, GATE_NAME, GATE_LEFT, GATE_RIGHT, RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, + RACE_LOG_GOLF_DOWN_TIME, + RACE_LOG_START_PROCEDURE_TYPE, RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME, + + // configurations + CONFIGURATION_MATCHER, CONFIGURATION_CONFIG, CONFIGURATION_MATCHER_ID, CONFIGURATION_MATCHER_TYPE, CONFIGURATION_MATCHER_CLIENTS, + CONFIGURATION_CONFIG_ALLOWED_COURSE_AREA_NAMES, CONFIGURATION_CONFIG_MIN_ROUNDS, CONFIGURATION_CONFIG_MAX_ROUNDS, CONFIGURATION_CONFIG_RESULTS_MAIL, + CONFIGURATION_CONFIG_PROCEDURES; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index e3dd3a3506b..5a070081e79 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -29,6 +29,11 @@ import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.MaxPointsReason; @@ -39,6 +44,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -51,6 +57,7 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; @@ -68,7 +75,10 @@ import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tracking.Wind; import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.DeviceConfigurationJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer; public class MongoObjectFactoryImpl implements MongoObjectFactory { private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); @@ -467,6 +477,12 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { } else { dbRegatta.put(FieldNames.COURSE_AREA_ID.name(), null); } + if (regatta.getRegattaConfiguration() != null) { + JsonSerializer serializer = RegattaConfigurationJsonSerializer.create(); + JSONObject json = serializer.serialize(regatta.getRegattaConfiguration()); + DBObject configurationObject = (DBObject) JSON.parse(json.toString()); + dbRegatta.put(FieldNames.REGATTA_REGATTA_CONFIGURATION.name(), configurationObject); + } regattasCollection.update(query, dbRegatta, /* upsrt */ true, /* multi */ false); } @@ -545,6 +561,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { result.ensureIndex(new BasicDBObject(FieldNames.RACE_LOG_IDENTIFIER.name(), null)); return result; } + + private void storeRaceLogEventAuthor(DBObject dbObject, RaceLogEventAuthor author) { + if (author != null) { + dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_NAME.name(), author.getName()); + dbObject.put(FieldNames.RACE_LOG_EVENT_AUTHOR_PRIORITY.name(), author.getPriority()); + } + } public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogFlagEvent flagEvent) { BasicDBObject result = new BasicDBObject(); @@ -702,11 +725,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { } private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) { - storeTimed(event, result); + // for compatibility reasons we reuse the field name of Timed + storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS); storeTimePoint(event.getCreatedAt(), result, FieldNames.RACE_LOG_EVENT_CREATED_AT); result.put(FieldNames.RACE_LOG_EVENT_ID.name(), event.getId()); result.put(FieldNames.RACE_LOG_EVENT_PASS_ID.name(), event.getPassId()); result.put(FieldNames.RACE_LOG_EVENT_INVOLVED_BOATS.name(), storeInvolvedBoatsForRaceLogEvent(event.getInvolvedBoats())); + storeRaceLogEventAuthor(result, event.getAuthor()); } @@ -785,7 +810,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogGateLineOpeningTimeEvent.class.getSimpleName()); - result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTime()); + result.put(FieldNames.RACE_LOG_GATE_LINE_OPENING_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGateLaunchStopTime()); + result.put(FieldNames.RACE_LOG_GOLF_DOWN_TIME.name(), gateLineOpeningTimeEvent.getGateLineOpeningTimes().getGolfDownTime()); return result; } @@ -871,7 +897,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { public void storeCompetitor(Competitor competitor) { DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); JSONObject json = competitorSerializer.serialize(competitor); - BasicDBObject query = new BasicDBObject(CompetitorJsonSerializer.FIELD_ID, competitor.getId()); + DBObject query = (DBObject) JSON.parse(competitorSerializer.getCompetitorIdQuery(competitor).toString()); DBObject entry = (DBObject) JSON.parse(json.toString()); collection.update(query, entry, /* upsrt */true, /* multi */false); } @@ -887,7 +913,53 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { public void removeCompetitor(Competitor competitor) { logger.info("Removing persistent competitor info for competitor "+competitor.getName()+" with ID "+competitor.getId()); DBCollection collection = database.getCollection(CollectionNames.COMPETITORS.name()); - BasicDBObject query = new BasicDBObject(CompetitorJsonSerializer.FIELD_ID, competitor.getId().toString()); + DBObject query = (DBObject) JSON.parse(competitorSerializer.getCompetitorIdQuery(competitor).toString()); collection.remove(query); } + + @Override + public void storeDeviceConfiguration(DeviceConfigurationMatcher matcher, DeviceConfiguration configuration) { + DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name()); + + DBObject query = new BasicDBObject(); + query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); + + DBObject entryObject = new BasicDBObject(); + entryObject.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); + entryObject.put(FieldNames.CONFIGURATION_MATCHER.name(), createDeviceConfigurationMatcherObject(matcher)); + entryObject.put(FieldNames.CONFIGURATION_CONFIG.name(), createDeviceConfigurationObject(configuration)); + + configurationsCollections.update(query, entryObject, /* upsrt */ true, /* multi */ false); + } + + private DBObject createDeviceConfigurationMatcherObject(DeviceConfigurationMatcher matcher) { + DBObject matcherObject = new BasicDBObject(); + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_TYPE.name(), matcher.getMatcherType().name()); + if (matcher instanceof DeviceConfigurationMatcherSingle) { + BasicDBList client = new BasicDBList(); + client.add(((DeviceConfigurationMatcherSingle)matcher).getClientIdentifier()); + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), client); + } else if (matcher instanceof DeviceConfigurationMatcherMulti) { + BasicDBList clients = new BasicDBList(); + Util.addAll(((DeviceConfigurationMatcherMulti)matcher).getClientIdentifiers(), clients); + matcherObject.put(FieldNames.CONFIGURATION_MATCHER_CLIENTS.name(), clients); + } + return matcherObject; + } + + private DBObject createDeviceConfigurationObject(DeviceConfiguration configuration) { + JsonSerializer serializer = DeviceConfigurationJsonSerializer.create(); + JSONObject json = serializer.serialize(configuration); + DBObject entry = (DBObject) JSON.parse(json.toString()); + return entry; + } + + @Override + public void removeDeviceConfiguration(DeviceConfigurationMatcher matcher) { + DBCollection configurationsCollections = database.getCollection(CollectionNames.CONFIGURATIONS.name()); + DBObject query = new BasicDBObject(); + query.put(FieldNames.CONFIGURATION_MATCHER_ID.name(), matcher.getMatcherIdentifier()); + configurationsCollections.remove(query); + } + } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreImpl.java index 4b3adbed220..666945bb539 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreImpl.java @@ -15,25 +15,27 @@ public class MongoRaceLogStoreImpl implements RaceLogStore { private final MongoObjectFactory mongoObjectFactory; private final DomainObjectFactory domainObjectFactory; private final Map raceLogCache; - private final WeakHashMap listeners; + private final WeakHashMap listeners; public MongoRaceLogStoreImpl(MongoObjectFactory mongoObjectFactory, DomainObjectFactory domainObjectFactory) { this.mongoObjectFactory = mongoObjectFactory; this.domainObjectFactory = domainObjectFactory; this.raceLogCache = new HashMap<>(); - this.listeners = new WeakHashMap(); + this.listeners = new WeakHashMap(); } @Override public RaceLog getRaceLog(RaceLogIdentifier identifier, boolean ignoreCache) { + final RaceLog result; if (!ignoreCache && raceLogCache.containsKey(identifier)) { - return raceLogCache.get(identifier); + result = raceLogCache.get(identifier); + } else { + result = domainObjectFactory.loadRaceLog(identifier); + MongoRaceLogStoreVisitor listener = new MongoRaceLogStoreVisitor(identifier, mongoObjectFactory); + listeners.put(result, listener); + result.addListener(listener); + raceLogCache.put(identifier, result); } - RaceLog result = domainObjectFactory.loadRaceLog(identifier); - MongoRaceLogListener listener = new MongoRaceLogListener(identifier, mongoObjectFactory); - listeners.put(result, listener); - result.addListener(listener); - raceLogCache.put(identifier, result); return result; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogListener.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java similarity index 93% rename from java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogListener.java rename to java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java index 46e241fa4b8..0039e14010f 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogListener.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java @@ -21,15 +21,15 @@ import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; -public class MongoRaceLogListener implements RaceLogEventVisitor { +public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { private final RaceLogIdentifier raceLogIdentifier; private final MongoObjectFactoryImpl mongoObjectFactory; private final DBCollection raceLogsCollection; - private final static Logger logger = Logger.getLogger(MongoRaceLogListener.class.getName()); + private final static Logger logger = Logger.getLogger(MongoRaceLogStoreVisitor.class.getName()); - public MongoRaceLogListener(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) { + public MongoRaceLogStoreVisitor(RaceLogIdentifier identifier, MongoObjectFactory mongoObjectFactory) { super(); this.raceLogIdentifier = identifier; this.mongoObjectFactory = (MongoObjectFactoryImpl) mongoObjectFactory; diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java index e77d1e645cd..55ad06f1a26 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java @@ -5,21 +5,19 @@ import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.Serializable; -import java.util.List; +import java.util.Arrays; import org.junit.Test; -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.racelog.analyzing.impl.ConfirmedFinishPositioningListFinder; -@SuppressWarnings("unchecked") public class ConfirmedFinishPositioningListFinderTest extends - PassAwareRaceLogAnalyzerTest>> { + PassAwareRaceLogAnalyzerTest { @Override protected ConfirmedFinishPositioningListFinder createAnalyzer(RaceLog raceLog) { @@ -27,11 +25,10 @@ public class ConfirmedFinishPositioningListFinderTest extends } @Override - protected List> setupTargetEventsForPassAwareTests(int passId) { - RaceLogFinishPositioningConfirmedEvent event = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1, passId); - when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(List.class)); - raceLog.add(event); - return event.getPositionedCompetitorsIDsNamesMaxPointsReasons(); + protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) { + RaceLogFinishPositioningConfirmedEvent event = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1, passId, author); + when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class)); + return new TargetPair(Arrays.asList(event), event.getPositionedCompetitorsIDsNamesMaxPointsReasons()); } @Test @@ -45,7 +42,7 @@ public class ConfirmedFinishPositioningListFinderTest extends public void testMostRecent() { RaceLogFinishPositioningConfirmedEvent event1 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1); RaceLogFinishPositioningConfirmedEvent event2 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 2); - when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(List.class)); + when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class)); raceLog.add(event1); raceLog.add(event2); diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java index 9f12c8c2464..5bd34688cda 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/FinishedTimeFinderTest.java @@ -4,12 +4,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.when; +import java.util.Arrays; + import org.junit.Test; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.analyzing.impl.FinishedTimeFinder; @@ -19,13 +22,12 @@ public class FinishedTimeFinderTest extends PassAwareRaceLogAnalyzerTest { + + @Override + protected GateLineOpeningTimeFinder createAnalyzer(RaceLog raceLog) { + return new GateLineOpeningTimeFinder(raceLog); + } + + @Override + protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) { + RaceLogGateLineOpeningTimeEvent event = createEvent(RaceLogGateLineOpeningTimeEvent.class, 1, passId, author); + when(event.getGateLineOpeningTimes()).thenReturn(new GateLineOpeningTimes(2, 3)); + return new TargetPair(Arrays.asList(event), new GateLineOpeningTimes(2, 3)); + } + + @Test + public void testNullForNone() { + RaceLogEvent event1 = createEvent(RaceLogEvent.class, 1); + raceLog.add(event1); + assertNull(analyzer.analyze()); + } + + @Test + public void testMostRecent() { + RaceLogGateLineOpeningTimeEvent event1 = createEvent(RaceLogGateLineOpeningTimeEvent.class, 1); + when(event1.getGateLineOpeningTimes()).thenReturn(new GateLineOpeningTimes(1, 2)); + RaceLogGateLineOpeningTimeEvent event2 = createEvent(RaceLogGateLineOpeningTimeEvent.class, 2); + when(event2.getGateLineOpeningTimes()).thenReturn(new GateLineOpeningTimes(2, 3)); + + raceLog.add(event1); + raceLog.add(event2); + + assertEquals(event2.getGateLineOpeningTimes(), analyzer.analyze()); + } +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/IndividualRecallFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/IndividualRecallFinderTest.java new file mode 100644 index 00000000000..b235d5d5c38 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/IndividualRecallFinderTest.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.racelog.analyzing.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.junit.Test; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallFinder; + +public class IndividualRecallFinderTest extends PassAwareRaceLogAnalyzerTest { + + @Override + protected TargetPair getTargetEventsAndResultForPassAwareTests( + int passId, RaceLogEventAuthor author) { + RaceLogFlagEvent event = createEvent(RaceLogFlagEvent.class, 1, passId, author); + when(event.getUpperFlag()).thenReturn(Flags.XRAY); + when(event.isDisplayed()).thenReturn(true); + return new TargetPair(Arrays.asList(event), event.getLogicalTimePoint()); + } + + @Override + protected IndividualRecallFinder createAnalyzer(RaceLog raceLog) { + return new IndividualRecallFinder(raceLog) { + @Override + protected boolean isRelevant(RaceLogFlagEvent flagEvent) { + return flagEvent.getUpperFlag().equals(Flags.XRAY) && flagEvent.isDisplayed(); + } + }; + } + + @Test + public void testNullForNone() { + RaceLogEvent event1 = createEvent(RaceLogEvent.class, 1); + raceLog.add(event1); + assertNull(analyzer.analyze()); + } + + @Test + public void testMostRecent() { + RaceLogFlagEvent event1 = createEvent(RaceLogFlagEvent.class, 1); + when(event1.getUpperFlag()).thenReturn(Flags.XRAY); + when(event1.isDisplayed()).thenReturn(true); + RaceLogFlagEvent event2 = createEvent(RaceLogFlagEvent.class, 2); + when(event2.getUpperFlag()).thenReturn(Flags.XRAY); + when(event2.isDisplayed()).thenReturn(true); + + raceLog.add(event1); + raceLog.add(event2); + + assertEquals(event2.getLogicalTimePoint(), analyzer.analyze()); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java index 48bd04fdd32..36badea21b0 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/PassAwareRaceLogAnalyzerTest.java @@ -1,36 +1,113 @@ package com.sap.sailing.domain.racelog.analyzing.test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import java.util.Collection; import java.util.UUID; import org.junit.Test; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; public abstract class PassAwareRaceLogAnalyzerTest, ResultType> extends RaceLogAnalyzerTest { + + protected class TargetPair extends Util.Pair, ResultType> { + private static final long serialVersionUID = 7961879340006633439L; - protected abstract ResultType setupTargetEventsForPassAwareTests(int passId); + public TargetPair(Collection a, ResultType b) { + super(a, b); + } + + public void addAll(RaceLog destination) { + for (RaceLogEvent event : getA()) { + destination.add(event); + } + } + + } + + /** + * Implement in your test to drive some pass- and author-specific tests. + * + * @param passId pass to be used by your events + * @param author author to be used by your events + * @return {@link TargetPair} + */ + protected abstract TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author); + + /** + * Most of the times you do not need to implement this (only when your ResultType is a primitive). + */ + protected TargetPair getBlockingEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) { + return getTargetEventsAndResultForPassAwareTests(passId, author); + } + @Test public void testPassAwareWrongPass() { - ResultType nonExpectedResult = setupTargetEventsForPassAwareTests(0); - RaceLogEvent blockingEvent = createEvent(RaceLogEvent.class, 1, 1, UUID.randomUUID()); + RaceLogEventAuthor author = mock(RaceLogEventAuthor.class); + TargetPair pair = getTargetEventsAndResultForPassAwareTests(0, author); + ResultType nonExpectedResult = pair.getB(); + RaceLogEvent blockingEvent = createEvent(RaceLogEvent.class, 1, 1, UUID.randomUUID(), author); + pair.addAll(raceLog); raceLog.add(blockingEvent); assertNotSame(nonExpectedResult, analyzer.analyze()); } - + @Test public void testPassAwareCorrectPass() { - RaceLogEvent minorEvent = createEvent(RaceLogEvent.class, 0, 0, UUID.randomUUID()); - ResultType expectedResult = setupTargetEventsForPassAwareTests(1); - + RaceLogEventAuthor author = mock(RaceLogEventAuthor.class); + RaceLogEvent minorEvent = createEvent(RaceLogEvent.class, 0, 0, UUID.randomUUID(), author); + TargetPair pair = getTargetEventsAndResultForPassAwareTests(1, author); + ResultType expectedResult = pair.getB(); + + pair.addAll(raceLog); raceLog.add(minorEvent); - assertSame(expectedResult, analyzer.analyze()); + assertEquals(expectedResult, analyzer.analyze()); + } + + @Test + public void testPassAwareBlockedByMajorAuthor() { + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); + + TargetPair minorPair = getTargetEventsAndResultForPassAwareTests(0, minorAuthor); + minorPair.addAll(raceLog); + + TargetPair majorPair = getBlockingEventsAndResultForPassAwareTests(0, majorAuthor); + majorPair.addAll(raceLog); + + assertNotSame(minorPair.getB(), analyzer.analyze()); + assertEquals(majorPair.getB(), analyzer.analyze()); + } + + @Test + public void testPassAwareHidingMinorAuthor() { + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); + + TargetPair majorPair = getTargetEventsAndResultForPassAwareTests(0, majorAuthor); + majorPair.addAll(raceLog); + + TargetPair minorPair = getBlockingEventsAndResultForPassAwareTests(0, minorAuthor); + minorPair.addAll(raceLog); + + assertEquals(majorPair.getB(), analyzer.analyze()); + assertNotSame(minorPair.getB(), analyzer.analyze()); } } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ProtestStartTimeFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ProtestStartTimeFinderTest.java index 5bfd6bfd34d..9775c6aebe3 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ProtestStartTimeFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ProtestStartTimeFinderTest.java @@ -5,11 +5,14 @@ import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Arrays; + import org.junit.Test; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; import com.sap.sailing.domain.racelog.analyzing.impl.ProtestStartTimeFinder; @@ -21,11 +24,10 @@ public class ProtestStartTimeFinderTest extends PassAwareRaceLogAnalyzerTest T createEvent(Class type, long milliseconds, int passId, Serializable id) { + return createEvent(type, milliseconds, passId, id, mock(RaceLogEventAuthor.class)); + } + + protected static T createEvent(Class type, long milliseconds, int passId, RaceLogEventAuthor author) { + return createEvent(type, milliseconds, passId, UUID.randomUUID(), author); + } + + protected static T createEvent(Class type, long milliseconds, int passId, Serializable id, RaceLogEventAuthor author) { T event = mock(type); + when(event.getAuthor()).thenReturn(author); when(event.getPassId()).thenReturn(passId); - when(event.getTimePoint()).thenReturn(new MillisecondsTimePoint(milliseconds)); + when(event.getCreatedAt()).thenReturn(new MillisecondsTimePoint(milliseconds)); + when(event.getLogicalTimePoint()).thenReturn(new MillisecondsTimePoint(milliseconds)); when(event.getId()).thenReturn(id); return event; } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java index 711b123474b..18f5ddf6d07 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceStatusAnalyzerTest.java @@ -1,29 +1,53 @@ package com.sap.sailing.domain.racelog.analyzing.test; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.junit.Test; +import java.util.Arrays; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest { + private RacingProcedure racingProcedure = mock(RacingProcedure.class); + @Override protected RaceStatusAnalyzer createAnalyzer(RaceLog raceLog) { - return new RaceStatusAnalyzer(raceLog); + return new RaceStatusAnalyzer(raceLog, racingProcedure); + } + + @Override + protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, RaceLogEventAuthor author) { + RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author); + when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.RUNNING); + doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class)); + return new TargetPair(Arrays.asList(event), event.getNextStatus()); } @Override - protected RaceLogRaceStatus setupTargetEventsForPassAwareTests(int passId) { - RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId); - when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.RUNNING); - raceLog.add(event); - return event.getNextStatus(); + protected TargetPair getBlockingEventsAndResultForPassAwareTests( + int passId, RaceLogEventAuthor author) { + RaceLogRaceStatusEvent event = createEvent(RaceLogRaceStatusEvent.class, 1, passId, author); + when(event.getNextStatus()).thenReturn(RaceLogRaceStatus.FINISHING); + doAnswer(new StatusVisitorAnswer()).when(event).accept(any(RaceLogEventVisitor.class)); + return new TargetPair(Arrays.asList(event), event.getNextStatus()); } @Test @@ -36,11 +60,99 @@ public class RaceStatusAnalyzerTest extends PassAwareRaceLogAnalyzerTest { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + RaceLogStartTimeEvent event = (RaceLogStartTimeEvent) invocation.getMock(); + RaceLogEventVisitor visitor = (RaceLogEventVisitor) invocation.getArguments()[0]; + visitor.visit(event); + return null; + } + } + + private static class StatusVisitorAnswer implements Answer { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + RaceLogRaceStatusEvent event = (RaceLogRaceStatusEvent) invocation.getMock(); + RaceLogEventVisitor visitor = (RaceLogEventVisitor) invocation.getArguments()[0]; + visitor.visit(event); + return null; + } + } } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/StartTimeFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/StartTimeFinderTest.java index 83a04e8078d..0b0b8df4b40 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/StartTimeFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/StartTimeFinderTest.java @@ -5,11 +5,14 @@ import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Arrays; + import org.junit.Test; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; @@ -19,13 +22,13 @@ public class StartTimeFinderTest extends PassAwareRaceLogAnalyzerTest configuration; + private RaceStateChangedListener listener; + private TimePoint nowMock; + + private RaceState state; + + @Before + public void setUp() { + raceLog = new RaceLogImpl("test-log"); + author = new RaceLogEventAuthorImpl("Test", 1); + factory = RaceLogEventFactory.INSTANCE; + defaultRacingProcedureType = RacingProcedureType.RRS26; + configuration = new EmptyRegattaConfiguration(); + listener = mock(RaceStateChangedListener.class); + nowMock = mock(TimePoint.class); + + state = new RaceStateImpl(raceLog, author, factory, new RacingProcedureFactoryImpl(author, factory, configuration)); + } + + @Test + public void testStateOnEmptyRaceLog() { + assertNull(state.getCourseDesign()); + assertNull(state.getFinishedTime()); + assertNull(state.getFinishingTime()); + assertNull(state.getFinishPositioningList()); + assertNull(state.getProtestTime()); + assertNull(state.getStartTime()); + assertNotNull(state.getRacingProcedure()); + assertEquals(defaultRacingProcedureType, state.getRacingProcedure().getType()); + assertEquals(RaceLogRaceStatus.UNSCHEDULED, state.getStatus()); + assertNull(state.getConfirmedFinishPositioningList()); + } + + @Test + public void testInitialProcedureTypeFallback() throws Exception { + assertEquals(ReadonlyRaceStateImpl.fallbackInitialProcedureType, state.getRacingProcedure().getType()); + } + + @SuppressWarnings("unchecked") + @Test + public void testInitialProcedureTypeConfiguration() throws Exception { + RegattaConfigurationImpl config = new RegattaConfigurationImpl(); + config.setDefaultRacingProcedureType(RacingProcedureType.BASIC); + config.setBasicConfiguration(mock(RacingProcedureConfiguration.class)); + configuration = mock(ConfigurationLoader.class); + when(configuration.load()).thenReturn(config); + state = new RaceStateImpl(raceLog, author, factory, new RacingProcedureFactoryImpl(author, factory, configuration)); + + assertEquals(RacingProcedureType.BASIC, state.getRacingProcedure().getType()); + } + + @SuppressWarnings("unchecked") + @Test + public void testInitialProcedureTypeRaceLog() throws Exception { + RegattaConfigurationImpl config = new RegattaConfigurationImpl(); + config.setDefaultRacingProcedureType(RacingProcedureType.BASIC); + config.setBasicConfiguration(mock(RacingProcedureConfiguration.class)); + config.setESSConfiguration(mock(ESSConfiguration.class)); + configuration = mock(ConfigurationLoader.class); + when(configuration.load()).thenReturn(config); + raceLog.add(new RaceLogStartProcedureChangedEventImpl(nowMock, author, nowMock, "12", null, 0, RacingProcedureType.ESS)); + state = new RaceStateImpl(raceLog, author, factory, new RacingProcedureFactoryImpl(author, factory, configuration)); + + assertEquals(RacingProcedureType.ESS, state.getRacingProcedure().getType()); + } + + @Test + public void testStartTime() { + state.addChangedListener(listener); + + TimePoint startTime = MillisecondsTimePoint.now().plus(60 * 60 * 1000); + state.forceNewStartTime(nowMock, startTime); + + assertEquals(startTime, state.getStartTime()); + assertEquals(RaceLogRaceStatus.SCHEDULED, state.getStatus()); + verify(listener).onStartTimeChanged(state); + verify(listener).onStatusChanged(state); + verifyNoMoreInteractions(listener); + } + + @Test + public void testAdvancePass() { + state.addChangedListener(listener); + int oldPassId = raceLog.getCurrentPassId(); + + state.setAdvancePass(mock(TimePoint.class)); + + assertEquals(oldPassId + 1, raceLog.getCurrentPassId()); + verify(listener).onAdvancePass(state); + verifyNoMoreInteractions(listener); + } + + @Test + public void testAbort() { + state.addChangedListener(listener); + + state.setAborted(mock(TimePoint.class), false, Flags.NONE); + + // TODO: change test when interface is complete + verifyNoMoreInteractions(listener); + } + + @Test + public void testGeneralRecall() { + state.addChangedListener(listener); + + state.setGeneralRecall(mock(TimePoint.class)); + + // TODO: change test when interface is complete + verifyNoMoreInteractions(listener); + } + + @Test + public void testInvalidateAfterAdvancePass() throws InterruptedException { + state.addChangedListener(listener); + + state.forceNewStartTime(nowMock, new MillisecondsTimePoint(1)); + Thread.sleep(100); + state.setFinishedTime(new MillisecondsTimePoint(10)); + state.setCourseDesign(nowMock, mock(CourseBase.class)); + + state.setAdvancePass(mock(TimePoint.class)); + + assertNull(state.getStartTime()); + assertEquals(RaceLogRaceStatus.UNSCHEDULED, state.getStatus()); + verify(listener, times(1)).onAdvancePass(state); + verify(listener, times(2)).onStartTimeChanged(state); + verify(listener, times(2)).onFinishedTimeChanged(state); + verify(listener, times(3)).onStatusChanged(state); + verify(listener, times(1)).onCourseDesignChanged(state); + verifyNoMoreInteractions(listener); + } + + @Test + public void testNewRacingProcedure() throws InterruptedException { + state.addChangedListener(listener); + + RacingProcedure oldProcedure = state.getRacingProcedure(); + assertEquals(defaultRacingProcedureType, oldProcedure.getType()); + + state.setRacingProcedure(nowMock, RacingProcedureType.ESS); + + RacingProcedure newProcedure = state.getRacingProcedure(); + assertEquals(RacingProcedureType.ESS, newProcedure.getType()); + assertFalse(oldProcedure.equals(newProcedure)); + } + + @Test + public void testGetRacingProcedure() throws InterruptedException { + + state.setRacingProcedure(nowMock, RacingProcedureType.RRS26); + + GateStartRacingProcedure procedure = state.getTypedRacingProcedure(GateStartRacingProcedure.class); + assertNull(procedure); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventAuthorTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventAuthorTest.java new file mode 100644 index 00000000000..526a492837e --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventAuthorTest.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.racelog.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; + +public class RaceLogEventAuthorTest { + + @Test + public void testCompareToSame() { + RaceLogEventAuthor author = new RaceLogEventAuthorImpl("", 1); + int result = author.compareTo(author); + + assertEquals(0, result); + } + + @Test + public void testCompareToMinor() { + RaceLogEventAuthor author = new RaceLogEventAuthorImpl("", 1); + RaceLogEventAuthor minor = new RaceLogEventAuthorImpl("", 2); + int result = author.compareTo(minor); + + assertTrue(result > 0); + } + + @Test + public void testCompareToMajor() { + RaceLogEventAuthor author = new RaceLogEventAuthorImpl("", 3); + RaceLogEventAuthor major = new RaceLogEventAuthorImpl("", 2); + int result = author.compareTo(major); + + assertTrue(result < 0); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventComparatorTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventComparatorTest.java index d947d06a9b4..dd1e57d4c92 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventComparatorTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventComparatorTest.java @@ -8,14 +8,17 @@ import static org.mockito.Mockito.when; import org.junit.Before; import org.junit.Test; +import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; public class RaceLogEventComparatorTest { private RaceLogEvent eventOne; private RaceLogEvent eventTwo; + private RaceLogEventAuthor author; private RaceLogEventComparator comparator; @@ -23,13 +26,15 @@ public class RaceLogEventComparatorTest { public void setUp() { eventOne = mock(RaceLogEvent.class); eventTwo = mock(RaceLogEvent.class); + author = mock(RaceLogEventAuthor.class); comparator = RaceLogEventComparator.INSTANCE; } @Test public void testEqualsOnSame() { - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(eventOne.getAuthor()).thenReturn(mock(RaceLogEventAuthor.class)); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); when(eventOne.getId()).thenReturn("a"); int result = comparator.compare(eventOne, eventOne); @@ -37,12 +42,14 @@ public class RaceLogEventComparatorTest { } @Test - public void testSamePassAndSameTimeSameId() { + public void testSamePassAndSameAuthorAndSameTimeAndSameId() { when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventOne.getId()).thenReturn("a"); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventTwo.getId()).thenReturn("a"); int result = comparator.compare(eventOne, eventTwo); @@ -50,12 +57,14 @@ public class RaceLogEventComparatorTest { } @Test - public void testSamePassAndSameTimeDifferentId() { + public void testSamePassAndSameAuthorAndSameTimeButDifferentId() { when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventOne.getId()).thenReturn("a"); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventTwo.getId()).thenReturn("b"); int result = comparator.compare(eventOne, eventTwo); @@ -63,22 +72,58 @@ public class RaceLogEventComparatorTest { } @Test - public void testSamePassLowerHigherTime() { + public void testSamePassAndSameAuthorButLowerHigherTime() { when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); int result = comparator.compare(eventOne, eventTwo); assertTrue(result < 0); } @Test - public void testSamePassHigherLowerTime() { + public void testSamePassAndSameAuthorButHigherLowerTime() { when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); + + int result = comparator.compare(eventOne, eventTwo); + assertTrue(result > 0); + } + + @Test + public void testSamePassButLowerHigherAuthor() { + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); + + when(eventOne.getPassId()).thenReturn(0); + when(eventOne.getAuthor()).thenReturn(minorAuthor); + when(eventTwo.getPassId()).thenReturn(0); + when(eventTwo.getAuthor()).thenReturn(majorAuthor); + + int result = comparator.compare(eventOne, eventTwo); + assertTrue(result < 0); + } + + @Test + public void testSamePassButHigherLowerAuthor() { + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); + + when(eventOne.getPassId()).thenReturn(0); + when(eventOne.getAuthor()).thenReturn(majorAuthor); + when(eventTwo.getPassId()).thenReturn(0); + when(eventTwo.getAuthor()).thenReturn(minorAuthor); int result = comparator.compare(eventOne, eventTwo); assertTrue(result > 0); @@ -87,9 +132,9 @@ public class RaceLogEventComparatorTest { @Test public void testPassLowerHigher() { when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); when(eventTwo.getPassId()).thenReturn(1); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); int result = comparator.compare(eventOne, eventTwo); assertTrue(result < 0); @@ -98,12 +143,23 @@ public class RaceLogEventComparatorTest { @Test public void testPassHigherLower() { when(eventOne.getPassId()).thenReturn(1); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); int result = comparator.compare(eventOne, eventTwo); assertTrue(result > 0); } + @Test + public void testFallbackForTimed() { + when(eventOne.getPassId()).thenReturn(1); + when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + Timed timed = mock(Timed.class); + when(timed.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); + + int result = comparator.compare(eventOne, timed); + assertTrue(result < 0); + } + } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventImplTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventImplTest.java new file mode 100644 index 00000000000..959470e5581 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogEventImplTest.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.racelog.test; + +import static junit.framework.Assert.assertSame; +import static org.mockito.Mockito.mock; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.impl.RaceLogEventImpl; + +public class RaceLogEventImplTest { + + public void testGetTimePointRedirection() { + TimePoint createdAt = mock(TimePoint.class); + TimePoint logicalTimePoint = mock(TimePoint.class); + + RaceLogEvent event = new TestRaceLogEvent(createdAt, null, logicalTimePoint, null, null, 0); + assertSame(createdAt, event.getCreatedAt()); + assertSame(logicalTimePoint, event.getTimePoint()); + assertSame(createdAt, event.getLogicalTimePoint()); + } + + private static class TestRaceLogEvent extends RaceLogEventImpl { + private static final long serialVersionUID = -5856509136541176818L; + + public TestRaceLogEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId) { + super(createdAt, author, logicalTimePoint, pId, pInvolvedBoats, pPassId); + } + + @Override + public void accept(RaceLogEventVisitor visitor) { } + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java index ce1773c8a6f..ff074f84b98 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/test/RaceLogTest.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.racelog.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -9,17 +11,24 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.UUID; + import org.junit.Before; import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; import com.sap.sailing.domain.racelog.impl.RaceLogImpl; public class RaceLogTest { @@ -59,15 +68,17 @@ public class RaceLogTest { } @Test - public void testAddEventDifferentPassSameTimePoint() { + public void testAddEventDifferentPassButSameTimePoint() { RaceLogEvent eventOne = mock(RaceLogEvent.class); RaceLogEvent eventTwo = mock(RaceLogEvent.class); when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventOne.getAuthor()).thenReturn(mock(RaceLogEventAuthor.class)); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventOne.getId()).thenReturn("a"); when(eventTwo.getPassId()).thenReturn(1); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventTwo.getAuthor()).thenReturn(mock(RaceLogEventAuthor.class)); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventTwo.getId()).thenReturn("b"); assertTrue(raceLog.add(eventOne)); @@ -80,15 +91,18 @@ public class RaceLogTest { } @Test - public void testAddEventSamePassSameTimePointDifferentId() { + public void testAddEventSamePassAndSameAuthorAndSameTimePointButDifferentId() { RaceLogEvent eventOne = mock(RaceLogEvent.class); RaceLogEvent eventTwo = mock(RaceLogEvent.class); + RaceLogEventAuthor author = mock(RaceLogEventAuthor.class); when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventOne.getId()).thenReturn("a"); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventTwo.getId()).thenReturn("b"); assertTrue(raceLog.add(eventOne)); @@ -101,15 +115,18 @@ public class RaceLogTest { } @Test - public void testWontAddEventSamePassSameTimePointSameId() { + public void testWontAddEventSamePassAndSameAuthorAndSameTimePointAndSameId() { RaceLogEvent eventOne = mock(RaceLogEvent.class); RaceLogEvent eventTwo = mock(RaceLogEvent.class); + RaceLogEventAuthor author = mock(RaceLogEventAuthor.class); when(eventOne.getPassId()).thenReturn(0); - when(eventOne.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventOne.getAuthor()).thenReturn(author); + when(eventOne.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventOne.getId()).thenReturn("a"); when(eventTwo.getPassId()).thenReturn(0); - when(eventTwo.getTimePoint()).thenReturn(new MillisecondsTimePoint(0)); + when(eventTwo.getAuthor()).thenReturn(author); + when(eventTwo.getCreatedAt()).thenReturn(new MillisecondsTimePoint(0)); when(eventTwo.getId()).thenReturn("a"); assertTrue(raceLog.add(eventOne)); @@ -146,24 +163,54 @@ public class RaceLogTest { verify(listener, never()).visit(event); } + @Test + public void testAddListenerWhileNotifying() { + RaceLogFlagEvent event = mock(RaceLogFlagEvent.class); + RaceLogEventVisitor listener = mock(RaceLogEventVisitor.class); + final RaceLogEventVisitor dynamicListener = mock(RaceLogEventVisitor.class); + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + raceLog.addListener(dynamicListener); + return null; + } + }).when(event).accept(listener); + + raceLog.addListener(listener); + raceLog.add(event); + + assertEquals(2, Util.size(raceLog.getAllListeners())); + assertTrue(Util.contains(raceLog.getAllListeners(), listener)); + assertTrue(Util.contains(raceLog.getAllListeners(), dynamicListener)); + verify(event).accept(listener); + verify(event, never()).accept(dynamicListener); + } + @Test public void testRawFixesDescending() { RaceLogEvent event1 = mock(RaceLogEvent.class); RaceLogEvent event2 = mock(RaceLogEvent.class); RaceLogEvent event3 = mock(RaceLogEvent.class); + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); - when(event1.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); - when(event2.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); - when(event3.getTimePoint()).thenReturn(new MillisecondsTimePoint(3)); + when(event1.getAuthor()).thenReturn(majorAuthor); + when(event1.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); + when(event2.getAuthor()).thenReturn(minorAuthor); + when(event2.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); + when(event3.getAuthor()).thenReturn(minorAuthor); + when(event3.getCreatedAt()).thenReturn(new MillisecondsTimePoint(3)); raceLog.add(event1); raceLog.add(event2); raceLog.add(event3); raceLog.lockForRead(); - assertEquals(event3, Util.get(raceLog.getRawFixesDescending(), 0)); - assertEquals(event2, Util.get(raceLog.getRawFixesDescending(), 1)); - assertEquals(event1, Util.get(raceLog.getRawFixesDescending(), 2)); + assertEquals(event1, Util.get(raceLog.getRawFixesDescending(), 0)); + assertEquals(event3, Util.get(raceLog.getRawFixesDescending(), 1)); + assertEquals(event2, Util.get(raceLog.getRawFixesDescending(), 2)); raceLog.unlockAfterRead(); } @@ -172,23 +219,79 @@ public class RaceLogTest { RaceLogEvent event1 = mock(RaceLogEvent.class); RaceLogEvent event2 = mock(RaceLogEvent.class); RaceLogEvent event3 = mock(RaceLogEvent.class); + RaceLogEvent event4 = mock(RaceLogEvent.class); + RaceLogEventAuthor minorAuthor = mock(RaceLogEventAuthor.class); + RaceLogEventAuthor majorAuthor = mock(RaceLogEventAuthor.class); + when(minorAuthor.compareTo(majorAuthor)).thenReturn(-1); + when(majorAuthor.compareTo(minorAuthor)).thenReturn(1); when(event1.getPassId()).thenReturn(1); - when(event1.getTimePoint()).thenReturn(new MillisecondsTimePoint(1)); + when(event1.getAuthor()).thenReturn(majorAuthor); + when(event1.getCreatedAt()).thenReturn(new MillisecondsTimePoint(1)); when(event2.getPassId()).thenReturn(1); - when(event2.getTimePoint()).thenReturn(new MillisecondsTimePoint(2)); - when(event3.getPassId()).thenReturn(0); - when(event3.getTimePoint()).thenReturn(new MillisecondsTimePoint(3)); + when(event2.getAuthor()).thenReturn(minorAuthor); + when(event2.getCreatedAt()).thenReturn(new MillisecondsTimePoint(2)); + when(event3.getPassId()).thenReturn(1); + when(event3.getAuthor()).thenReturn(minorAuthor); + when(event3.getCreatedAt()).thenReturn(new MillisecondsTimePoint(3)); + when(event4.getPassId()).thenReturn(0); + when(event4.getAuthor()).thenReturn(majorAuthor); + when(event4.getCreatedAt()).thenReturn(new MillisecondsTimePoint(4)); raceLog.add(event1); raceLog.add(event2); raceLog.add(event3); + raceLog.add(event4); raceLog.lockForRead(); - assertEquals(event2, Util.get(raceLog.getFixesDescending(), 0)); - assertEquals(event1, Util.get(raceLog.getFixesDescending(), 1)); + assertEquals(3, Util.size(raceLog.getFixesDescending())); + assertEquals(event1, Util.get(raceLog.getFixesDescending(), 0)); + assertEquals(event3, Util.get(raceLog.getFixesDescending(), 1)); + assertEquals(event2, Util.get(raceLog.getFixesDescending(), 2)); raceLog.unlockAfterRead(); } + private RaceLogEventAuthor author = new RaceLogEventAuthorImpl("Test Author", 1); + + @Test + public void testAddingEventsFromMultipleClients() { + RaceLog raceLog = new RaceLogImpl("RaceLogTest", "test-identifier"); + UUID client1Id = UUID.randomUUID(); + UUID client2Id = UUID.randomUUID(); + final MillisecondsTimePoint now = MillisecondsTimePoint.now(); + RaceLogStartTimeEvent startTimeEvent1 = RaceLogEventFactory.INSTANCE.createStartTimeEvent(now, author, 1, now.plus(1)); + Iterable empty = raceLog.add(startTimeEvent1, client1Id); + assertTrue(Util.isEmpty(empty)); + RaceLogStartTimeEvent startTimeEvent2 = RaceLogEventFactory.INSTANCE.createStartTimeEvent(now.plus(2), author, 1, now.plus(3)); + Iterable nonEmpty = raceLog.add(startTimeEvent2, client2Id); + assertEquals(1, Util.size(nonEmpty)); + assertSame(startTimeEvent1, nonEmpty.iterator().next()); + RaceLogStartTimeEvent startTimeEvent3 = RaceLogEventFactory.INSTANCE.createStartTimeEvent(now.plus(4), author, 1, now.plus(5)); + Iterable nonEmpty2 = raceLog.add(startTimeEvent3, client1Id); + assertEquals(1, Util.size(nonEmpty2)); + assertSame(startTimeEvent2, nonEmpty2.iterator().next()); + + } + + @Test() + public void testGetFirstFixAfterNew() { + + TimePoint createdAtEventOne = new MillisecondsTimePoint(100); + RaceLogEvent eventOne = mock(RaceLogEvent.class); + when(eventOne.getAuthor()).thenReturn(mock(RaceLogEventAuthor.class)); + when(eventOne.getCreatedAt()).thenReturn(createdAtEventOne); + when(eventOne.getTimePoint()).thenReturn(createdAtEventOne); + raceLog.add(eventOne); + + TimePoint createdAtEventTwo = new MillisecondsTimePoint(99); + RaceLogEvent eventTwo = mock(RaceLogEvent.class); + when(eventTwo.getAuthor()).thenReturn(mock(RaceLogEventAuthor.class)); + when(eventTwo.getCreatedAt()).thenReturn(createdAtEventTwo); + when(eventTwo.getTimePoint()).thenReturn(createdAtEventTwo); + raceLog.add(eventTwo); + + assertNull(raceLog.getFirstFixAfter(new MillisecondsTimePoint(100))); + assertEquals(eventOne, raceLog.getFirstFixAfter(new MillisecondsTimePoint(99))); + } private class VisitFlagEventAnswer implements Answer { @Override diff --git a/mobile/com.sap.sailing.racecommittee.app.test/.gitignore b/java/com.sap.sailing.domain.shared.android/.gitignore old mode 100755 new mode 100644 similarity index 100% rename from mobile/com.sap.sailing.racecommittee.app.test/.gitignore rename to java/com.sap.sailing.domain.shared.android/.gitignore diff --git a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF index 0a26e77fc02..d4dca443728 100644 --- a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF @@ -7,12 +7,25 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Require-Bundle: com.sap.sailing.domain.common;bundle-version="1.0.0" Export-Package: com.sap.sailing.domain.base, + com.sap.sailing.domain.base.configuration, + com.sap.sailing.domain.base.configuration.impl, + com.sap.sailing.domain.base.configuration.procedures, com.sap.sailing.domain.base.impl, com.sap.sailing.domain.base.racegroup, com.sap.sailing.domain.base.racegroup.impl, com.sap.sailing.domain.racelog, com.sap.sailing.domain.racelog.analyzing.impl, com.sap.sailing.domain.racelog.impl, + com.sap.sailing.domain.racelog.state, + com.sap.sailing.domain.racelog.state.impl, + com.sap.sailing.domain.racelog.state.racingprocedure, + com.sap.sailing.domain.racelog.state.racingprocedure.ess, + com.sap.sailing.domain.racelog.state.racingprocedure.ess.impl, + com.sap.sailing.domain.racelog.state.racingprocedure.gate, + com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl, + com.sap.sailing.domain.racelog.state.racingprocedure.impl, + com.sap.sailing.domain.racelog.state.racingprocedure.rrs26, + com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, com.sap.sailing.util.impl diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java index 35550cb35db..b078bf8162c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/Competitor.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; @@ -7,4 +8,6 @@ public interface Competitor extends Named, WithID, IsManagedBySharedDomainFactor Team getTeam(); Boat getBoat(); + + Color getColor(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorFactory.java new file mode 100644 index 00000000000..167bc2080ff --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorFactory.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.base; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.impl.DynamicBoat; +import com.sap.sailing.domain.base.impl.DynamicTeam; +import com.sap.sailing.domain.common.Color; + +/** + * Base interface for classes managing a set of {@link Competitor} objects. + */ +public interface CompetitorFactory { + /** + * If a valid competitor is returned and the caller has information available that could be used to update the competitor, + * the caller must check the result of {@link #isCompetitorToUpdateDuringGetOrCreate(Competitor)}, and if true, + * must call {@link #getOrCreateCompetitor(Serializable, String, DynamicTeam, DynamicBoat)} to cause an update of the + * competitor's values. + */ + Competitor getExistingCompetitorById(Serializable competitorId); + + /** + * Checks if the competitor shall be updated from the default provided by, e.g., a tracking infrastructure. + * Callers of {@link #getExistingCompetitorById(Serializable)} or {@link #getExistingCompetitorByIdAsString(String)} + * must call this method in case they retrieve a valid competitor by ID and have data available that can be used to update + * the competitor. + */ + boolean isCompetitorToUpdateDuringGetOrCreate(Competitor result); + + Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/CompetitorStore.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java similarity index 72% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/CompetitorStore.java rename to java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java index 0b2a1b80c50..31b1170f772 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/CompetitorStore.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java @@ -4,6 +4,7 @@ import java.io.Serializable; import com.sap.sailing.domain.base.impl.DynamicBoat; import com.sap.sailing.domain.base.impl.DynamicTeam; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.dto.CompetitorDTO; /** @@ -13,14 +14,7 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO; * @author Axel Uhl (d043530) * */ -public interface CompetitorStore { - /** - * If a valid competitor is returned and the caller has information available that could be used to update the competitor, - * the caller must check the result of {@link #isCompetitorToUpdateDuringGetOrCreate(Competitor)}, and if true, - * must call {@link #getOrCreateCompetitor(Serializable, String, DynamicTeam, DynamicBoat)} to cause an update of the - * competitor's values. - */ - Competitor getExistingCompetitorById(Serializable competitorId); +public interface CompetitorStore extends CompetitorFactory { /** * If a valid competitor is returned and the caller has information available that could be used to update the competitor, @@ -29,16 +23,6 @@ public interface CompetitorStore { * competitor's values. */ Competitor getExistingCompetitorByIdAsString(String idAsString); - - /** - * Checks if the competitor shall be updated from the default provided by, e.g., a tracking infrastructure. - * Callers of {@link #getExistingCompetitorById(Serializable)} or {@link #getExistingCompetitorByIdAsString(String)} - * must call this method in case they retrieve a valid competitor by ID and have data available that can be used to update - * the competitor. - */ - boolean isCompetitorToUpdateDuringGetOrCreate(Competitor result); - - Competitor getOrCreateCompetitor(Serializable competitorId, String name, DynamicTeam team, DynamicBoat boat); /** * When a competitor is queried using {@link #getOrCreateCompetitor(Serializable, String, DynamicTeam, DynamicBoat)} @@ -75,7 +59,7 @@ public interface CompetitorStore { * * If no competitor with the ID requested is found, the call is a no-op, doing nothing, not even throwing an exception. */ - Competitor updateCompetitor(String idAsString, String newName, String newSailId, Nationality newNationality); + Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, Nationality newNationality); CompetitorDTO convertToCompetitorDTO(Competitor c); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java index bd90d7a8be7..c170a53ecb7 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CourseArea.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.base; +import java.util.UUID; + import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.WithID; @@ -11,5 +13,5 @@ import com.sap.sailing.domain.common.WithID; * */ public interface CourseArea extends Named, WithID, IsManagedBySharedDomainFactory { - + UUID getId(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java index bdc259d3d59..42b6c4d5fec 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/SharedDomainFactory.java @@ -1,15 +1,15 @@ package com.sap.sailing.domain.base; import java.io.Serializable; +import java.util.List; import java.util.UUID; -import com.sap.sailing.domain.base.impl.DynamicBoat; -import com.sap.sailing.domain.base.impl.DynamicCompetitor; -import com.sap.sailing.domain.base.impl.DynamicTeam; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; -public interface SharedDomainFactory { +public interface SharedDomainFactory extends CompetitorFactory { /** * Looks up or, if not found, creates a {@link Nationality} object and re-uses threeLetterIOCCode also as the @@ -76,33 +76,23 @@ public interface SharedDomainFactory { BoatClass getOrCreateBoatClass(String name); /** - * If a valid competitor is returned and the caller has information available that could be used to update the competitor, - * the caller must check the result of {@link #isCompetitorToUpdateDuringGetOrCreate(Competitor)}, and if true, - * must call {@link #getOrCreateCompetitor(Serializable, String, DynamicTeam, DynamicBoat)} to cause an update of the - * competitor's values. + * Gets the {@link CompetitorStore} of this {@link SharedDomainFactory}. */ - Competitor getExistingCompetitorById(Serializable competitorId); - + CompetitorStore getCompetitorStore(); + /** - * Checks if the competitor shall be updated from the default provided by, e.g., a tracking infrastructure. - * Callers of {@link #getExistingCompetitorById(Serializable)} or {@link #getExistingCompetitorByIdAsString(String)} - * must call this method in case they retrieve a valid competitor by ID and have data available that can be used to update - * the competitor. + * If a {@link CourseArea} with the given id already exists, it is returned. Otherwise a new {@link CourseArea} + * is created. */ - boolean isCompetitorToUpdateDuringGetOrCreate(Competitor result); - - Competitor getOrCreateCompetitor(Serializable competitorId, String name, DynamicTeam team, DynamicBoat boat); + CourseArea getOrCreateCourseArea(UUID id, String name); /** - * Same as {@link #getOrCreateCompetitor(Serializable, String, DynamicTeam, DynamicBoat)} but returns the writable - * specialization of the {@link Competitor} interface. Use with care because the setters offered by the Dynamic... - * subinterfaces don't automatically replicate or store the changes. + * Gets the {@link CourseArea} with passed id; if there is no such {@link CourseArea} null will be returned. */ - DynamicCompetitor getOrCreateDynamicCompetitor(UUID competitorId, String name, DynamicTeam team, DynamicBoat boat); - - CourseArea getOrCreateCourseArea(Serializable courseAreaId, String name); - CourseArea getExistingCourseAreaById(Serializable courseAreaId); - - + + /** + * Hm. + */ + DeviceConfigurationMatcher getOrCreateDeviceConfigurationMatcher(DeviceConfigurationMatcherType type, List clientIdentifiers); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/ConfigurationLoader.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/ConfigurationLoader.java new file mode 100644 index 00000000000..864fd3805d7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/ConfigurationLoader.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.base.configuration; + + +/** + * Capable of loading and storing a {@link ConfigurationType}. + */ +public interface ConfigurationLoader { + + /** + * Loads the configuration from its database. + * @return A immutable {@link ConfigurationType}. + */ + ConfigurationType load(); + + + /** + * Saves the configuration to its database. + */ + void store(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfiguration.java new file mode 100644 index 00000000000..607839e8836 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfiguration.java @@ -0,0 +1,35 @@ +package com.sap.sailing.domain.base.configuration; + +import java.io.Serializable; +import java.util.List; + +/** + * Interface holding information about tablet's (RCApp) configuration. + */ +public interface DeviceConfiguration extends Serializable { + + /** + * Course area names this tablet is allowed to log on. + */ + List getAllowedCourseAreaNames(); + + /** + * She is getting the result mails. + */ + String getResultsMailRecipient(); + + /** + * Course names allowed in the By-Name-Course-Designer + */ + List getByNameCourseDesignerCourseNames(); + + /** + * Default configuration for all races without a custom {@link RegattaConfiguration} + */ + RegattaConfiguration getRegattaConfiguration(); + + /** + * Copy me. + */ + DeviceConfiguration copy(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationIdentifier.java new file mode 100644 index 00000000000..3ba79ef24db --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationIdentifier.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.base.configuration; + +import java.io.Serializable; + +/** + * Each tablet configuration is bound to a specific client. Use this interface instead of a plain {@link String} to + * enable additions (like event-specific configurations). + */ +public interface DeviceConfigurationIdentifier extends Serializable { + String getClientIdentifier(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationMatcher.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationMatcher.java new file mode 100644 index 00000000000..e5faab2f50b --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/DeviceConfigurationMatcher.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.base.configuration; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMapImpl; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; + +/** + *

+ * Classes implementing this interface are matching {@link DeviceConfigurationIdentifier}s to associate them with a + * {@link DeviceConfiguration}. + *

+ * + *

The rank (see {@link DeviceConfigurationMatcher#getMatchingRank()}) is used as a priority when multiple matchers + * are matching a {@link DeviceConfigurationIdentifier} (see also {@link DeviceConfigurationMapImpl}) (lower wins).

+ */ +public interface DeviceConfigurationMatcher extends IsManagedBySharedDomainFactory, Serializable { + + /** + * Returns the kind of matching used by this matcher. + * + * @return the {@link Type}. + */ + DeviceConfigurationMatcherType getMatcherType(); + + /** + * Gets the matcher's rank. + * + * @return the rank. + */ + int getMatchingRank(); + + /** + * Returns true if this {@link DeviceConfigurationMatcher} matches the given + * {@link DeviceConfigurationIdentifier}; otherwise false. + * + * @param identifier + */ + boolean matches(DeviceConfigurationIdentifier identifier); + + /** + * Gets this matcher's identifier. + * + * @return the identifier. + */ + Serializable getMatcherIdentifier(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java new file mode 100644 index 00000000000..75108585928 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RacingProcedureConfiguration.java @@ -0,0 +1,31 @@ +package com.sap.sailing.domain.base.configuration; + +import java.io.Serializable; + +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + +/** + * Interface holding configuration options common to all {@link RacingProcedure}s. + */ +public interface RacingProcedureConfiguration extends Serializable { + + /** + * Gets the class flag to be used by the {@link RacingProcedure}. + */ + Flags getClassFlag(); + + /** + * Indicates whether the {@link RacingProcedure} should allow individual recall or not. + */ + Boolean hasInidividualRecall(); + + /** + * Merges this {@link RacingProcedureConfiguration} with the passed configuration, returning a copy with all + * non-null fields of the passed configuration set to the passed configuration's value. + * + * @return a merged copy. + */ + RacingProcedureConfiguration merge(RacingProcedureConfiguration update); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RegattaConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RegattaConfiguration.java new file mode 100644 index 00000000000..46deb21d3dd --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/RegattaConfiguration.java @@ -0,0 +1,63 @@ +package com.sap.sailing.domain.base.configuration; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.configuration.procedures.ESSConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; +import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + +/** + * Interface holding configuration options for a Regatta, including configurations for + * all types of a {@link RacingProcedure}s. + */ +public interface RegattaConfiguration extends Serializable, Cloneable { + + /** + * Gets the default {@link CourseDesignerMode} for races configured. + */ + CourseDesignerMode getDefaultCourseDesignerMode(); + + /** + * Gets the default {@link RacingProcedureType} for races configured. + */ + RacingProcedureType getDefaultRacingProcedureType(); + + /** + * Gets the configuration for RRS26 races. + * @return null if there is no configuration. + */ + RRS26Configuration getRRS26Configuration(); + + /** + * Gets the configuration for Gate Start races. + * @return null if there is no configuration. + */ + GateStartConfiguration getGateStartConfiguration(); + + /** + * Gets the configuration for ESS races. + * @return null if there is no configuration. + */ + ESSConfiguration getESSConfiguration(); + + /** + * Gets the configuration for Basic races. + * @return null if there is no configuration. + */ + RacingProcedureConfiguration getBasicConfiguration(); + + /** + * Copy me. + */ + RegattaConfiguration clone(); + + /** + * Creates a copied {@link RegattaConfiguration} with all + * non-null fields taken from the passed {@link RegattaConfiguration}. + */ + RegattaConfiguration merge(RegattaConfiguration update); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationIdentifierImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationIdentifierImpl.java new file mode 100644 index 00000000000..3ca6aa1dc99 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationIdentifierImpl.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; + +public class DeviceConfigurationIdentifierImpl implements DeviceConfigurationIdentifier { + + private static final long serialVersionUID = 2785847392322741468L; + + private final String clientIdentifier; + + public DeviceConfigurationIdentifierImpl(String clientIdentifier) { + this.clientIdentifier = clientIdentifier; + } + + @Override + public String getClientIdentifier() { + return clientIdentifier; + } + + @Override + public String toString() { + return "DeviceConfigurationIdentifierImpl [clientIdentifier=" + clientIdentifier + "]"; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationImpl.java new file mode 100644 index 00000000000..076f5599356 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationImpl.java @@ -0,0 +1,65 @@ +package com.sap.sailing.domain.base.configuration.impl; +import java.util.List; + +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; + +public class DeviceConfigurationImpl implements DeviceConfiguration { + + private static final long serialVersionUID = 6084215932610324314L; + + private RegattaConfiguration regattaConfiguration; + + private List allowedCourseAreaNames; + private String resultsMailRecipient; + private List byNameDesignerCourseNames; + + public DeviceConfigurationImpl(RegattaConfiguration regattaConfiguration) { + this.regattaConfiguration = regattaConfiguration; + } + + public void setRegattaConfiguration(RegattaConfiguration proceduresConfiguration) { + this.regattaConfiguration = proceduresConfiguration; + } + + @Override + public RegattaConfiguration getRegattaConfiguration() { + return regattaConfiguration; + } + + @Override + public List getAllowedCourseAreaNames() { + return allowedCourseAreaNames; + } + + public void setAllowedCourseAreaNames(List newAllowedCourseAreaNames) { + this.allowedCourseAreaNames = newAllowedCourseAreaNames; + } + + @Override + public String getResultsMailRecipient() { + return resultsMailRecipient; + } + + public void setResultsMailRecipient(String resultsMailRecipient) { + this.resultsMailRecipient = resultsMailRecipient; + } + + @Override + public List getByNameCourseDesignerCourseNames() { + return byNameDesignerCourseNames; + } + + public void setByNameDesignerCourseNames(List byNameDesignerCourseNames) { + this.byNameDesignerCourseNames = byNameDesignerCourseNames; + } + + public DeviceConfiguration copy() { + DeviceConfigurationImpl copyConfiguration = new DeviceConfigurationImpl(regattaConfiguration.clone()); + copyConfiguration.setAllowedCourseAreaNames(allowedCourseAreaNames); + copyConfiguration.setByNameDesignerCourseNames(byNameDesignerCourseNames); + copyConfiguration.setResultsMailRecipient(resultsMailRecipient); + return copyConfiguration; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMapImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMapImpl.java new file mode 100644 index 00000000000..fe420eb5a44 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMapImpl.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.sap.sailing.domain.base.configuration.DeviceConfiguration; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; + +/** + *

+ * Helper class providing a {@link Map} of {@link DeviceConfigurationMatcher}s associated with their + * {@link DeviceConfiguration}. + *

+ * + *

+ * When searching for a match, the highest ranked {@link DeviceConfigurationMatcher} (i.e. lowest number) wins. + *

+ */ +public class DeviceConfigurationMapImpl extends ConcurrentHashMap { + + private static final long serialVersionUID = -8009136964737670452L; + + private interface Action { + DeviceConfiguration call(DeviceConfigurationMatcher matcher); + } + + public DeviceConfiguration getByMatch(DeviceConfigurationIdentifier key) { + return doByMatch(key, new Action() { + @Override + public DeviceConfiguration call(DeviceConfigurationMatcher match) { + return get(match); + } + }); + } + + public DeviceConfiguration removeByMatch(DeviceConfigurationIdentifier key) { + return doByMatch(key, new Action() { + @Override + public DeviceConfiguration call(DeviceConfigurationMatcher match) { + return remove(match); + } + }); + } + + private DeviceConfiguration doByMatch(DeviceConfigurationIdentifier key, Action action) { + DeviceConfigurationMatcher match = null; + for (Entry entry : entrySet()) { + if (entry.getKey().matches(key)) { + if (match == null || match.getMatchingRank() > entry.getKey().getMatchingRank()) { + match = entry.getKey(); + } + } + } + if (match != null) { + return action.call(match); + } else { + return null; + } + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherMulti.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherMulti.java new file mode 100644 index 00000000000..8bde8674115 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherMulti.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; +import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; + +/** + * Matches a set of {@link DeviceConfigurationIdentifier}s. + */ +public class DeviceConfigurationMatcherMulti implements DeviceConfigurationMatcher { + + private static final long serialVersionUID = 2372299957258702516L; + + private final List clientIdentifiers; + + public DeviceConfigurationMatcherMulti(List matchingClientIdentifiers) { + this.clientIdentifiers = new ArrayList(matchingClientIdentifiers); + Collections.sort(this.clientIdentifiers); + } + + @Override + public DeviceConfigurationMatcherType getMatcherType() { + return DeviceConfigurationMatcherType.MULTI; + } + + @Override + public int getMatchingRank() { + return getMatcherType().getRank(); + } + + @Override + public boolean matches(DeviceConfigurationIdentifier identifier) { + return this.clientIdentifiers.contains(identifier.getClientIdentifier()); + } + + public Iterable getClientIdentifiers() { + return clientIdentifiers; + } + + @Override + public Serializable getMatcherIdentifier() { + return String.format("%s%s", DeviceConfigurationMatcherMulti.class.getSimpleName(), clientIdentifiers.toString()); + } + + @Override + public String toString() { + return "DeviceConfigurationMatcherMulti [clientIdentifiers=" + clientIdentifiers + "]"; + } + + @Override + public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) { + return domainFactory.getOrCreateDeviceConfigurationMatcher(getMatcherType(), clientIdentifiers); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherSingle.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherSingle.java new file mode 100644 index 00000000000..429a180a1f9 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/DeviceConfigurationMatcherSingle.java @@ -0,0 +1,58 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import java.io.Serializable; +import java.util.Arrays; + +import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; +import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationIdentifier; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; + +/** + * Matches a single {@link DeviceConfigurationIdentifier}. + */ +public class DeviceConfigurationMatcherSingle implements DeviceConfigurationMatcher { + + private static final long serialVersionUID = -3662453614818373801L; + private final String clientIdentifier; + + public DeviceConfigurationMatcherSingle(String matchingClientIdentifier) { + this.clientIdentifier = matchingClientIdentifier; + } + + @Override + public boolean matches(DeviceConfigurationIdentifier identifier) { + return this.clientIdentifier.equals(identifier.getClientIdentifier()); + } + + @Override + public DeviceConfigurationMatcherType getMatcherType() { + return DeviceConfigurationMatcherType.SINGLE; + } + + @Override + public int getMatchingRank() { + return getMatcherType().getRank(); + } + + public String getClientIdentifier() { + return clientIdentifier; + } + + @Override + public Serializable getMatcherIdentifier() { + return String.format("%s%s", DeviceConfigurationMatcherSingle.class.getSimpleName(), clientIdentifier); + } + + @Override + public String toString() { + return "DeviceConfigurationMatcherSingle [clientIdentifier=" + clientIdentifier + "]"; + } + + @Override + public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) { + return domainFactory.getOrCreateDeviceConfigurationMatcher(getMatcherType(), Arrays.asList(clientIdentifier)); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/ESSConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/ESSConfigurationImpl.java new file mode 100644 index 00000000000..5158ab9a7f5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/ESSConfigurationImpl.java @@ -0,0 +1,21 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.ESSConfiguration; + +public class ESSConfigurationImpl extends RacingProcedureConfigurationImpl implements ESSConfiguration { + + private static final long serialVersionUID = 5406098147408723197L; + + protected ESSConfiguration copy() { + ESSConfigurationImpl copy = (ESSConfigurationImpl) super.copy(new ESSConfigurationImpl()); + return copy; + } + + @Override + public RacingProcedureConfiguration merge(RacingProcedureConfiguration update) { + ESSConfigurationImpl target = (ESSConfigurationImpl) super.merge(update); + return target; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java new file mode 100644 index 00000000000..f9832609406 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/EmptyRegattaConfiguration.java @@ -0,0 +1,32 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; + +/** + * Is empty, does nothing on store and load. + */ +public class EmptyRegattaConfiguration extends RegattaConfigurationImpl implements + ConfigurationLoader { + + private static final long serialVersionUID = -4187341706420504456L; + + @Override + public RegattaConfiguration load() { + setDefaultRacingProcedureType(RacingProcedureType.UNKNOWN); + setDefaultCourseDesignerMode(CourseDesignerMode.UNKNOWN); + setRRS26Configuration(new RRS26ConfigurationImpl()); + setGateStartConfiguration(new GateStartConfigurationImpl()); + setESSConfiguration(new ESSConfigurationImpl()); + setBasicConfiguration(new RacingProcedureConfigurationImpl()); + return clone(); + } + + @Override + public void store() { + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/GateStartConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/GateStartConfigurationImpl.java new file mode 100644 index 00000000000..920afbd413e --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/GateStartConfigurationImpl.java @@ -0,0 +1,52 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; + +public class GateStartConfigurationImpl extends RacingProcedureConfigurationImpl implements GateStartConfiguration { + + private static final long serialVersionUID = 1862408542829215027L; + + private Boolean hasPathfinder; + private Boolean hasAdditionalGolfDownTime; + + public void setHasPathfinder(Boolean hasPathfinder) { + this.hasPathfinder = hasPathfinder; + } + + @Override + public Boolean hasPathfinder() { + return hasPathfinder; + } + + public void setHasAdditionalGolfDownTime(Boolean hasAdditionalGolfDownTime) { + this.hasAdditionalGolfDownTime = hasAdditionalGolfDownTime; + } + + @Override + public Boolean hasAdditionalGolfDownTime() { + return hasAdditionalGolfDownTime; + } + + @Override + public RacingProcedureConfiguration merge(RacingProcedureConfiguration value) { + GateStartConfiguration update = (GateStartConfiguration) value; + GateStartConfigurationImpl target = (GateStartConfigurationImpl) super.merge(update); + if (update.hasPathfinder() != null) { + target.setHasPathfinder(update.hasPathfinder()); + } + if (update.hasAdditionalGolfDownTime() != null) { + target.setHasAdditionalGolfDownTime(update.hasAdditionalGolfDownTime()); + } + return target; + } + + protected GateStartConfiguration copy() { + GateStartConfigurationImpl copy = (GateStartConfigurationImpl) super.copy(new GateStartConfigurationImpl()); + copy.setHasPathfinder(hasPathfinder); + copy.setHasAdditionalGolfDownTime(hasAdditionalGolfDownTime); + return copy; + } + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RRS26ConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RRS26ConfigurationImpl.java new file mode 100644 index 00000000000..f620c8d0531 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RRS26ConfigurationImpl.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import java.util.List; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; +import com.sap.sailing.domain.common.racelog.Flags; + +public class RRS26ConfigurationImpl extends RacingProcedureConfigurationImpl implements + RRS26Configuration { + + private static final long serialVersionUID = 260941807346644026L; + + private List startModeFlags; + + @Override + public List getStartModeFlags() { + return startModeFlags; + } + + public void setStartModeFlags(List flags) { + this.startModeFlags = flags; + } + + @Override + public RacingProcedureConfiguration merge(RacingProcedureConfiguration value) { + RRS26Configuration update = (RRS26Configuration) value; + RRS26ConfigurationImpl target = (RRS26ConfigurationImpl) super.merge(update); + if (update.getStartModeFlags() != null) { + target.setStartModeFlags(update.getStartModeFlags()); + } + return target; + } + + protected RRS26Configuration copy() { + RRS26ConfigurationImpl copy = (RRS26ConfigurationImpl) super.copy(new RRS26ConfigurationImpl()); + copy.setStartModeFlags(startModeFlags); + return copy; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java new file mode 100644 index 00000000000..21977ed851d --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RacingProcedureConfigurationImpl.java @@ -0,0 +1,52 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.common.racelog.Flags; + +public class RacingProcedureConfigurationImpl implements RacingProcedureConfiguration { + + private static final long serialVersionUID = 8501755084811977792L; + + private Flags classFlag; + private Boolean hasInidividualRecall;@Override + + public Flags getClassFlag() { + return classFlag; + } + + public void setClassFlag(Flags classFlag) { + this.classFlag = classFlag; + } + + @Override + public Boolean hasInidividualRecall() { + return hasInidividualRecall; + } + + public void setHasInidividualRecall(Boolean hasInidividualRecall) { + this.hasInidividualRecall = hasInidividualRecall; + } + + protected RacingProcedureConfiguration copy() { + return copy(new RacingProcedureConfigurationImpl()); + } + + protected RacingProcedureConfiguration copy(RacingProcedureConfigurationImpl target) { + target.setClassFlag(this.getClassFlag()); + target.setHasInidividualRecall(this.hasInidividualRecall()); + return target; + } + + @Override + public RacingProcedureConfiguration merge(RacingProcedureConfiguration update) { + RacingProcedureConfigurationImpl target = (RacingProcedureConfigurationImpl) this.copy(); + if (update.getClassFlag() != null) { + target.setClassFlag(update.getClassFlag()); + } + if (update.hasInidividualRecall() != null) { + target.setHasInidividualRecall(update.hasInidividualRecall()); + } + return target; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RegattaConfigurationImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RegattaConfigurationImpl.java new file mode 100644 index 00000000000..bfc8f0eda91 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/impl/RegattaConfigurationImpl.java @@ -0,0 +1,118 @@ +package com.sap.sailing.domain.base.configuration.impl; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.ESSConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; +import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; +import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; + +public class RegattaConfigurationImpl implements RegattaConfiguration { + + private static final long serialVersionUID = 8501755084811977792L; + + private RacingProcedureType defaultRacingProcedureType; + private CourseDesignerMode defaultCourseDesignerMode; + + private RRS26Configuration rrs26Configuration; + private GateStartConfiguration gateStartConfiguration; + private ESSConfiguration essConfiguration; + private RacingProcedureConfiguration basicConfiguration; + + @Override + public RacingProcedureType getDefaultRacingProcedureType() { + return defaultRacingProcedureType; + } + + public void setDefaultRacingProcedureType(RacingProcedureType type) { + defaultRacingProcedureType = type; + } + + @Override + public CourseDesignerMode getDefaultCourseDesignerMode() { + return defaultCourseDesignerMode; + } + + public void setDefaultCourseDesignerMode(CourseDesignerMode mode) { + defaultCourseDesignerMode = mode; + } + + @Override + public RRS26Configuration getRRS26Configuration() { + return rrs26Configuration; + } + + public void setRRS26Configuration(RRS26Configuration rrs26Configuration) { + this.rrs26Configuration = rrs26Configuration; + } + + @Override + public GateStartConfiguration getGateStartConfiguration() { + return gateStartConfiguration; + } + + public void setGateStartConfiguration(GateStartConfiguration gateStartConfiguration) { + this.gateStartConfiguration = gateStartConfiguration; + } + + @Override + public ESSConfiguration getESSConfiguration() { + return essConfiguration; + } + + public void setESSConfiguration(ESSConfiguration essConfiguration) { + this.essConfiguration = essConfiguration; + } + + @Override + public RacingProcedureConfiguration getBasicConfiguration() { + return basicConfiguration; + } + + public void setBasicConfiguration(RacingProcedureConfiguration basicConfiguration) { + this.basicConfiguration = basicConfiguration; + } + + @Override + public RegattaConfiguration clone() { + RegattaConfigurationImpl copy = new RegattaConfigurationImpl(); + copy.setDefaultRacingProcedureType(defaultRacingProcedureType); + copy.setDefaultCourseDesignerMode(defaultCourseDesignerMode); + copy.setRRS26Configuration(rrs26Configuration); + copy.setGateStartConfiguration(gateStartConfiguration); + copy.setESSConfiguration(essConfiguration); + copy.setBasicConfiguration(basicConfiguration); + return copy; + } + + @Override + public RegattaConfiguration merge(RegattaConfiguration update) { + RegattaConfigurationImpl target = (RegattaConfigurationImpl) this.clone(); + + if (update.getDefaultCourseDesignerMode() != null) { + target.setDefaultCourseDesignerMode(update.getDefaultCourseDesignerMode()); + } + if (update.getDefaultRacingProcedureType() != null) { + target.setDefaultRacingProcedureType(update.getDefaultRacingProcedureType()); + } + if (update.getRRS26Configuration() != null) { + target.setRRS26Configuration( + (RRS26Configuration) target.getRRS26Configuration().merge(update.getRRS26Configuration())); + } + if (update.getGateStartConfiguration() != null) { + target.setGateStartConfiguration( + (GateStartConfiguration) target.getGateStartConfiguration().merge(update.getGateStartConfiguration())); + } + if (update.getESSConfiguration() != null) { + target.setESSConfiguration( + (ESSConfiguration) target.getESSConfiguration().merge(update.getESSConfiguration())); + } + if (update.getBasicConfiguration() != null) { + target.setBasicConfiguration( + (RacingProcedureConfiguration) target.getBasicConfiguration().merge(update.getBasicConfiguration())); + } + + return target; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/ESSConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/ESSConfiguration.java new file mode 100644 index 00000000000..a07ea9ff4e6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/ESSConfiguration.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.base.configuration.procedures; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; + +public interface ESSConfiguration extends RacingProcedureConfiguration { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/GateStartConfiguration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/GateStartConfiguration.java new file mode 100644 index 00000000000..bdf6874da8a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/GateStartConfiguration.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.base.configuration.procedures; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; + +public interface GateStartConfiguration extends RacingProcedureConfiguration { + + Boolean hasPathfinder(); + Boolean hasAdditionalGolfDownTime(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/RRS26Configuration.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/RRS26Configuration.java new file mode 100644 index 00000000000..83c56adfe2a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/configuration/procedures/RRS26Configuration.java @@ -0,0 +1,12 @@ +package com.sap.sailing.domain.base.configuration.procedures; + +import java.util.List; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.common.racelog.Flags; + +public interface RRS26Configuration extends RacingProcedureConfiguration { + + List getStartModeFlags(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java index e99717653a3..38ed945fc75 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CompetitorImpl.java @@ -4,6 +4,7 @@ import java.io.Serializable; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.SharedDomainFactory; +import com.sap.sailing.domain.common.Color; public class CompetitorImpl implements DynamicCompetitor { private static final long serialVersionUID = 294603681016643157L; @@ -12,13 +13,16 @@ public class CompetitorImpl implements DynamicCompetitor { private final Serializable id; private String name; - public CompetitorImpl(Serializable id, String name, DynamicTeam team, DynamicBoat boat) { + private Color color; + + public CompetitorImpl(Serializable id, String name, Color color, DynamicTeam team, DynamicBoat boat) { this.id = id; this.name = name; this.team = team; this.boat = boat; + this.color = color; } - + @Override public String getName() { return name; @@ -51,7 +55,15 @@ public class CompetitorImpl implements DynamicCompetitor { @Override public Competitor resolve(SharedDomainFactory domainFactory) { - Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getTeam(), getBoat()); + Competitor result = domainFactory.getOrCreateCompetitor(getId(), getName(), getColor(), getTeam(), getBoat()); return result; } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseAreaImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseAreaImpl.java index 62581d234a1..1f6642d9a2d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseAreaImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseAreaImpl.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.base.impl; -import java.io.Serializable; +import java.util.UUID; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; @@ -10,15 +10,15 @@ import com.sap.sailing.domain.common.impl.NamedImpl; public class CourseAreaImpl extends NamedImpl implements CourseArea { private static final long serialVersionUID = 5912385360170509150L; - private final Serializable id; + private final UUID id; - public CourseAreaImpl(String name, Serializable id) { + public CourseAreaImpl(String name, UUID id) { super(name); this.id = id; } @Override - public Serializable getId() { + public UUID getId() { return id; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java index 43080e5c2b8..c65006c886c 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/DynamicCompetitor.java @@ -1,9 +1,12 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Renamable; public interface DynamicCompetitor extends Competitor, Renamable { DynamicBoat getBoat(); DynamicTeam getTeam(); + + void setColor(Color displayColor); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java new file mode 100644 index 00000000000..dc84765444c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/SharedDomainFactoryImpl.java @@ -0,0 +1,316 @@ +package com.sap.sailing.domain.base.impl; + +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorStore; +import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.CourseArea; +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.SharedDomainFactory; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherMulti; +import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle; +import com.sap.sailing.domain.common.Color; +import com.sap.sailing.domain.common.MarkType; +import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.WithID; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; + +public class SharedDomainFactoryImpl implements SharedDomainFactory { + + /** + * Ensure that the same string is used as key that is also used to set the {@link Nationality} + * object's {@link Nationality#getThreeLetterIOCAcronym() IOC code}. + */ + private final Map nationalityCache; + + private final Map markCache; + + /** + * For all marks ever created by this factory, the mark {@link WithID#getId() ID}'s string representation + * is mapped here to the actual ID. This allows clients to send only the string representation to the server + * and still be able to identify a mark uniquely this way. + */ + private final Map markIdCache; + + private final Map boatClassCache; + + protected final CompetitorStore competitorStore; + + private final Map courseAreaCache; + + /** + * Weakly references the waypoints. If a waypoint is no longer strongly referenced, the corresponding reference contained + * as value will have its referred object be null. In this case, the methods reading from this cache will purge + * the record and behave as if the record hadn't existed at the time of the read operation. + */ + private final ConcurrentHashMap waypointCache; + + private final ReferenceQueue waypointCacheReferenceQueue; + + private final Map configurationMatcherCache; + + /** + * Weak references to {@link Waypoint} objects of this type are registered with + * {@link DomainFactoryImpl#waypointCacheReferenceQueue} upon construction so that when their referents are no + * longer strongly referenced and the reference was nulled, they are entered into that queue. + * Methods managing the {@link #waypointCache} can poll the queue and then remove cache entries based on + * the {@link #id} stored in the reference. + * + * @author Axel Uhl (D043530) + * + */ + private class WeakWaypointReference extends WeakReference { + private final Serializable id; + + public WeakWaypointReference(Waypoint waypoint) { + super(waypoint, waypointCacheReferenceQueue); + this.id = waypoint.getId(); + } + + public void removeCacheEntry() { + waypointCache.remove(id); + } + } + + private final Set mayStartWithNoUpwindLeg; + + /** + * Uses a transient competitor store + */ + public SharedDomainFactoryImpl() { + this(new TransientCompetitorStoreImpl()); + } + + public SharedDomainFactoryImpl(CompetitorStore competitorStore) { + waypointCacheReferenceQueue = new ReferenceQueue(); + nationalityCache = new HashMap(); + markCache = new HashMap(); + markIdCache = new HashMap(); + boatClassCache = new HashMap(); + this.competitorStore = competitorStore; + waypointCache = new ConcurrentHashMap(); + mayStartWithNoUpwindLeg = new HashSet(Arrays.asList(new String[] { "extreme40", "ess", "ess40" })); + courseAreaCache = new HashMap(); + configurationMatcherCache = new HashMap(); + } + + @Override + public Nationality getOrCreateNationality(String threeLetterIOCCode) { + synchronized (nationalityCache) { + Nationality result = nationalityCache.get(threeLetterIOCCode); + if (result == null) { + result = new NationalityImpl(threeLetterIOCCode); + nationalityCache.put(threeLetterIOCCode, result); + } + return result; + } + } + + @Override + public Mark getOrCreateMark(String name) { + return getOrCreateMark(name, name); + } + + @Override + public Mark getOrCreateMark(Serializable id, String name) { + Mark result = markCache.get(id); + if (result == null) { + result = new MarkImpl(id, name); + cacheMark(id, result); + } + return result; + } + + @Override + public Mark getOrCreateMark(String toStringRepresentationOfID, String name) { + final Mark result; + if (markIdCache.containsKey(toStringRepresentationOfID)) { + Serializable id = markIdCache.get(toStringRepresentationOfID); + result = getOrCreateMark(id, name); + } else { + result = new MarkImpl(toStringRepresentationOfID, name); + cacheMark(toStringRepresentationOfID, result); + } + return result; + } + + private void cacheMark(Serializable id, Mark result) { + markCache.put(id, result); + markIdCache.put(id.toString(), id); + } + + + + @Override + public Mark getOrCreateMark(Serializable id, String name, MarkType type, String color, String shape, String pattern) { + Mark result = markCache.get(id); + if (result == null) { + result = new MarkImpl(id, name, type, color, shape, pattern); + cacheMark(id, result); + } + return result; + } + + @Override + public Gate createGate(Mark left, Mark right, String name) { + return new GateImpl(left, right, name); + } + + @Override + public Gate createGate(Serializable id, Mark left, Mark right, String name) { + return new GateImpl(id, left, right, name); + } + + @Override + public Waypoint createWaypoint(ControlPoint controlPoint, NauticalSide passingSide) { + synchronized (waypointCache) { + expungeStaleWaypointCacheEntries(); + Waypoint result = new WaypointImpl(controlPoint, passingSide); + waypointCache.put(result.getId(), new WeakWaypointReference(result)); + return result; + } + } + + @Override + public Waypoint getExistingWaypointById(Waypoint waypointPrototype) { + synchronized (waypointCache) { + expungeStaleWaypointCacheEntries(); + Waypoint result = null; + Reference ref = waypointCache.get(waypointPrototype.getId()); + if (ref != null) { + result = ref.get(); + if (result == null) { + // waypoint was finalized; remove entry from cache + waypointCache.remove(waypointPrototype.getId()); + } + } + return result; + } + } + + @Override + public Waypoint getExistingWaypointByIdOrCache(Waypoint waypoint) { + synchronized (waypointCache) { + expungeStaleWaypointCacheEntries(); + Waypoint result = null; + Reference ref = waypointCache.get(waypoint.getId()); + if (ref != null) { + result = ref.get(); + if (result == null) { + // waypoint was finalized; remove entry from cache and add anew + result = waypoint; + waypointCache.put(waypoint.getId(), new WeakWaypointReference(waypoint)); + } // else, result is the waypoint found in the cache; return it + } else { + // No entry found in the cache; not even a stale, finalized one. Create a new entry: + result = waypoint; + waypointCache.put(waypoint.getId(), new WeakWaypointReference(waypoint)); + } + return result; + } + } + + private void expungeStaleWaypointCacheEntries() { + Reference ref; + while ((ref=waypointCacheReferenceQueue.poll()) != null) { + ((WeakWaypointReference) ref).removeCacheEntry(); + } + } + + @Override + public BoatClass getOrCreateBoatClass(String name, boolean typicallyStartsUpwind) { + synchronized (boatClassCache) { + BoatClass result = boatClassCache.get(name); + if (result == null) { + result = new BoatClassImpl(name, typicallyStartsUpwind); + boatClassCache.put(name, result); + } + return result; + } + } + + @Override + public BoatClass getOrCreateBoatClass(String name) { + return getOrCreateBoatClass(name, /* typicallyStartsUpwind */!mayStartWithNoUpwindLeg.contains(name.toLowerCase())); + } + + @Override + public CourseArea getOrCreateCourseArea(UUID courseAreaId, String name) { + CourseArea result = getExistingCourseAreaById(courseAreaId); + if (result == null) { + result = new CourseAreaImpl(name, courseAreaId); + courseAreaCache.put(courseAreaId, result); + } + return result; + } + + @Override + public CourseArea getExistingCourseAreaById(Serializable courseAreaId) { + return courseAreaCache.get(courseAreaId); + } + + @Override + public CompetitorStore getCompetitorStore() { + return competitorStore; + } + + @Override + public Competitor getExistingCompetitorById(Serializable competitorId) { + return getCompetitorStore().getExistingCompetitorById(competitorId); + } + + @Override + public boolean isCompetitorToUpdateDuringGetOrCreate(Competitor result) { + return getCompetitorStore().isCompetitorToUpdateDuringGetOrCreate(result); + } + + @Override + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + return getCompetitorStore().getOrCreateCompetitor(competitorId, name, displayColor, team, boat); + } + + @Override + public DeviceConfigurationMatcher getOrCreateDeviceConfigurationMatcher(DeviceConfigurationMatcherType type, + List clientIdentifiers) { + DeviceConfigurationMatcher probe = createMatcher(type, clientIdentifiers); + DeviceConfigurationMatcher matcher = configurationMatcherCache.get(probe.getMatcherIdentifier()); + if (matcher == null) { + configurationMatcherCache.put(probe.getMatcherIdentifier(), probe); + matcher = probe; + } + return matcher; + } + + private DeviceConfigurationMatcher createMatcher(DeviceConfigurationMatcherType type, List clientIdentifiers) { + DeviceConfigurationMatcher matcher = null; + switch (type) { + case SINGLE: + matcher = new DeviceConfigurationMatcherSingle(clientIdentifiers.get(0)); + break; + case MULTI: + matcher = new DeviceConfigurationMatcherMulti(clientIdentifiers); + break; + default: + throw new IllegalArgumentException("Unknown matcher type: " + type); + } + return matcher; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java similarity index 89% rename from java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java rename to java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index ea5f4f1f6da..82e71541d9f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -13,6 +13,7 @@ import java.util.WeakHashMap; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CompetitorStore; import com.sap.sailing.domain.base.Nationality; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.CountryCode; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; @@ -38,19 +39,19 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab public TransientCompetitorStoreImpl() { lock = new NamedReentrantReadWriteLock("CompetitorStore", /* fair */ false); - competitorCache = new HashMap<>(); - competitorsByIdAsString = new HashMap<>(); - competitorsToUpdateDuringGetOrCreate = new HashSet<>(); - weakCompetitorDTOCache = new WeakHashMap<>(); + competitorCache = new HashMap(); + competitorsByIdAsString = new HashMap(); + competitorsToUpdateDuringGetOrCreate = new HashSet(); + weakCompetitorDTOCache = new WeakHashMap(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); - weakCompetitorDTOCache = new WeakHashMap<>(); + weakCompetitorDTOCache = new WeakHashMap(); } - private Competitor createCompetitor(Serializable id, String name, DynamicTeam team, DynamicBoat boat) { - Competitor result = new CompetitorImpl(id, name, team, boat); + private Competitor createCompetitor(Serializable id, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { + Competitor result = new CompetitorImpl(id, name, displayColor, team, boat); addNewCompetitor(id, result); return result; } @@ -71,25 +72,25 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor getOrCreateCompetitor(Serializable competitorId, String name, DynamicTeam team, DynamicBoat boat) { + public Competitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat) { Competitor result = getExistingCompetitorById(competitorId); // avoid synchronization for successful read access if (result == null) { LockUtil.lockForWrite(lock); try { result = getExistingCompetitorById(competitorId); // try again, now while holding the write lock if (result == null) { - result = createCompetitor(competitorId, name, team, boat); + result = createCompetitor(competitorId, name, displayColor, team, boat); } } finally { LockUtil.unlockAfterWrite(lock); } } else if (isCompetitorToUpdateDuringGetOrCreate(result)) { - updateCompetitor(result.getId().toString(), name, boat.getSailID(), team.getNationality()); + updateCompetitor(result.getId().toString(), name, displayColor, boat.getSailID(), team.getNationality()); competitorNoLongerToUpdateDuringGetOrCreate(result); } return result; } - + private void competitorNoLongerToUpdateDuringGetOrCreate(Competitor result) { competitorsToUpdateDuringGetOrCreate.remove(result); } @@ -162,12 +163,13 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public Competitor updateCompetitor(String idAsString, String newName, String newSailId, Nationality newNationality) { + public Competitor updateCompetitor(String idAsString, String newName, Color newDisplayColor, String newSailId, Nationality newNationality) { DynamicCompetitor competitor = (DynamicCompetitor) getExistingCompetitorByIdAsString(idAsString); if (competitor != null) { LockUtil.lockForWrite(lock); try { competitor.setName(newName); + competitor.setColor(newDisplayColor); competitor.getBoat().setSailId(newSailId); competitor.getTeam().setNationality(newNationality); weakCompetitorDTOCache.remove(competitor); @@ -192,7 +194,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab if (competitorDTO == null) { final Nationality nationality = c.getTeam().getNationality(); CountryCode countryCode = nationality == null ? null : nationality.getCountryCode(); - competitorDTO = new CompetitorDTOImpl(c.getName(), countryCode == null ? "" + competitorDTO = new CompetitorDTOImpl(c.getName(), c.getColor(), countryCode == null ? "" : countryCode.getTwoLetterISOCode(), countryCode == null ? "" : countryCode.getThreeLetterIOCCode(), countryCode == null ? "" : countryCode.getName(), c .getBoat().getSailID(), c.getId().toString(), new BoatClassDTO(c.getBoat().getBoatClass() diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java index 854c8eef986..620d8903c58 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/RaceGroup.java @@ -3,7 +3,9 @@ package com.sap.sailing.domain.base.racegroup; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.Named; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; /** * A {@link RaceGroup} is an abstract representation of a set of races managed in fleets and series. @@ -40,4 +42,9 @@ public interface RaceGroup extends Named { * Gets the collection of series. */ public Iterable getSeries(); + + /** + * Gets configuration objects for {@link RacingProcedure}s of this {@link RaceGroup}'s races. + */ + public RegattaConfiguration getRegattaConfiguration(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java index 8435184b2de..9267c6cde17 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/racegroup/impl/RaceGroupImpl.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.racegroup.impl; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.racegroup.RaceGroup; import com.sap.sailing.domain.base.racegroup.SeriesWithRows; import com.sap.sailing.domain.common.impl.NamedImpl; @@ -13,16 +14,15 @@ public class RaceGroupImpl extends NamedImpl implements RaceGroup { private final BoatClass boatClass; private final CourseArea courseArea; private final Iterable series; + private final RegattaConfiguration regattaConfiguration; - public RaceGroupImpl( - String name, - BoatClass boatClass, - CourseArea courseArea, - Iterable series) { + public RaceGroupImpl(String name, BoatClass boatClass, CourseArea courseArea, Iterable series, + RegattaConfiguration regattaConfiguration) { super(name); this.boatClass = boatClass; this.courseArea = courseArea; this.series = series; + this.regattaConfiguration = regattaConfiguration; } public CourseArea getDefaultCourseArea() { @@ -37,4 +37,9 @@ public class RaceGroupImpl extends NamedImpl implements RaceGroup { return boatClass; } + @Override + public RegattaConfiguration getRegattaConfiguration() { + return regattaConfiguration; + } + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java new file mode 100644 index 00000000000..03cbde6c23b --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/CompetitorResults.java @@ -0,0 +1,11 @@ +package com.sap.sailing.domain.racelog; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.impl.Util.Triple; + +public interface CompetitorResults extends List> { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java index 379133b2f19..9fcaf733bd6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLog.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.racelog; import java.util.HashSet; +import java.util.UUID; import com.sap.sailing.domain.common.WithID; import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; @@ -67,6 +68,23 @@ public interface RaceLog extends Track, WithID { Iterable getAllListeners(); + /** + * Adds an event to this race log and returns a superset of all race log events (excluding the new + * event) that were added to this race log but not yet returned to the client with ID + * clientId by this method. In general, the list returned is not a true superset but + * equals exactly those events not yet delivered to the client. However, if the server was re-started + * since the client last called this method, and since the underlying data structures are not durably + * stored, the entire set of all race log events would be delivered to the client once. + */ + Iterable add(RaceLogEvent event, UUID clientId); + + /** + * Returns all {@link #getRawFixes() raw fixes} and marks them as delivered to the client identified by clientId + * so that when that ID appears in a subsequent call to {@link #add(RaceLogEvent, UUID)}, the fixes returned by this call + * are already considered delivered to the client identified by clientId. + */ + Iterable getRawFixes(UUID clientId); + /** * Like {@link #add(RaceLogEvent)}, only that no events are triggered. Use this method only when loading a race log, * e.g., from a replication or master data import or when loading from the database. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogChangedListener.java new file mode 100644 index 00000000000..2fd55f1271c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogChangedListener.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog; + +public interface RaceLogChangedListener { + + /** + * Called when an event is added to the {@link RaceLog}. + * @param event that was added + */ + public void eventAdded(RaceLogEvent event); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEvent.java index 1a90d3d51c8..458d51a0867 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEvent.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelog; +import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.base.Competitor; @@ -28,24 +29,40 @@ import com.sap.sailing.domain.common.WithID; * * The timestamp received with {@link RaceLogEvent#getCreatedAt()} denotes the point in time the event was created. * - * The timestamp received with {@link RaceLogEvent#getTimePoint()} denotes the logical point in time the event has - * occurred (sometimes called 'event time'). This timestamp might be different from {@link RaceLogEvent#getCreatedAt()}. - * For example setting the start time of race to a point in time in the past, results in a {@link RaceLogEvent} - * returning "now" on {@link RaceLogEvent#getCreatedAt()} and such a point in time that a complete startphase could have - * occured on {@link RaceLogEvent#getTimePoint()}. + * The timestamp received with {@link RaceLogEvent#getLogicalTimePoint()} denotes the logical point in time the event + * has occurred (sometimes called 'event time'). This timestamp might be different from + * {@link RaceLogEvent#getCreatedAt()}. For example setting the start time of race to a point in time in the past, + * results in a {@link RaceLogEvent} returning "now" on {@link RaceLogEvent#getCreatedAt()} and such a point in time + * that a complete startphase could have occured on {@link RaceLogEvent#getTimePoint()}. * * To ensure an ordering there should be no two {@link RaceLogEvent}s returning the same point in time on - * {@link RaceLogEvent#getTimePoint()} per pass. + * {@link RaceLogEvent#getCreatedAt()} per pass. + * + * The {@link RaceLogEvent}'s {@link Timed} interface redirects to {@link RaceLogEvent#getCreatedAt()} (was + * {@link RaceLogEvent#getLogicalTimePoint()}!). *

* + *

+ * Race log events have an author that has a name and a priority assigned. This can be used to represent multiple + * concurrent race log authors such as a device used on a starting vessel, another device used at the finish line and + * yet another device used on shore. In case more than one device make a statement about something, such as the start + * time or the start procedure, the statement from the device with the highest priority needs to take precedence. + *

*/ -public interface RaceLogEvent extends Timed, WithID { +public interface RaceLogEvent extends Timed, WithID, Serializable { /** * Gets the {@link TimePoint} this event was created at. */ TimePoint getCreatedAt(); + /** + * Gets the event's logical timestamp. + * + * @return + */ + TimePoint getLogicalTimePoint(); + /** * Gets the event's pass identifier. * @@ -68,4 +85,6 @@ public interface RaceLogEvent extends Timed, WithID { * Visitor pattern to implement certain {@link RaceLogEvent} subclass specific behavior. */ void accept(RaceLogEventVisitor visitor); + + RaceLogEventAuthor getAuthor(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java new file mode 100755 index 00000000000..c69dd7a2eb6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventAuthor.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.racelog; + +import com.sap.sailing.domain.common.Named; + +/** + * Multiple devices may concurrently post to a single {@link RaceLog}. In this case it is important to understand + * who posted what. The "who" is described by instances of this class. In particular, an author has a name and a + * priority which is then used to arbitrate between different posts on the same subject, such as a race's start time + * or transitions from one race phase to another. + * + * @author Axel Uhl (d043530) + * + */ +public interface RaceLogEventAuthor extends Named, Comparable { + + public static final int PRIORITY_COMPATIBILITY = 16; + public static final String NAME_COMPATIBILITY = "Compatibility"; + + /** + * A lesser number represents a higher priority. Suggested priority levels: + *
    + *
  1. Start Vessel
  2. + *
  3. Finish Vessel
  4. + *
  5. Shore Control
  6. + *
  7. Compatibility priority for old events
  8. + *
+ */ + int getPriority(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java index a0b8c3860b3..7cf001ce9a5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventFactory.java @@ -5,89 +5,87 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventFactoryImpl; import com.sap.sailing.domain.tracking.Wind; public interface RaceLogEventFactory { RaceLogEventFactory INSTANCE = new RaceLogEventFactoryImpl(); - RaceLogFlagEvent createFlagEvent(TimePoint timePoint, Serializable id, List involvedBoats, int passId, - Flags upperFlag, Flags lowerFlag, boolean isDisplayed); + RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, List involvedBoats, + int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed); - RaceLogFlagEvent createFlagEvent(TimePoint timePoint, int passId, Flags upperFlag, Flags lowerFlag, - boolean isDisplayed); + RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Flags upperFlag, + Flags lowerFlag, boolean isDisplayed); - RaceLogStartTimeEvent createStartTimeEvent(TimePoint timePoint, Serializable id, List involvedBoats, - int passId, TimePoint startTime); + RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List involvedBoats, int passId, TimePoint startTime); - RaceLogStartTimeEvent createStartTimeEvent(TimePoint timePoint, int passId, TimePoint startTime); + RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, TimePoint startTime); - RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint timePoint, Serializable id, List competitors, - int passId, RaceLogRaceStatus nextStatus); + RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List competitors, int passId, RaceLogRaceStatus nextStatus); - RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint timePoint, int passId, RaceLogRaceStatus nextStatus); + RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, RaceLogRaceStatus nextStatus); - RaceLogPassChangeEvent createPassChangeEvent(TimePoint timePoint, Serializable id, List competitors, - int passId); + RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List competitors, int passId); - RaceLogPassChangeEvent createPassChangeEvent(TimePoint timePoint, int passId); + RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId); - RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, Serializable courseAreaId); + RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Serializable courseAreaId); - RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint timePoint, int passId, - Serializable courseAreaId); + RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Serializable courseAreaId); - RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, CourseBase courseData); + RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, CourseBase courseData); - RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint timePoint, int passId, - CourseBase courseData); + RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CourseBase courseData); - RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint timePoint, - Serializable id, List competitors, int passId, - List> positionedCompetitors); + RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, Serializable id, List competitors, + int passId, CompetitorResults positionedCompetitors); - RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint timePoint, int passId, - List> positionedCompetitors); + RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CompetitorResults positionedCompetitors); - RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, List> positionedCompetitors); + RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors); - RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint timePoint, int passId, - List> positionedCompetitors); + RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CompetitorResults positionedCompetitors); - RaceLogPathfinderEvent createPathfinderEvent(TimePoint timePoint, Serializable id, List competitors, - int passId, String pathfinderId); + RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List competitors, int passId, String pathfinderId); - RaceLogPathfinderEvent createPathfinderEvent(TimePoint timePoint, int passId, String pathfinderId); + RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, String pathfinderId); - RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, Long gateLineOpeningTimeInMillis); + RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Long gateLaunchStopTime, Long golfDownTime); - RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint timePoint, int passId, - Long gateLineOpeningTimeInMillis); + RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Long gateLaunchStopTime, Long golfDownTime); - RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, StartProcedureType type); + RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, RacingProcedureType type); - RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint timePoint, int passId, - StartProcedureType type); + RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, RacingProcedureType type); - RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, TimePoint protestStartTime); + RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, TimePoint protestStartTime); - RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint timePoint, int passId, TimePoint protestStartTime); + RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, TimePoint protestStartTime); - RaceLogWindFixEvent createWindFixEvent(TimePoint eventTime, int currentPassId, Wind wind); + RaceLogWindFixEvent createWindFixEvent(TimePoint eventTime, RaceLogEventAuthor author, int currentPassId, Wind wind); - RaceLogWindFixEvent createWindFixEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, Wind wind); + RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Wind wind); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java index 9094f945cf1..b277143e39c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogEventRestoreFactory.java @@ -5,56 +5,55 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.impl.RaceLogEventRestoreFactoryImpl; import com.sap.sailing.domain.tracking.Wind; public interface RaceLogEventRestoreFactory extends RaceLogEventFactory { RaceLogEventRestoreFactory INSTANCE = new RaceLogEventRestoreFactoryImpl(); - RaceLogFlagEvent createFlagEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed); + RaceLogFlagEvent createFlagEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed); - RaceLogStartTimeEvent createStartTimeEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List involvedBoats, int passId, TimePoint startTime); + RaceLogStartTimeEvent createStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List involvedBoats, int passId, TimePoint startTime); - RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List competitors, int passId, RaceLogRaceStatus nextStatus); + RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus); - RaceLogPassChangeEvent createPassChangeEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List competitors, int passId); + RaceLogPassChangeEvent createPassChangeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List competitors, int passId); - RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, Serializable courseAreaId); + RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, Serializable courseAreaId); - RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, CourseBase courseData); + RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, CourseBase courseData); RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint createdAt, - TimePoint logicalTimePoint, Serializable id, List competitors, int passId, - List> positionedCompetitors); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, + int passId, CompetitorResults positionedCompetitors); RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint createdAt, - TimePoint logicalTimePoint, Serializable id, List competitors, int passId, - List> positionedCompetitors); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, + int passId, CompetitorResults positionedCompetitors); - RaceLogPathfinderEvent createPathfinderEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List competitors, int passId, String pathfinderId); + RaceLogPathfinderEvent createPathfinderEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List competitors, int passId, String pathfinderId); - RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, Long gateLineOpeningTime); + RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, Long gateLaunchStopTime, + Long golfDownTime); - RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, List competitors, int passId, StartProcedureType type); + RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, RacingProcedureType type); - RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, List competitors, int passId, TimePoint protestStartTime); + RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, TimePoint protestStartTime); - RaceLogWindFixEvent createWindFixEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - List competitors, Integer passId, Wind wind); + RaceLogWindFixEvent createWindFixEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List competitors, Integer passId, Wind wind); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogFinishPositioningEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogFinishPositioningEvent.java index cfc2d0af9dd..f0ae8e1fba3 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogFinishPositioningEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogFinishPositioningEvent.java @@ -1,10 +1,5 @@ package com.sap.sailing.domain.racelog; -import java.io.Serializable; -import java.util.List; - -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; public abstract interface RaceLogFinishPositioningEvent extends RaceLogEvent { @@ -12,5 +7,5 @@ public abstract interface RaceLogFinishPositioningEvent extends RaceLogEvent { * @return a triple holding the competitor ID, the competitor name and the {@link MaxPointReason} documenting the * score for the competitor */ - List> getPositionedCompetitorsIDsNamesMaxPointsReasons(); + CompetitorResults getPositionedCompetitorsIDsNamesMaxPointsReasons(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java index e47e110d8ba..74d202cb0fa 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogGateLineOpeningTimeEvent.java @@ -1,6 +1,20 @@ package com.sap.sailing.domain.racelog; +import com.sap.sailing.domain.common.impl.Util.Pair; + public interface RaceLogGateLineOpeningTimeEvent extends RaceLogEvent { - Long getGateLineOpeningTime(); + + public class GateLineOpeningTimes extends Pair { + private static final long serialVersionUID = -3262303047719105275L; + + public GateLineOpeningTimes(long gateLaunchStopTime, long golfDownTime) { + super(gateLaunchStopTime, golfDownTime); + } + + public final long getGateLaunchStopTime() { return this.getA(); } + public final long getGolfDownTime() { return this.getB(); } + } + + GateLineOpeningTimes getGateLineOpeningTimes(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogServletConstants.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogServletConstants.java new file mode 100755 index 00000000000..4ed78fe2025 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogServletConstants.java @@ -0,0 +1,15 @@ +package com.sap.sailing.domain.racelog; + +/** + * Constants shared across race committee app and servlet implementation + * + * @author Axel Uhl (d043530) + * + */ +public interface RaceLogServletConstants { + static final String PARAMS_LEADERBOARD_NAME = "leaderboard"; + static final String PARAMS_RACE_COLUMN_NAME = "raceColumn"; + static final String PARAMS_RACE_FLEET_NAME = "fleet"; + static final String PARAMS_CLIENT_UUID = "clientuuid"; + static final String PARAM_COURSE_AREA_FILTER = "courseArea"; +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogStartProcedureChangedEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogStartProcedureChangedEvent.java index 01cc4f46a61..2526df4332c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogStartProcedureChangedEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/RaceLogStartProcedureChangedEvent.java @@ -1,9 +1,10 @@ package com.sap.sailing.domain.racelog; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; + public interface RaceLogStartProcedureChangedEvent extends RaceLogEvent { - StartProcedureType getStartProcedureType(); + RacingProcedureType getStartProcedureType(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/ConfirmedFinishPositioningListFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/ConfirmedFinishPositioningListFinder.java index d11a7ca45e1..dd595be2f31 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/ConfirmedFinishPositioningListFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/ConfirmedFinishPositioningListFinder.java @@ -1,22 +1,18 @@ package com.sap.sailing.domain.racelog.analyzing.impl; -import java.io.Serializable; -import java.util.List; - -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; -public class ConfirmedFinishPositioningListFinder extends RaceLogAnalyzer>> { +public class ConfirmedFinishPositioningListFinder extends RaceLogAnalyzer { public ConfirmedFinishPositioningListFinder(RaceLog raceLog) { super(raceLog); } @Override - protected List> performAnalysis() { + protected CompetitorResults performAnalysis() { for (RaceLogEvent event : getPassEventsDescending()) { if (event instanceof RaceLogFinishPositioningConfirmedEvent) { RaceLogFinishPositioningConfirmedEvent finishPositioningEvent = (RaceLogFinishPositioningConfirmedEvent) event; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishPositioningListFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishPositioningListFinder.java index 545e0607875..978bdd8da82 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishPositioningListFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishPositioningListFinder.java @@ -1,22 +1,18 @@ package com.sap.sailing.domain.racelog.analyzing.impl; -import java.io.Serializable; -import java.util.List; - -import com.sap.sailing.domain.common.MaxPointsReason; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; -public class FinishPositioningListFinder extends RaceLogAnalyzer>> { +public class FinishPositioningListFinder extends RaceLogAnalyzer { public FinishPositioningListFinder(RaceLog raceLog) { super(raceLog); } @Override - protected List> performAnalysis() { + protected CompetitorResults performAnalysis() { for (RaceLogEvent event : getPassEventsDescending()) { if (event instanceof RaceLogFinishPositioningListChangedEvent) { RaceLogFinishPositioningListChangedEvent finishPositioningEvent = (RaceLogFinishPositioningListChangedEvent) event; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishedTimeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishedTimeFinder.java index a75f215fb3f..a6fa037f0ec 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishedTimeFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishedTimeFinder.java @@ -18,7 +18,7 @@ public class FinishedTimeFinder extends RaceLogAnalyzer { if (event instanceof RaceLogRaceStatusEvent) { RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHED)) { - return statusEvent.getTimePoint(); + return statusEvent.getLogicalTimePoint(); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishingTimeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishingTimeFinder.java index 76f683bf0b4..0b05110e787 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishingTimeFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/FinishingTimeFinder.java @@ -18,7 +18,7 @@ public class FinishingTimeFinder extends RaceLogAnalyzer { if (event instanceof RaceLogRaceStatusEvent) { RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; if (statusEvent.getNextStatus().equals(RaceLogRaceStatus.FINISHING)) { - return statusEvent.getTimePoint(); + return statusEvent.getLogicalTimePoint(); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/GateLineOpeningTimeFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/GateLineOpeningTimeFinder.java index 0b7399a2606..9b3c1720f1f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/GateLineOpeningTimeFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/GateLineOpeningTimeFinder.java @@ -3,18 +3,19 @@ package com.sap.sailing.domain.racelog.analyzing.impl; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent.GateLineOpeningTimes; -public class GateLineOpeningTimeFinder extends RaceLogAnalyzer { +public class GateLineOpeningTimeFinder extends RaceLogAnalyzer { public GateLineOpeningTimeFinder(RaceLog raceLog) { super(raceLog); } @Override - protected Long performAnalysis() { - for (RaceLogEvent event : getPassEvents()) { + protected GateLineOpeningTimes performAnalysis() { + for (RaceLogEvent event : getPassEventsDescending()) { if (event instanceof RaceLogGateLineOpeningTimeEvent) { - return ((RaceLogGateLineOpeningTimeEvent) event).getGateLineOpeningTime(); + return ((RaceLogGateLineOpeningTimeEvent) event).getGateLineOpeningTimes(); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IndividualRecallFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IndividualRecallFinder.java index 61a5f97a31c..8995a35cfe6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IndividualRecallFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IndividualRecallFinder.java @@ -15,11 +15,11 @@ public abstract class IndividualRecallFinder extends RaceLogAnalyzer @Override protected TimePoint performAnalysis() { - for (RaceLogEvent event : getPassEvents()) { + for (RaceLogEvent event : getPassEventsDescending()) { if (event instanceof RaceLogFlagEvent) { RaceLogFlagEvent flagEvent = (RaceLogFlagEvent) event; if (isRelevant(flagEvent)) { - return flagEvent.getTimePoint(); + return flagEvent.getLogicalTimePoint(); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IsIndividualRecallDisplayedAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IsIndividualRecallDisplayedAnalyzer.java new file mode 100644 index 00000000000..3c880386a6c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/IsIndividualRecallDisplayedAnalyzer.java @@ -0,0 +1,34 @@ +package com.sap.sailing.domain.racelog.analyzing.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLog; + +public class IsIndividualRecallDisplayedAnalyzer extends RaceLogAnalyzer { + + private final IndividualRecallFinder displayedFinder; + private final IndividualRecallFinder removedFinder; + + public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog) { + this(raceLog, new IndividualRecallDisplayedFinder(raceLog), new IndividualRecallRemovedFinder(raceLog)); + } + + public IsIndividualRecallDisplayedAnalyzer(RaceLog raceLog, IndividualRecallDisplayedFinder displayedFinder, + IndividualRecallRemovedFinder removedFinder) { + super(raceLog); + this.displayedFinder = displayedFinder; + this.removedFinder = removedFinder; + } + + @Override + protected Boolean performAnalysis() { + TimePoint displayedAt = displayedFinder.analyze(); + TimePoint removedAt = removedFinder.analyze(); + if (displayedAt != null) { + if (removedAt == null || displayedAt.after(removedAt)) { + return true; + } + } + return false; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastFlagsFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastFlagsFinder.java index 475ab27f806..7553e8f68d5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastFlagsFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/LastFlagsFinder.java @@ -12,10 +12,12 @@ import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; /** - * Analyzation returns the most recent {@link RaceLogFlagEvent}s. + * Analysis returns the most recent {@link RaceLogFlagEvent}s. + *

* * If there is no {@link RaceLogFlagEvent} in the current pass, null is returned. Otherwise a {@link List} - * of {@link RaceLogFlagEvent} is returned containing all {@link RaceLogEvent}s with the most recent {@link TimePoint}. + * of {@link RaceLogFlagEvent} is returned containing all {@link RaceLogEvent}s with the most recent (logical) + * {@link TimePoint}. * */ public class LastFlagsFinder extends RaceLogAnalyzer> { @@ -38,9 +40,9 @@ public class LastFlagsFinder extends RaceLogAnalyzer> { private List collectAllWithSameTimePoint(Iterator iterator, RaceLogFlagEvent flagEvent) { List result = new ArrayList(); - TimePoint lastFlagsTime = flagEvent.getTimePoint(); + TimePoint logicalTime = flagEvent.getLogicalTimePoint(); - while (flagEvent.getTimePoint().equals(lastFlagsTime)) { + while (flagEvent.getLogicalTimePoint().equals(logicalTime)) { result.add(flagEvent); flagEvent = getNextFlagEvent(iterator); if (flagEvent == null) { @@ -62,11 +64,11 @@ public class LastFlagsFinder extends RaceLogAnalyzer> { } /** - * Use this method the obtain the most "interesting" {@link RaceLogFlagEvent} of the {@link LastFlagsFinder}'s - * analyzation result. + * Use this method to obtain the most "interesting" {@link RaceLogFlagEvent} of the {@link LastFlagsFinder}'s + * analysis result. * * If an empty list or null is passed, this method returns null. Otherwise the (most - * recent) {@link RaceLogFlagEvent} with the highest ID is returned favoring events with + * recent by logical timestamp) {@link RaceLogFlagEvent} with the highest ID is returned favoring events with * {@link RaceLogFlagEvent#isDisplayed()} returning true. * * @param events @@ -82,7 +84,7 @@ public class LastFlagsFinder extends RaceLogAnalyzer> { Collections.sort(sortedEvents, new Comparator() { @Override public int compare(RaceLogFlagEvent left, RaceLogFlagEvent right) { - int result = right.getTimePoint().compareTo(left.getTimePoint()); + int result = right.getLogicalTimePoint().compareTo(left.getLogicalTimePoint()); if (result == 0) { result = Boolean.valueOf(right.isDisplayed()).compareTo(left.isDisplayed()); if (result == 0) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java index 2837c899871..9c04c01f70f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RRS26StartModeFlagFinder.java @@ -4,15 +4,17 @@ import java.util.List; import java.util.Arrays; import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; public class RRS26StartModeFlagFinder extends RaceLogAnalyzer { - private final List startModeFlags = Arrays.asList(Flags.PAPA, Flags.ZULU, Flags.BLACK, Flags.INDIA); - private StartProcedureTypeAnalyzer procedureAnalyzer; + private final static List defaultStartModeFlags = Arrays.asList(Flags.PAPA, Flags.ZULU, Flags.BLACK, Flags.INDIA); + + private final RacingProcedureTypeAnalyzer procedureAnalyzer; + private final List startModeFlags; /** * Searches for the start mode flag of a RRS26 race. @@ -21,24 +23,31 @@ public class RRS26StartModeFlagFinder extends RaceLogAnalyzer { * to be used to ensure a RRS26 race. Must operate on the same race log. Otherwise a * {@link IllegalArgumentException} is thrown. */ - public RRS26StartModeFlagFinder(StartProcedureTypeAnalyzer procedureAnalyzer, RaceLog raceLog) { + public RRS26StartModeFlagFinder(RacingProcedureTypeAnalyzer procedureAnalyzer, RaceLog raceLog) { + this(procedureAnalyzer, raceLog, defaultStartModeFlags); + } + + public RRS26StartModeFlagFinder(RacingProcedureTypeAnalyzer procedureAnalyzer, RaceLog raceLog, List startModeFlags) { super(raceLog); if (raceLog != procedureAnalyzer.getRaceLog()) { throw new IllegalArgumentException("Both analyzers must operate on the same race log."); } this.procedureAnalyzer = procedureAnalyzer; + this.startModeFlags = startModeFlags; } @Override protected Flags performAnalysis() { - StartProcedureType type = procedureAnalyzer.analyze(); - if (StartProcedureType.RRS26.equals(type)) { - for (RaceLogEvent event : getPassEventsDescending()) { - if (event instanceof RaceLogFlagEvent) { - RaceLogFlagEvent flagEvent = (RaceLogFlagEvent) event; - if (isStartModeFlagEvent(flagEvent)) { - return flagEvent.getUpperFlag(); - } + RacingProcedureType type = procedureAnalyzer.analyze(); + if (!RacingProcedureType.RRS26.equals(type)) { + return null; + } + + for (RaceLogEvent event : getPassEventsDescending()) { + if (event instanceof RaceLogFlagEvent) { + RaceLogFlagEvent flagEvent = (RaceLogFlagEvent) event; + if (isStartModeFlagEvent(flagEvent)) { + return flagEvent.getUpperFlag(); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java index 7ca600b5125..1d296cbd3cd 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RaceStatusAnalyzer.java @@ -1,14 +1,39 @@ package com.sap.sailing.domain.racelog.analyzing.impl; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.impl.BaseRaceLogEventVisitor; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; public class RaceStatusAnalyzer extends RaceLogAnalyzer { - - public RaceStatusAnalyzer(RaceLog raceLog) { + + public interface Clock { + TimePoint now(); + } + + public final static class StandardClock implements Clock { + @Override + public TimePoint now() { + return MillisecondsTimePoint.now(); + } + + } + + private final EventDispatcher eventDispatcher; + + public RaceStatusAnalyzer(RaceLog raceLog, ReadonlyRacingProcedure racingProcedure) { super(raceLog); + this.eventDispatcher = new EventDispatcher(new StandardClock(), racingProcedure); + } + + public RaceStatusAnalyzer(RaceLog raceLog, Clock clock, ReadonlyRacingProcedure racingProcedure) { + super(raceLog); + this.eventDispatcher = new EventDispatcher(clock, racingProcedure); } @Override @@ -16,11 +41,43 @@ public class RaceStatusAnalyzer extends RaceLogAnalyzer { for (RaceLogEvent event : getPassEventsDescending()) { if (event instanceof RaceLogRaceStatusEvent) { RaceLogRaceStatusEvent statusEvent = (RaceLogRaceStatusEvent) event; - return statusEvent.getNextStatus(); + statusEvent.accept(eventDispatcher); + return eventDispatcher.nextStatus; } } return RaceLogRaceStatus.UNSCHEDULED; } + + private class EventDispatcher extends BaseRaceLogEventVisitor { + + private final Clock clock; + private final ReadonlyRacingProcedure racingProcedure; + public RaceLogRaceStatus nextStatus; + + public EventDispatcher(Clock clock, ReadonlyRacingProcedure racingProcedure) { + this.clock = clock; + this.racingProcedure = racingProcedure; + this.nextStatus = RaceLogRaceStatus.UNKNOWN; + } + + @Override + public void visit(RaceLogStartTimeEvent event) { + TimePoint now = clock.now(); + if (racingProcedure.isStartphaseActive(event.getStartTime(), now)) { + nextStatus = RaceLogRaceStatus.STARTPHASE; + } else if (now.before(event.getStartTime())) { + nextStatus = RaceLogRaceStatus.SCHEDULED; + } else { + nextStatus = RaceLogRaceStatus.RUNNING; + } + }; + + @Override + public void visit(RaceLogRaceStatusEvent event) { + nextStatus = event.getNextStatus(); + }; + + }; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/StartProcedureTypeAnalyzer.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RacingProcedureTypeAnalyzer.java similarity index 66% rename from java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/StartProcedureTypeAnalyzer.java rename to java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RacingProcedureTypeAnalyzer.java index 8d6bfc7db5a..d9cb4231081 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/StartProcedureTypeAnalyzer.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/analyzing/impl/RacingProcedureTypeAnalyzer.java @@ -1,25 +1,25 @@ package com.sap.sailing.domain.racelog.analyzing.impl; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; -public class StartProcedureTypeAnalyzer extends RaceLogAnalyzer { +public class RacingProcedureTypeAnalyzer extends RaceLogAnalyzer { - public StartProcedureTypeAnalyzer(RaceLog raceLog) { + public RacingProcedureTypeAnalyzer(RaceLog raceLog) { super(raceLog); } @Override - protected StartProcedureType performAnalysis() { + protected RacingProcedureType performAnalysis() { for (RaceLogEvent event : getAllEventsDescending()) { if (event instanceof RaceLogStartProcedureChangedEvent) { RaceLogStartProcedureChangedEvent startProcedureEvent = (RaceLogStartProcedureChangedEvent) event; return startProcedureEvent.getStartProcedureType(); } } - return null; + return RacingProcedureType.UNKNOWN; } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java new file mode 100644 index 00000000000..bcd55c81e54 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/BaseRaceLogEventVisitor.java @@ -0,0 +1,88 @@ +package com.sap.sailing.domain.racelog.impl; + +import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogFlagEvent; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; +import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; +import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; +import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; + +/** + * Base class implementing {@link RaceLogEventVisitor}. Every method is implemented as a no-op. + */ +public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor { + + @Override + public void visit(RaceLogFlagEvent event) { + + } + + @Override + public void visit(RaceLogPassChangeEvent event) { + + } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + + } + + @Override + public void visit(RaceLogStartTimeEvent event) { + + } + + @Override + public void visit(RaceLogCourseAreaChangedEvent event) { + + } + + @Override + public void visit(RaceLogCourseDesignChangedEvent event) { + + } + + @Override + public void visit(RaceLogFinishPositioningListChangedEvent event) { + + } + + @Override + public void visit(RaceLogFinishPositioningConfirmedEvent event) { + + } + + @Override + public void visit(RaceLogPathfinderEvent event) { + + } + + @Override + public void visit(RaceLogGateLineOpeningTimeEvent event) { + + } + + @Override + public void visit(RaceLogStartProcedureChangedEvent event) { + + } + + @Override + public void visit(RaceLogProtestStartTimeEvent event) { + + } + + @Override + public void visit(RaceLogWindFixEvent event) { + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java new file mode 100644 index 00000000000..3eed3b472d8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/CompetitorResultsImpl.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.racelog.impl; + +import java.io.Serializable; +import java.util.ArrayList; + +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; + +public class CompetitorResultsImpl extends ArrayList> implements CompetitorResults { + + private static final long serialVersionUID = 4928351242700897387L; + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java new file mode 100644 index 00000000000..159c52f49c4 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/NoAddingRaceLogWrapper.java @@ -0,0 +1,192 @@ +package com.sap.sailing.domain.racelog.impl; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; + +/** + * Wrapper for a {@link RaceLog} which will ignore all calls trying to add an {@link RaceLogEvent}. All other + * functionality redirects to the wrapper {@link RaceLog}. + */ +public class NoAddingRaceLogWrapper implements RaceLog { + private static final long serialVersionUID = -5145744179072564993L; + + private static final Logger logger = Logger.getLogger(NoAddingRaceLogWrapper.class.getSimpleName()); + + private final RaceLog innerRaceLog; + + public NoAddingRaceLogWrapper(RaceLog innerRaceLog) { + this.innerRaceLog = innerRaceLog; + } + + @Override + public boolean add(RaceLogEvent event) { + logger.log(Level.FINEST, "add() called; event will not be added."); + return false; + } + + @Override + public Iterable add(RaceLogEvent event, UUID clientId) { + logger.log(Level.FINEST, "add() called; event will not be added."); + return Collections.emptyList(); + } + + @Override + public void addListener(RaceLogEventVisitor listener) { + innerRaceLog.addListener(listener); + } + + @Override + public void removeListener(RaceLogEventVisitor listener) { + innerRaceLog.removeListener(listener); + } + + @Override + public void addAllListeners(HashSet listeners) { + innerRaceLog.addAllListeners(listeners); + } + + @Override + public HashSet removeAllListeners() { + return innerRaceLog.removeAllListeners(); + } + + @Override + public Iterable getAllListeners() { + return innerRaceLog.getAllListeners(); + } + + @Override + public void lockForRead() { + innerRaceLog.lockForRead(); + } + + @Override + public void unlockAfterRead() { + innerRaceLog.unlockAfterRead(); + } + + @Override + public Iterable getFixes() { + return innerRaceLog.getFixes(); + } + + @Override + public Iterable getRawFixes() { + return innerRaceLog.getRawFixes(); + } + + @Override + public RaceLogEvent getLastFixAtOrBefore(TimePoint timePoint) { + return innerRaceLog.getLastFixAtOrBefore(timePoint); + } + + @Override + public RaceLogEvent getLastFixBefore(TimePoint timePoint) { + return innerRaceLog.getLastFixBefore(timePoint); + } + + @Override + public RaceLogEvent getLastRawFixAtOrBefore(TimePoint timePoint) { + return innerRaceLog.getLastRawFixAtOrBefore(timePoint); + } + + @Override + public RaceLogEvent getFirstFixAtOrAfter(TimePoint timePoint) { + return innerRaceLog.getFirstFixAtOrAfter(timePoint); + } + + @Override + public RaceLogEvent getFirstRawFixAtOrAfter(TimePoint timePoint) { + return innerRaceLog.getFirstRawFixAtOrAfter(timePoint); + } + + @Override + public RaceLogEvent getLastRawFixBefore(TimePoint timePoint) { + return innerRaceLog.getLastRawFixBefore(timePoint); + } + + @Override + public RaceLogEvent getFirstRawFixAfter(TimePoint timePoint) { + return innerRaceLog.getFirstRawFixAfter(timePoint); + } + + @Override + public RaceLogEvent getFirstFixAfter(TimePoint timePoint) { + return innerRaceLog.getFirstFixAfter(timePoint); + } + + @Override + public RaceLogEvent getFirstRawFix() { + return innerRaceLog.getFirstRawFix(); + } + + @Override + public RaceLogEvent getLastRawFix() { + return innerRaceLog.getLastRawFix(); + } + + @Override + public Iterator getFixesIterator(TimePoint startingAt, boolean inclusive) { + return innerRaceLog.getFixesIterator(startingAt, inclusive); + } + + @Override + public Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive) { + return innerRaceLog.getRawFixesIterator(startingAt, inclusive); + } + + @Override + public Serializable getId() { + return innerRaceLog; + } + + @Override + public int getCurrentPassId() { + return innerRaceLog.getCurrentPassId(); + } + + @Override + public boolean isEmpty() { + return innerRaceLog.isEmpty(); + } + + @Override + public Iterable getRawFixesDescending() { + return innerRaceLog.getRawFixesDescending(); + } + + @Override + public Iterable getFixesDescending() { + return innerRaceLog.getFixesDescending(); + } + + @Override + public Iterable getRawFixes(UUID clientId) { + return innerRaceLog.getRawFixes(clientId); + } + + @Override + public boolean load(RaceLogEvent event) { + return innerRaceLog.load(event); + } + + @Override + public Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + return innerRaceLog.getFixesDescendingIterator(startingAt, inclusive); + } + + @Override + public Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + return innerRaceLog.getRawFixesDescendingIterator(startingAt, inclusive); + } +} diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/racelog/impl/RaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java similarity index 91% rename from mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/racelog/impl/RaceLogChangedVisitor.java rename to java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java index 64105d2a686..59e8d6fbd44 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/racelog/impl/RaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogChangedVisitor.java @@ -1,5 +1,6 @@ -package com.sap.sailing.racecommittee.app.domain.racelog.impl; +package com.sap.sailing.domain.racelog.impl; +import com.sap.sailing.domain.racelog.RaceLogChangedListener; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; @@ -14,7 +15,6 @@ import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; -import com.sap.sailing.racecommittee.app.domain.racelog.RaceLogChangedListener; public class RaceLogChangedVisitor implements RaceLogEventVisitor { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseAreaChangeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseAreaChangeEventImpl.java index 6304f0ff9f5..8d987d89efb 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseAreaChangeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseAreaChangeEventImpl.java @@ -6,17 +6,18 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; public class RaceLogCourseAreaChangeEventImpl extends RaceLogEventImpl implements RaceLogCourseAreaChangedEvent { private static final long serialVersionUID = -3943108136024977860L; - private Serializable courseAreaId; + private final Serializable courseAreaId; public RaceLogCourseAreaChangeEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, Serializable courseAreaId) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, Serializable courseAreaId) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.courseAreaId = courseAreaId; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseDesignChangedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseDesignChangedEventImpl.java index 53ca4ff32e2..7448d221283 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseDesignChangedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogCourseDesignChangedEventImpl.java @@ -7,16 +7,17 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; public class RaceLogCourseDesignChangedEventImpl extends RaceLogEventImpl implements RaceLogCourseDesignChangedEvent { private static final long serialVersionUID = 1565936170747420547L; - private CourseBase courseDesign; + private final CourseBase courseDesign; public RaceLogCourseDesignChangedEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, CourseBase courseDesign) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, CourseBase courseDesign) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.courseDesign = courseDesign; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java new file mode 100755 index 00000000000..5bdfe31a1de --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventAuthorImpl.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.racelog.impl; + +import com.sap.sailing.domain.common.impl.NamedImpl; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; + +public class RaceLogEventAuthorImpl extends NamedImpl implements RaceLogEventAuthor { + private static final long serialVersionUID = -5602802911563685812L; + private final int priority; + + public static RaceLogEventAuthor createCompatibilityAuthor() { + return new RaceLogEventAuthorImpl(NAME_COMPATIBILITY, PRIORITY_COMPATIBILITY); + } + + public RaceLogEventAuthorImpl(String name, int priority) { + super(name); + this.priority = priority; + } + + @Override + public int getPriority() { + return priority; + } + + @Override + public int compareTo(RaceLogEventAuthor other) { + return new Integer(other.getPriority()).compareTo(getPriority()); + } + + @Override + public String toString() { + return super.toString()+" with priority "+getPriority(); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventComparator.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventComparator.java index f7e1bb326b2..36d184f8b4a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventComparator.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventComparator.java @@ -5,38 +5,50 @@ import java.util.Comparator; import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.tracking.impl.TimedComparator; +/** + * Comparator sorting by pass, then by {@link RaceLogEventAuthor}, then by {@link RaceLogEvent#getCreatedAt()} + * timestamp. + * + * If one of the passed objects is not a {@link RaceLogEvent}, sorting is done by {@link Timed#getTimePoint()}. + */ public enum RaceLogEventComparator implements Comparator, Serializable { INSTANCE; - + private Comparator timedComparator; - + private RaceLogEventComparator() { this.timedComparator = TimedComparator.INSTANCE; } - + @Override public int compare(Timed o1, Timed o2) { if (o1 instanceof RaceLogEvent && o2 instanceof RaceLogEvent) { - return compareEvents((RaceLogEvent)o1, (RaceLogEvent)o2); + return compareEvents((RaceLogEvent) o1, (RaceLogEvent) o2); } - + // fallback to timed comparison return timedComparator.compare(o1, o2); } private int compareEvents(RaceLogEvent e1, RaceLogEvent e2) { int result = comparePasses(e1, e2); - return result == 0 ? compareTimes(e1, e2) : result; + return result == 0 ? compareAuthorPriorities(e1, e2) : result; } private int comparePasses(RaceLogEvent e1, RaceLogEvent e2) { return e1.getPassId() - e2.getPassId(); } - private int compareTimes(RaceLogEvent e1, RaceLogEvent e2) { - int result = timedComparator.compare(e1, e2); + private int compareAuthorPriorities(RaceLogEvent e1, RaceLogEvent e2) { + int result = e1.getAuthor().compareTo(e2.getAuthor()); + return result == 0 ? compareCreatedAtTimes(e1, e2) : result; + } + + private int compareCreatedAtTimes(RaceLogEvent e1, RaceLogEvent e2) { + int result = e1.getCreatedAt().compareTo(e2.getCreatedAt()); return result == 0 ? compareIds(e1, e2) : result; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java index 3813df8eff8..6ca7b073a19 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventFactoryImpl.java @@ -7,15 +7,15 @@ import java.util.UUID; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventFactory; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; @@ -32,172 +32,172 @@ import com.sap.sailing.domain.tracking.Wind; public class RaceLogEventFactoryImpl implements RaceLogEventFactory { @Override - public RaceLogFlagEvent createFlagEvent(TimePoint timePoint, Serializable id, List involvedBoats, - int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { - return new RaceLogFlagEventImpl(MillisecondsTimePoint.now(), timePoint, id, involvedBoats, passId, upperFlag, + public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { + return new RaceLogFlagEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, passId, + upperFlag, lowerFlag, isDisplayed); + } + + @Override + public RaceLogFlagEvent createFlagEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Flags upperFlag, + Flags lowerFlag, boolean isDisplayed) { + return createFlagEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, upperFlag, lowerFlag, isDisplayed); } @Override - public RaceLogFlagEvent createFlagEvent(TimePoint timePoint, int passId, Flags upperFlag, Flags lowerFlag, - boolean isDisplayed) { - return createFlagEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, upperFlag, lowerFlag, - isDisplayed); + public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List involvedBoats, int passId, TimePoint startTime) { + return new RaceLogStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, involvedBoats, + passId, startTime); } @Override - public RaceLogStartTimeEvent createStartTimeEvent(TimePoint timePoint, Serializable id, - List involvedBoats, int passId, TimePoint startTime) { - return new RaceLogStartTimeEventImpl(MillisecondsTimePoint.now(), timePoint, id, involvedBoats, passId, - startTime); + public RaceLogStartTimeEvent createStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, TimePoint startTime) { + return createStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, startTime); } @Override - public RaceLogStartTimeEvent createStartTimeEvent(TimePoint timePoint, int passId, TimePoint startTime) { - return createStartTimeEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, startTime); + public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus) { + return new RaceLogRaceStatusEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, nextStatus); } @Override - public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, RaceLogRaceStatus nextStatus) { - return new RaceLogRaceStatusEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, - nextStatus); + public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, RaceLogRaceStatus nextStatus) { + return createRaceStatusEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, nextStatus); } @Override - public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint timePoint, int passId, RaceLogRaceStatus nextStatus) { - return createRaceStatusEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, nextStatus); + public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId) { + return new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId); } @Override - public RaceLogPassChangeEvent createPassChangeEvent(TimePoint timePoint, Serializable id, - List competitors, int passId) { - return new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId); + public RaceLogPassChangeEvent createPassChangeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId) { + return createPassChangeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId); } @Override - public RaceLogPassChangeEvent createPassChangeEvent(TimePoint timePoint, int passId) { - return createPassChangeEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId); + public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Serializable courseAreaId) { + return new RaceLogCourseAreaChangeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, courseAreaId); } @Override - public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, Serializable courseAreaId) { - return new RaceLogCourseAreaChangeEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, - courseAreaId); + public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Serializable courseAreaId) { + return createCourseAreaChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, courseAreaId); } @Override - public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint timePoint, int passId, - Serializable courseAreaId) { - return createCourseAreaChangedEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, - courseAreaId); + public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, CourseBase courseData) { + return new RaceLogCourseDesignChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, courseData); } @Override - public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, CourseBase courseData) { - return new RaceLogCourseDesignChangedEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, - courseData); + public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CourseBase courseData) { + return createCourseDesignChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, courseData); } @Override - public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint timePoint, int passId, - CourseBase courseData) { - return createCourseDesignChangedEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, - courseData); + public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, Serializable id, List competitors, + int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningListChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, + id, competitors, passId, positionedCompetitors); } @Override - public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint timePoint, - Serializable id, List competitors, int passId, - List> positionedCompetitors) { - return new RaceLogFinishPositioningListChangedEventImpl(MillisecondsTimePoint.now(), timePoint, id, + public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, int passId, CompetitorResults positionedCompetitors) { + return createFinishPositioningListChangedEvent(logicalTimePoint, author, UUID.randomUUID(), + new ArrayList(), passId, positionedCompetitors); + } + + @Override + public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, + RaceLogEventAuthor author, Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningConfirmedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, positionedCompetitors); } @Override - public RaceLogFinishPositioningListChangedEventImpl createFinishPositioningListChangedEvent(TimePoint timePoint, - int passId, List> positionedCompetitors) { - return createFinishPositioningListChangedEvent(timePoint, UUID.randomUUID(), new ArrayList(), - passId, positionedCompetitors); + public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, CompetitorResults positionedCompetitors) { + return createFinishPositioningConfirmedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, positionedCompetitors); } @Override - public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint timePoint, - Serializable id, List competitors, int passId, List> positionedCompetitors) { - return new RaceLogFinishPositioningConfirmedEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, - passId, positionedCompetitors); + public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, String pathfinderId) { + return new RaceLogPathfinderEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, pathfinderId); } @Override - public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint timePoint, int passId, - List> positionedCompetitors) { - return createFinishPositioningConfirmedEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, positionedCompetitors); + public RaceLogPathfinderEvent createPathfinderEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, String pathfinderId) { + return createPathfinderEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, pathfinderId); } @Override - public RaceLogPathfinderEvent createPathfinderEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, String pathfinderId) { - return new RaceLogPathfinderEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, - pathfinderId); + public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, Long gateLaunchStopTime, Long golfDownTime) { + return new RaceLogGateLineOpeningTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, + passId, gateLaunchStopTime, golfDownTime); } @Override - public RaceLogPathfinderEvent createPathfinderEvent(TimePoint timePoint, int passId, String pathfinderId) { - return createPathfinderEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, pathfinderId); + public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, Long gateLaunchStopTime, Long golfDownTime) { + return createGateLineOpeningTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), + passId, gateLaunchStopTime, golfDownTime); } @Override - public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, Long gateLineOpeningTimeInMillis) { - return new RaceLogGateLineOpeningTimeEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, - gateLineOpeningTimeInMillis); + public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, RacingProcedureType type) { + return new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, + competitors, passId, type); } @Override - public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint timePoint, int passId, - Long gateLineOpeningTimeInMillis) { - return createGateLineOpeningTimeEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, - gateLineOpeningTimeInMillis); + public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, RacingProcedureType type) { + return createStartProcedureChangedEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, type); } @Override - public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, StartProcedureType type) { - return new RaceLogStartProcedureChangedEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, - passId, type); + public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + Serializable id, List competitors, int passId, TimePoint protestStartTime) { + return new RaceLogProtestStartTimeEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, + competitors, passId, protestStartTime); } @Override - public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint timePoint, int passId, - StartProcedureType type) { - return createStartProcedureChangedEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, type); - } - - @Override - public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint timePoint, Serializable id, - List competitors, int passId, TimePoint protestStartTime) { - return new RaceLogProtestStartTimeEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, + public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, + int passId, TimePoint protestStartTime) { + return createProtestStartTimeEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, protestStartTime); } @Override - public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint timePoint, int passId, - TimePoint protestStartTime) { - return createProtestStartTimeEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, - protestStartTime); + public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, int passId, Wind wind) { + return createWindFixEvent(logicalTimePoint, author, UUID.randomUUID(), new ArrayList(), passId, wind); } @Override - public RaceLogWindFixEvent createWindFixEvent(TimePoint timePoint, int passId, Wind wind) { - return createWindFixEvent(timePoint, UUID.randomUUID(), new ArrayList(), passId, wind); - } - - @Override - public RaceLogWindFixEvent createWindFixEvent(TimePoint timePoint, Serializable id, List competitors, - int passId, Wind wind) { - return new RaceLogWindFixEventImpl(MillisecondsTimePoint.now(), timePoint, id, competitors, passId, wind); + public RaceLogWindFixEvent createWindFixEvent(TimePoint logicalTimePoint, RaceLogEventAuthor author, Serializable id, + List competitors, int passId, Wind wind) { + return new RaceLogWindFixEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, wind); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventImpl.java index 9af8367e157..45a98036713 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventImpl.java @@ -6,21 +6,24 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; public abstract class RaceLogEventImpl implements RaceLogEvent { private static final long serialVersionUID = -5810258278984777732L; - private TimePoint createdAt; - private TimePoint logicalTimePoint; - private Serializable id; - private List involvedBoats; - private int passId; + private final TimePoint createdAt; + private final TimePoint logicalTimePoint; + private final Serializable id; + private final List involvedBoats; + private final int passId; + private final RaceLogEventAuthor author; - public RaceLogEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId) { + public RaceLogEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId) { this.createdAt = createdAt; - this.logicalTimePoint = pTimePoint; + this.author = author; + this.logicalTimePoint = logicalTimePoint; this.id = pId; this.involvedBoats = pInvolvedBoats; this.passId = pPassId; @@ -31,8 +34,16 @@ public abstract class RaceLogEventImpl implements RaceLogEvent { return createdAt; } + /* + * Redirects to getCreatedAt() + */ @Override public TimePoint getTimePoint() { + return getCreatedAt(); + } + + @Override + public TimePoint getLogicalTimePoint() { return logicalTimePoint; } @@ -50,5 +61,16 @@ public abstract class RaceLogEventImpl implements RaceLogEvent { public int getPassId() { return passId; } + + @Override + public RaceLogEventAuthor getAuthor() { + return author; + } + @Override + public String toString() { + return getClass().getName() + ": createdAt: " + getCreatedAt() + ", logicalTimePoint: " + getLogicalTimePoint() + + ", id: " + getId() + ", involvedBoats: " + getInvolvedBoats() + ", passId: " + getPassId() + + ", author: "+ getAuthor(); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java index 3f793d6037d..cebfa95dd57 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogEventRestoreFactoryImpl.java @@ -5,14 +5,14 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventRestoreFactory; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; @@ -30,81 +30,83 @@ import com.sap.sailing.domain.tracking.Wind; public class RaceLogEventRestoreFactoryImpl extends RaceLogEventFactoryImpl implements RaceLogEventRestoreFactory { @Override - public RaceLogFlagEvent createFlagEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { - return new RaceLogFlagEventImpl(createdAt, logicalTimePoint, id, involvedBoats, passId, upperFlag, lowerFlag, isDisplayed); + public RaceLogFlagEvent createFlagEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List involvedBoats, int passId, Flags upperFlag, Flags lowerFlag, boolean isDisplayed) { + return new RaceLogFlagEventImpl(createdAt, author, logicalTimePoint, id, involvedBoats, passId, upperFlag, lowerFlag, isDisplayed); } @Override - public RaceLogStartTimeEvent createStartTimeEvent(TimePoint createdAt, TimePoint logicalTimePoint, Serializable id, - List involvedBoats, int passId, TimePoint startTime) { - return new RaceLogStartTimeEventImpl(createdAt, logicalTimePoint, id, involvedBoats, passId, startTime); + public RaceLogStartTimeEvent createStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List involvedBoats, int passId, TimePoint startTime) { + return new RaceLogStartTimeEventImpl(createdAt, author, logicalTimePoint, id, involvedBoats, passId, startTime); } @Override - public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus) { - return new RaceLogRaceStatusEventImpl(createdAt, logicalTimePoint, id, competitors, passId, nextStatus); + public RaceLogRaceStatusEvent createRaceStatusEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, RaceLogRaceStatus nextStatus) { + return new RaceLogRaceStatusEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, nextStatus); } @Override - public RaceLogPassChangeEvent createPassChangeEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId) { - return new RaceLogPassChangeEventImpl(createdAt, logicalTimePoint, id, competitors, passId); + public RaceLogPassChangeEvent createPassChangeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId) { + return new RaceLogPassChangeEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId); } @Override - public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, Serializable courseAreaId) { - return new RaceLogCourseAreaChangeEventImpl(createdAt, logicalTimePoint, id, competitors, passId, courseAreaId); + public RaceLogCourseAreaChangedEvent createCourseAreaChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, Serializable courseAreaId) { + return new RaceLogCourseAreaChangeEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId); } @Override public RaceLogCourseDesignChangedEvent createCourseDesignChangedEvent(TimePoint createdAt, - TimePoint logicalTimePoint, Serializable id, List competitors, int passId, CourseBase courseData) { - return new RaceLogCourseDesignChangedEventImpl(createdAt, logicalTimePoint, id, competitors, passId, courseData); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, int passId, CourseBase courseData) { + return new RaceLogCourseDesignChangedEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, courseData); } @Override public RaceLogFinishPositioningListChangedEvent createFinishPositioningListChangedEvent(TimePoint createdAt, - TimePoint logicalTimePoint, Serializable id, List competitors, int passId, List> positionedCompetitors) { - return new RaceLogFinishPositioningListChangedEventImpl(createdAt, logicalTimePoint, id, competitors, passId, positionedCompetitors); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningListChangedEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); } @Override public RaceLogFinishPositioningConfirmedEvent createFinishPositioningConfirmedEvent(TimePoint createdAt, - TimePoint logicalTimePoint, Serializable id, List competitors, int passId, List> positionedCompetitors) { - return new RaceLogFinishPositioningConfirmedEventImpl(createdAt, logicalTimePoint, id, competitors, passId, positionedCompetitors); + RaceLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, List competitors, int passId, CompetitorResults positionedCompetitors) { + return new RaceLogFinishPositioningConfirmedEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, positionedCompetitors); } @Override - public RaceLogPathfinderEvent createPathfinderEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, String pathfinderId) { - return new RaceLogPathfinderEventImpl(createdAt, logicalTimePoint, id, competitors, passId, pathfinderId); + public RaceLogPathfinderEvent createPathfinderEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, String pathfinderId) { + return new RaceLogPathfinderEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, pathfinderId); } @Override - public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint createdAt, TimePoint logicalTimePoint, - Serializable id, List competitors, int passId, Long gateLineOpeningTime) { - return new RaceLogGateLineOpeningTimeEventImpl(createdAt, logicalTimePoint, id, competitors, passId, gateLineOpeningTime); + public RaceLogGateLineOpeningTimeEvent createGateLineOpeningTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, Long gateLaunchStopTime, + Long golfDownTime) { + return new RaceLogGateLineOpeningTimeEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, gateLaunchStopTime, + golfDownTime); } @Override - public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, List competitors, int passId, StartProcedureType type) { - return new RaceLogStartProcedureChangedEventImpl(createdAt, timePoint, id, competitors, passId, type); + public RaceLogStartProcedureChangedEvent createStartProcedureChangedEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, RacingProcedureType type) { + return new RaceLogStartProcedureChangedEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, type); } @Override - public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint createdAt, TimePoint timePoint, - Serializable id, List competitors, int passId, TimePoint protestStartTime) { - return new RaceLogProtestStartTimeEventImpl(createdAt, timePoint, id, competitors, passId, protestStartTime); + public RaceLogProtestStartTimeEvent createProtestStartTimeEvent(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint logicalTimePoint, Serializable id, List competitors, int passId, TimePoint protestStartTime) { + return new RaceLogProtestStartTimeEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, protestStartTime); } @Override - public RaceLogWindFixEvent createWindFixEvent(TimePoint createdAt, TimePoint timePoint, Serializable id, - List competitors, Integer passId, Wind wind) { - return new RaceLogWindFixEventImpl(createdAt, timePoint, id, competitors, passId, wind); + public RaceLogWindFixEvent createWindFixEvent(TimePoint createdAt, RaceLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, List competitors, Integer passId, Wind wind) { + return new RaceLogWindFixEventImpl(createdAt, author, logicalTimePoint, id, competitors, passId, wind); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningConfirmedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningConfirmedEventImpl.java index b57b8a28e23..f6897b0fed3 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningConfirmedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningConfirmedEventImpl.java @@ -4,9 +4,9 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; @@ -15,8 +15,8 @@ public class RaceLogFinishPositioningConfirmedEventImpl extends RaceLogFinishPos private static final long serialVersionUID = 5028181339200048499L; public RaceLogFinishPositioningConfirmedEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, List> positionedCompetitors) { - super(createdAt, pTimePoint, pId, pCompetitors, pPassId, positionedCompetitors); + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, CompetitorResults positionedCompetitors) { + super(createdAt, author, pTimePoint, pId, pCompetitors, pPassId, positionedCompetitors); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningEventImpl.java index c80cef23f40..5d8437b2cc8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningEventImpl.java @@ -4,25 +4,25 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningEvent; public abstract class RaceLogFinishPositioningEventImpl extends RaceLogEventImpl implements RaceLogFinishPositioningEvent { private static final long serialVersionUID = -8168584588697908309L; - protected List> positionedCompetitors; + private final CompetitorResults positionedCompetitors; - public RaceLogFinishPositioningEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId, List> positionedCompetitors) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogFinishPositioningEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, CompetitorResults positionedCompetitors) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.positionedCompetitors = positionedCompetitors; } @Override - public List> getPositionedCompetitorsIDsNamesMaxPointsReasons() { + public CompetitorResults getPositionedCompetitorsIDsNamesMaxPointsReasons() { return positionedCompetitors; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningListChangedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningListChangedEventImpl.java index 266a3d72e9f..fda2333dd1f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningListChangedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFinishPositioningListChangedEventImpl.java @@ -4,9 +4,9 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.Util.Triple; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; @@ -15,8 +15,8 @@ public class RaceLogFinishPositioningListChangedEventImpl extends RaceLogFinishP private static final long serialVersionUID = -8167472925561954739L; public RaceLogFinishPositioningListChangedEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List competitors, int pPassId, List> positionedCompetitors) { - super(createdAt, pTimePoint, pId, competitors, pPassId, positionedCompetitors); + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List competitors, int pPassId, CompetitorResults positionedCompetitors) { + super(createdAt, author, pTimePoint, pId, competitors, pPassId, positionedCompetitors); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFlagEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFlagEventImpl.java index d43cc2eb86c..5e4a471092d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFlagEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogFlagEventImpl.java @@ -6,18 +6,19 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogFlagEvent; public class RaceLogFlagEventImpl extends RaceLogEventImpl implements RaceLogFlagEvent { private static final long serialVersionUID = 6333303528852541914L; - private Flags upperFlag; - private Flags lowerFlag; - private boolean isDisplayed; + private final Flags upperFlag; + private final Flags lowerFlag; + private final boolean isDisplayed; - public RaceLogFlagEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, Flags pUpperFlag, Flags pLowerFlag, boolean pIsDisplayed) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogFlagEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, Flags pUpperFlag, Flags pLowerFlag, boolean pIsDisplayed) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.upperFlag = pUpperFlag; this.lowerFlag = pLowerFlag; this.isDisplayed = pIsDisplayed; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogGateLineOpeningTimeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogGateLineOpeningTimeEventImpl.java index 0f90197e34c..717d34a258c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogGateLineOpeningTimeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogGateLineOpeningTimeEventImpl.java @@ -5,18 +5,20 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; public class RaceLogGateLineOpeningTimeEventImpl extends RaceLogEventImpl implements RaceLogGateLineOpeningTimeEvent { private static final long serialVersionUID = 793529890804809490L; - private Long gateLineOpeningTime; + private final GateLineOpeningTimes gateLineOpeningTimes; public RaceLogGateLineOpeningTimeEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, Long gateLineOpeningTime) { - super(createdAt, pTimePoint, pId, pCompetitors, pPassId); - this.gateLineOpeningTime = gateLineOpeningTime; + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, + long gateLaunchStopTime, long golfDownTime) { + super(createdAt, author, pTimePoint, pId, pCompetitors, pPassId); + this.gateLineOpeningTimes = new GateLineOpeningTimes(gateLaunchStopTime, golfDownTime); } @Override @@ -25,8 +27,8 @@ public class RaceLogGateLineOpeningTimeEventImpl extends RaceLogEventImpl implem } @Override - public Long getGateLineOpeningTime() { - return this.gateLineOpeningTime; + public GateLineOpeningTimes getGateLineOpeningTimes() { + return this.gateLineOpeningTimes; } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java index b1fd70195b4..2251aabc9e6 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogImpl.java @@ -3,9 +3,13 @@ package com.sap.sailing.domain.racelog.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; import java.util.NavigableSet; import java.util.Set; +import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -13,22 +17,39 @@ import com.sap.sailing.domain.base.Timed; import com.sap.sailing.domain.racelog.RaceLog; import com.sap.sailing.domain.racelog.RaceLogEvent; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.impl.PartialNavigableSetView; import com.sap.sailing.domain.tracking.impl.TrackImpl; import com.sap.sailing.util.impl.ArrayListNavigableSet; /** + * {@link Track} implementation for {@link RaceLogEvent}s. + * + *

* "Fix" validity is decided based on the {@link #getCurrentPassId() current pass}. The validity is not cached. + *

+ * + *

+ * {@link TrackImpl#getDummyFix(com.sap.sailing.domain.common.TimePoint)} is not overridden, see + * {@link RaceLogEventComparator} for sorting when interface methods like + * {@link Track#getFirstFixAfter(com.sap.sailing.domain.common.TimePoint)} are used. + *

+ * */ public class RaceLogImpl extends TrackImpl implements RaceLog { private static final long serialVersionUID = -176745401321893502L; private static final String DefaultLockName = RaceLogImpl.class.getName() + ".lock"; private final static Logger logger = Logger.getLogger(RaceLogImpl.class.getName()); - + + /** + * Clients can use the {@link #add(RaceLogEvent, UUID)} method + */ + private transient Map> eventsDeliveredToClient = new HashMap>(); + private final Serializable id; private transient Set listeners; private int currentPassId; - + /** * Initializes a new {@link RaceLogImpl} with the default lock name. */ @@ -38,30 +59,33 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { /** * Initializes a new {@link RaceLogImpl}. - * @param nameForReadWriteLock name of lock. + * + * @param nameForReadWriteLock + * name of lock. */ public RaceLogImpl(String nameForReadWriteLock, Serializable identifier) { super(new ArrayListNavigableSet(RaceLogEventComparator.INSTANCE), nameForReadWriteLock); - + this.listeners = new HashSet(); this.currentPassId = DefaultPassId; this.id = identifier; } - + @Override public Serializable getId() { return this.id; } - + @Override public int getCurrentPassId() { return currentPassId; } - + /** - * Sets a new active pass id. - * Ignored if new and current are equal. - * @param newPassId to be set. + * Sets a new active pass id. Ignored if new and current are equal. + * + * @param newPassId + * to be set. */ public void setCurrentPassId(int newPassId) { if (newPassId != this.currentPassId) { @@ -81,6 +105,8 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { } if (isAdded) { logger.finer(String.format("%s (%s) was added to log.", event, event.getClass().getName())); + // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time + // point setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); notifyListenersAboutReceive(event); } else { @@ -88,7 +114,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { } return isAdded; } - + @Override public boolean load(RaceLogEvent event) { boolean isAdded = false; @@ -102,21 +128,57 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { logger.finer(String.format("%s (%s) was loaded into log.", event, event.getClass().getName())); setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); } else { - logger.warning(String.format("%s (%s) was not loaded into log. Ignoring", event, event.getClass().getName())); + logger.warning(String + .format("%s (%s) was not loaded into log. Ignoring", event, event.getClass().getName())); } return isAdded; } + @Override + public Iterable add(RaceLogEvent event, UUID clientId) { + boolean isAdded = false; + lockForWrite(); + try { + isAdded = getInternalRawFixes().add(event); + } finally { + unlockAfterWrite(); + } + if (isAdded) { + logger.finer(String.format("%s (%s) was added to log.", event, event.getClass().getName())); + // FIXME with out-of-order delivery would destroy currentPassId; need to check at least the createdAt time + // point + setCurrentPassId(Math.max(event.getPassId(), this.currentPassId)); + notifyListenersAboutReceive(event); + } else { + logger.warning(String.format("%s (%s) was not added to log. Ignoring", event, event.getClass().getName())); + } + // FIXME lock for read getInternalRawFixes? + LinkedHashSet stillToDeliverToClient = new LinkedHashSet(getInternalRawFixes()); + stillToDeliverToClient.remove(event); + Set deliveredToClient = eventsDeliveredToClient.get(clientId); + if (deliveredToClient != null) { + stillToDeliverToClient.removeAll(deliveredToClient); + } else { + deliveredToClient = new HashSet(); + eventsDeliveredToClient.put(clientId, deliveredToClient); + } + deliveredToClient.addAll(stillToDeliverToClient); + deliveredToClient.add(event); + return stillToDeliverToClient; + } + protected void notifyListenersAboutReceive(RaceLogEvent event) { + Set workingListeners = new HashSet(); synchronized (listeners) { - for (RaceLogEventVisitor listener : listeners) { - try { - event.accept(listener); - } catch (Throwable t) { - logger.log(Level.SEVERE, "RaceLogEventVisitor " + listener + " threw exception " + t.getMessage()); - logger.throwing(RaceLogImpl.class.getName(), "notifyListenersAboutReceive(RaceLogEvent)", t); - t.printStackTrace(); - } + workingListeners.addAll(listeners); + } + for (RaceLogEventVisitor listener : workingListeners) { + try { + event.accept(listener); + } catch (Throwable t) { + logger.log(Level.SEVERE, "RaceLogEventVisitor " + listener + " threw exception " + t.getMessage()); + logger.throwing(RaceLogImpl.class.getName(), "notifyListenersAboutReceive(RaceLogEvent)", t); + t.printStackTrace(); } } } @@ -139,7 +201,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { listeners.remove(listener); } } - + @Override protected NavigableSet getInternalFixes() { return new PartialNavigableSetView(super.getInternalFixes()) { @@ -149,20 +211,21 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { } }; } - + /** * When deserializing, needs to initialize empty set of listeners. */ private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); listeners = new HashSet(); + eventsDeliveredToClient = new HashMap>(); } @Override public Iterable getRawFixesDescending() { return getRawFixes().descendingSet(); } - + @Override public Iterable getFixesDescending() { return getFixes().descendingSet(); @@ -170,7 +233,7 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { @Override public HashSet removeAllListeners() { - synchronized(listeners) { + synchronized (listeners) { HashSet clonedListeners = new HashSet(listeners); listeners = new HashSet(); return clonedListeners; @@ -188,5 +251,17 @@ public class RaceLogImpl extends TrackImpl implements RaceLog { public Iterable getAllListeners() { return this.listeners; } - + + @Override + public Iterable getRawFixes(UUID clientId) { + assertReadLock(); + NavigableSet result = getRawFixes(); + Set edtc = eventsDeliveredToClient.get(clientId); + if (edtc == null) { + edtc = new HashSet(); + eventsDeliveredToClient.put(clientId, edtc); + } + edtc.addAll(result); + return result; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPassChangeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPassChangeEventImpl.java index d6a211e079b..5c7e77dd0a4 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPassChangeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPassChangeEventImpl.java @@ -5,15 +5,16 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; public class RaceLogPassChangeEventImpl extends RaceLogEventImpl implements RaceLogPassChangeEvent { private static final long serialVersionUID = -3737606977320640630L; - public RaceLogPassChangeEventImpl(TimePoint createdAt, TimePoint pTimePoint, - Serializable pId, List pInvolvedBoats, int pPassId) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogPassChangeEventImpl(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPathfinderEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPathfinderEventImpl.java index 556ca263cc7..f8227d03b7b 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPathfinderEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogPathfinderEventImpl.java @@ -5,17 +5,18 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; public class RaceLogPathfinderEventImpl extends RaceLogEventImpl implements RaceLogPathfinderEvent { private static final long serialVersionUID = -1654474931330970804L; - private String pathfinderId; + private final String pathfinderId; public RaceLogPathfinderEventImpl(TimePoint createdAt, - TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, String pathfinderId) { - super(createdAt, pTimePoint, pId, pCompetitors, pPassId); + RaceLogEventAuthor author, TimePoint pTimePoint, Serializable pId, List pCompetitors, int pPassId, String pathfinderId) { + super(createdAt, author, pTimePoint, pId, pCompetitors, pPassId); this.pathfinderId = pathfinderId; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogProtestStartTimeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogProtestStartTimeEventImpl.java index 775fe9bfd91..1ad2c53d865 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogProtestStartTimeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogProtestStartTimeEventImpl.java @@ -5,17 +5,18 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; public class RaceLogProtestStartTimeEventImpl extends RaceLogEventImpl implements RaceLogProtestStartTimeEvent { private static final long serialVersionUID = -1800827552916395996L; - private TimePoint protestStartTime; + private final TimePoint protestStartTime; - public RaceLogProtestStartTimeEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId, TimePoint protestStartTime) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogProtestStartTimeEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, TimePoint protestStartTime) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.protestStartTime = protestStartTime; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventImpl.java index 7461873ee56..7c9ddaa7113 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogRaceStatusEventImpl.java @@ -6,17 +6,18 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; public class RaceLogRaceStatusEventImpl extends RaceLogEventImpl implements RaceLogRaceStatusEvent { private static final long serialVersionUID = -8809758843066724482L; - protected RaceLogRaceStatus nextStatus; + private final RaceLogRaceStatus nextStatus; - public RaceLogRaceStatusEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId, RaceLogRaceStatus nextStatus) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogRaceStatusEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, RaceLogRaceStatus nextStatus) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.nextStatus = nextStatus; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartProcedureChangedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartProcedureChangedEventImpl.java index c530bec7006..0f85f2b7572 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartProcedureChangedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartProcedureChangedEventImpl.java @@ -5,7 +5,8 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.racelog.StartProcedureType; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; @@ -14,17 +15,17 @@ public class RaceLogStartProcedureChangedEventImpl extends RaceLogEventImpl impl private static final long serialVersionUID = -8102049547516570034L; - private StartProcedureType startProcedureType; + private final RacingProcedureType startProcedureType; - public RaceLogStartProcedureChangedEventImpl(TimePoint createdAt, TimePoint pTimePoint, - Serializable pId, List pInvolvedBoats, int pPassId, StartProcedureType startProcedureType) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogStartProcedureChangedEventImpl(TimePoint createdAt, RaceLogEventAuthor author, + TimePoint pTimePoint, Serializable pId, List pInvolvedBoats, int pPassId, RacingProcedureType startProcedureType) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.startProcedureType = startProcedureType; } @Override - public StartProcedureType getStartProcedureType() { + public RacingProcedureType getStartProcedureType() { return startProcedureType; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartTimeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartTimeEventImpl.java index ae69e2e4d0d..313d8744d74 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartTimeEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogStartTimeEventImpl.java @@ -6,17 +6,18 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; public class RaceLogStartTimeEventImpl extends RaceLogRaceStatusEventImpl implements RaceLogStartTimeEvent { private static final long serialVersionUID = 8185811395997196162L; - private TimePoint startTime; + private final TimePoint startTime; - public RaceLogStartTimeEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId, TimePoint pStartTime) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId, RaceLogRaceStatus.SCHEDULED); + public RaceLogStartTimeEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, TimePoint pStartTime) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId, RaceLogRaceStatus.SCHEDULED); this.startTime = pStartTime; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogWindFixEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogWindFixEventImpl.java index 44defb957d9..d4d5b22d757 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogWindFixEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/impl/RaceLogWindFixEventImpl.java @@ -5,6 +5,7 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; import com.sap.sailing.domain.racelog.RaceLogEventVisitor; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.tracking.Wind; @@ -14,9 +15,9 @@ public class RaceLogWindFixEventImpl extends RaceLogEventImpl implements RaceLog private final Wind windFix; - public RaceLogWindFixEventImpl(TimePoint createdAt, TimePoint pTimePoint, Serializable pId, - List pInvolvedBoats, int pPassId, Wind windFix) { - super(createdAt, pTimePoint, pId, pInvolvedBoats, pPassId); + public RaceLogWindFixEventImpl(TimePoint createdAt, RaceLogEventAuthor author, TimePoint pTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, Wind windFix) { + super(createdAt, author, pTimePoint, pId, pInvolvedBoats, pPassId); this.windFix = windFix; } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java new file mode 100644 index 00000000000..5b02fd93a54 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceState.java @@ -0,0 +1,142 @@ +package com.sap.sailing.domain.racelog.state; + +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedurePrerequisiteAutoResolver; +import com.sap.sailing.domain.tracking.Wind; + +/** + * Extension to the {@link ReadonlyRaceState} allowing write-access to the state of a race. + * + * All methods changing the state of the race (e.g. {@link RaceState#forceNewStartTime(TimePoint,TimePoint)}) map to a + * single add on the underlying {@link RaceLog}. You are in charge of advancing the pass identifier ( + * {@link RaceState#setAdvancePass(TimePoint)} ) at appropriate times. + * + * Additionally this ensures that there is no difference in changing the state via this interface and by + * {@link RaceLogEvent}s added to the {@link RaceLog} by any other component. The {@link RaceState} will react the same + * way. + */ +public interface RaceState extends ReadonlyRaceState { + + /** + * Gets the {@link RaceLogEventAuthor} that is used for all created {@link RaceLogEvent}s. + */ + RaceLogEventAuthor getAuthor(); + + /** + * Gets the currently attached {@link RacingProcedure}. + */ + RacingProcedure getRacingProcedure(); + + /** + * Gets the currently attached {@link RacingProcedure}. Use this method to avoid casting in cases you are sure about + * the {@link RacingProcedure}'s type. + */ + T getTypedRacingProcedure(); + + /** + * Gets the currently attached {@link RacingProcedure}. Use this method to avoid casting in cases you are sure about + * the {@link RacingProcedure}'s type. + */ + T getTypedRacingProcedure(Class clazz); + + /** + * Sets the {@link RacingProcedure}. This will replace the current {@link RacingProcedure}. + * + * @param now + * logical {@link TimePoint} the creation should be attached to. + * @param racingProcedureType + * new {@link RacingProcedure} to be created- + */ + void setRacingProcedure(TimePoint now, RacingProcedureType racingProcedureType); + + /** + * Advances the pass of the underlying {@link RaceLog}. + */ + void setAdvancePass(TimePoint now); + + /** + * Forces a new start time without checking for any prerequisites to be fulfilled. Although there is no technical + * reason for the prerequisites to be fulfilled before setting a start time event, there might be clients of this + * {@link RaceState} that expect them to be. Therefore use this method with caution. + * + * @param now + * logical {@link TimePoint} the start time event should be attached to. + */ + void forceNewStartTime(TimePoint now, TimePoint startTime); + + /** + * Starts a request to set a new start time. Before the start time is set the + * {@link RacingProcedurePrerequisite.Resolver} must fulfill all prerequisites set by the currently active + * {@link RacingProcedure}. + * + * @param now + * logical {@link TimePoint} the start time event (and all events created due to fulfilled prerequisites) + * should be attached to. + * @param startTime + * start time to be set + * @param resolver + * Object used to fulfill all {@link RacingProcedurePrerequisite}s. If you just want to use reasonable + * defaults pass a {@link RacingProcedurePrerequisiteAutoResolver}. + */ + void requestNewStartTime(TimePoint now, TimePoint startTime, RacingProcedurePrerequisite.Resolver resolver); + + /** + * Sets the finishing time. + */ + void setFinishingTime(TimePoint now); + + /** + * Sets the finished time. + */ + void setFinishedTime(TimePoint now); + + /** + * Sets the protest (start) time. + */ + void setProtestTime(TimePoint now, TimePoint protestStartTime); + + /** + * Signals the abort of this race. + * + * @param isPostponed + * indicates whether the abort is caused by a postponment of the race (rather than a full abort) + * @param reasonFlag + * instance of {@link Flags} explaining the abort. + */ + void setAborted(TimePoint now, boolean isPostponed, Flags reasonFlag); + + /** + * Signals general recall. + */ + void setGeneralRecall(TimePoint now); + + /** + * Sets a new unconfirmed finishing list. + */ + void setFinishPositioningListChanged(TimePoint now, CompetitorResults positionedCompetitors); + + /** + * Sets a new confirmed finishing list. + */ + void setFinishPositioningConfirmed(TimePoint now); + + /** + * Sets a new active course design. + */ + void setCourseDesign(TimePoint now, CourseBase courseDesign); + + /** + * Enters a new wind fix for this race. + */ + void setWindFix(TimePoint now, Wind wind); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateChangedListener.java new file mode 100644 index 00000000000..08844e62dcd --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateChangedListener.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.racelog.state; + + +public interface RaceStateChangedListener { + + void onRacingProcedureChanged(ReadonlyRaceState state); + void onStatusChanged(ReadonlyRaceState state); + void onStartTimeChanged(ReadonlyRaceState state); + void onFinishingTimeChanged(ReadonlyRaceState state); + void onFinishedTimeChanged(ReadonlyRaceState state); + void onProtestTimeChanged(ReadonlyRaceState state); + void onAdvancePass(ReadonlyRaceState state); + /* + * TODO: We need to recheck the AbortFinder to enable these interface methods + * void onAborted(RaceState2 state); + * void onGeneralRecall(RaceState2 state);*/ + void onFinishingPositioningsChanged(ReadonlyRaceState state); + void onFinishingPositionsConfirmed(ReadonlyRaceState state); + void onCourseDesignChanged(ReadonlyRaceState state); + void onWindFixChanged(ReadonlyRaceState state); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEvent.java new file mode 100644 index 00000000000..c7bb6428c1b --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEvent.java @@ -0,0 +1,12 @@ +package com.sap.sailing.domain.racelog.state; + +import java.io.Serializable; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; + +public interface RaceStateEvent extends Timed, Serializable { + + RaceStateEvents getEventName(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventProcessor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventProcessor.java new file mode 100644 index 00000000000..ef2b597b6f6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventProcessor.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.state; + +public interface RaceStateEventProcessor { + + boolean processStateEvent(RaceStateEvent event); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventScheduler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventScheduler.java new file mode 100644 index 00000000000..1f6a3c0d27a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/RaceStateEventScheduler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.domain.racelog.state; + +import java.util.Collection; + +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; + +public interface RaceStateEventScheduler { + + void scheduleStateEvents(Collection stateEvents); + + void unscheduleStateEvent(RaceStateEvents stateEventName); + + void unscheduleAllEvents(); + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/ReadonlyRaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/ReadonlyRaceState.java new file mode 100644 index 00000000000..3a4909ce656 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/ReadonlyRaceState.java @@ -0,0 +1,133 @@ +package com.sap.sailing.domain.racelog.state; + +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator; +import com.sap.sailing.domain.racelog.impl.RaceLogImpl; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; +import com.sap.sailing.domain.tracking.Wind; + +/** + * This interface gives you reading access to the state of a race. You can query various properties of the race + * including but not limited to its start time, finished time and its current course design. + * + * There is always a {@link ReadonlyRacingProcedure} attached. This object decides on various aspects of the race, + * including the starting sequence. + * + * Types implementing {@link ReadonlyRaceState} will monitor the underlying {@link RaceLog} all the time and will change + * according to the contents of it. Due to the use of {@link RaceLogAnalyzer}s for this, the current state is derived + * from the current valid {@link RaceLogEvent}s defined by the validity implementation of {@link RaceLogImpl} and the + * ordering of the {@link RaceLogEventComparator}. For example for a {@link RaceLog} containing a high-priority + * {@link RaceLogStartTimeEvent} the {@link ReadonlyRaceState} won't trigger or signal a change when a low-priority + * start time is added later on. + * + * When you want to use this interface for a short period of time, just use the getters to query the state of the race's + * properties. On the other it is good practice to let the interface do the work by registering a + * {@link RaceStateChangedListener} via {@link ReadonlyRaceState#addChangedListener(RaceStateChangedListener)}. Your + * listener will be called whenever any of the race's property changes. + * + * When doing so it is a good idea to set a {@link RaceStateEventScheduler} via + * {@link ReadonlyRaceState#setStateEventScheduler(RaceStateEventScheduler)}. This way the {@link ReadonlyRaceState} is + * able to inform you about automatic timer-driven changes like a change in the displayed flags. Having a + * {@link RaceStateEventScheduler} is not mandatory for the state to function properly. + * + */ +public interface ReadonlyRaceState extends RaceStateEventProcessor { + + /** + * Accesses the underlying {@link RaceLog}. + */ + RaceLog getRaceLog(); + + /** + * Gets the currently attached {@link ReadonlyRacingProcedure}. + */ + ReadonlyRacingProcedure getRacingProcedure(); + + /** + * Gets the currently attached {@link ReadonlyRacingProcedure}. Use this method to avoid casting in cases you are + * sure about the {@link ReadonlyRacingProcedure}'s type. + */ + T getTypedReadonlyRacingProcedure(); + + /** + * Gets the currently attached {@link ReadonlyRacingProcedure}. Use this method to avoid casting in cases you are + * sure about the {@link ReadonlyRacingProcedure}'s type. + */ + T getTypedReadonlyRacingProcedure(Class clazz); + + /** + * Sets the {@link RaceStateEventScheduler} which drives automatic events triggering calls to the registered + * {@link RaceStateEventScheduler}s. + */ + void setStateEventScheduler(RaceStateEventScheduler scheduler); + + /** + * Gets the {@link RegattaConfiguration} that is used for this race. + */ + RegattaConfiguration getConfiguration(); + + /** + * Adds a {@link RaceStateChangedListener}. + */ + void addChangedListener(RaceStateChangedListener listener); + + /** + * Removes the {@link RaceStateChangedListener}. + */ + void removeChangedListener(RaceStateChangedListener listener); + + /** + * Gets the races overall status. + */ + RaceLogRaceStatus getStatus(); + + /** + * If there is a start time set for the current pass, returns it. Otherwise null. + */ + TimePoint getStartTime(); + + /** + * If there is a finishing time set for the current pass, returns it. Otherwise null. + */ + TimePoint getFinishingTime(); + + /** + * If there is a finished time set for the current pass, returns it. Otherwise null. + */ + TimePoint getFinishedTime(); + + /** + * If there is a (unconfirmed) finish positioning list set for the current pass, returns the most recent one. + * Otherwise null. + */ + CompetitorResults getFinishPositioningList(); + + /** + * If there is a (confirmed) finish positioning list set for the current pass, returns the most recent one. + * Otherwise null. + */ + CompetitorResults getConfirmedFinishPositioningList(); + + /** + * If there is a protest time set, returns the most recent one. Otherwise null. + */ + TimePoint getProtestTime(); + + /** + * If there is a course set, returns the most recent one. Otherwise null. + */ + CourseBase getCourseDesign(); + + /** + * If wind has been entered, returns the most recent one. Otherwise null. + */ + Wind getWindFix(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/BaseRaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/BaseRaceStateChangedListener.java new file mode 100644 index 00000000000..ada19547768 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/BaseRaceStateChangedListener.java @@ -0,0 +1,66 @@ +package com.sap.sailing.domain.racelog.state.impl; + +import com.sap.sailing.domain.racelog.state.RaceStateChangedListener; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; + +/** + * Providing an empty implementation of {@link RaceStateChangedListener}. + */ +public abstract class BaseRaceStateChangedListener implements RaceStateChangedListener { + + @Override + public void onRacingProcedureChanged(ReadonlyRaceState state) { + + } + + @Override + public void onStatusChanged(ReadonlyRaceState state) { + + } + + @Override + public void onStartTimeChanged(ReadonlyRaceState state) { + + } + + @Override + public void onFinishingTimeChanged(ReadonlyRaceState state) { + + } + + @Override + public void onFinishedTimeChanged(ReadonlyRaceState state) { + + } + + @Override + public void onProtestTimeChanged(ReadonlyRaceState state) { + + } + + @Override + public void onAdvancePass(ReadonlyRaceState state) { + + } + + @Override + public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + + } + + @Override + public void onFinishingPositionsConfirmed(ReadonlyRaceState state) { + + } + + @Override + public void onCourseDesignChanged(ReadonlyRaceState state) { + + } + + @Override + public void onWindFixChanged(ReadonlyRaceState state) { + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateChangedListeners.java new file mode 100644 index 00000000000..8318bc33a33 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateChangedListeners.java @@ -0,0 +1,90 @@ +package com.sap.sailing.domain.racelog.state.impl; + +import java.util.HashSet; + +import com.sap.sailing.domain.racelog.state.RaceStateChangedListener; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; + +public class RaceStateChangedListeners extends HashSet implements RaceStateChangedListener { + + private static final long serialVersionUID = 2028183211903975659L; + + @Override + public void onRacingProcedureChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onRacingProcedureChanged(state); + } + } + + @Override + public void onStatusChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onStatusChanged(state); + } + } + + @Override + public void onStartTimeChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onStartTimeChanged(state); + } + } + + @Override + public void onFinishingTimeChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onFinishingTimeChanged(state); + } + } + + @Override + public void onFinishedTimeChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onFinishedTimeChanged(state); + } + } + + @Override + public void onProtestTimeChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onProtestTimeChanged(state); + } + } + + @Override + public void onAdvancePass(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onAdvancePass(state); + } + } + + @Override + public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onFinishingPositioningsChanged(state); + } + } + + @Override + public void onFinishingPositionsConfirmed(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onFinishingPositionsConfirmed(state); + } + } + + @Override + public void onCourseDesignChanged(ReadonlyRaceState state) { + for (RaceStateChangedListener listener : this) { + listener.onCourseDesignChanged(state); + } + } + + @Override + public void onWindFixChanged(ReadonlyRaceState state) { + // TODO Auto-generated method stub + + } + + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEventImpl.java new file mode 100644 index 00000000000..ab223e983bd --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEventImpl.java @@ -0,0 +1,33 @@ +package com.sap.sailing.domain.racelog.state.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; + +public class RaceStateEventImpl implements RaceStateEvent { + + private static final long serialVersionUID = -5523748426501793355L; + + private final TimePoint timePoint; + private final RaceStateEvents eventName; + + public RaceStateEventImpl(TimePoint timePoint, RaceStateEvents eventName) { + this.timePoint = timePoint; + this.eventName = eventName; + } + + @Override + public TimePoint getTimePoint() { + return timePoint; + } + + @Override + public RaceStateEvents getEventName() { + return eventName; + } + + @Override + public String toString() { + return "RaceStateEventImpl [timePoint=" + timePoint + ", eventName=" + eventName + "]"; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEvents.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEvents.java new file mode 100644 index 00000000000..85f8d2589e5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateEvents.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.racelog.state.impl; + +public enum RaceStateEvents { + UNKNOWN, + START, + INDIVIDUAL_RECALL_TIMEOUT, + + RRS26_CLASS_UP, + RRS26_STARTMODE_UP, + RRS26_STARTMODE_DOWN, + + ESS_AP_DOWN, + ESS_THREE_UP, + ESS_TWO_UP, + ESS_ONE_UP, + + GATE_CLASS_OVER_GOLF_UP, + GATE_PAPA_UP, + GATE_PAPA_DOWN, + GATE_GOLF_DOWM, + GATE_SHUTDOWN +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java new file mode 100644 index 00000000000..f1158dbda02 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/RaceStateImpl.java @@ -0,0 +1,161 @@ +package com.sap.sailing.domain.racelog.state.impl; + +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; +import com.sap.sailing.domain.racelog.state.RaceState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureFactoryImpl; +import com.sap.sailing.domain.tracking.Wind; + +/** + * Write-enabled {@link RaceState}. + */ +public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { + + private final RaceLogEventAuthor author; + private final RaceLogEventFactory factory; + + /** + * Creates a {@link RaceState} with the initial racing procedure type set to a fallback value and an empty configuration. + */ + public static RaceState create(RaceLog raceLog, RaceLogEventAuthor author) { + return create(raceLog, author, new EmptyRegattaConfiguration()); + } + + /** + * Creates a {@link RaceState}. + */ + public static RaceState create(RaceLog raceLog, RaceLogEventAuthor author, ConfigurationLoader configuration) { + return new RaceStateImpl(raceLog, author, + RaceLogEventFactory.INSTANCE, + new RacingProcedureFactoryImpl(author, RaceLogEventFactory.INSTANCE, configuration)); + } + + public RaceStateImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory eventFactory, + RacingProcedureFactory procedureFactory) { + this(raceLog, author, eventFactory, new RaceStatusAnalyzer.StandardClock(), procedureFactory); + } + + private RaceStateImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory eventFactory, RaceStatusAnalyzer.Clock analyzersClock, + RacingProcedureFactory procedureFactory) { + super(raceLog, analyzersClock, procedureFactory); + this.author = author; + this.factory = eventFactory; + } + + @Override + public RacingProcedure getRacingProcedure() { + return (RacingProcedure) super.getRacingProcedure(); + } + + @SuppressWarnings("unchecked") + @Override + public T getTypedRacingProcedure() { + return (T) getRacingProcedure(); + } + + @SuppressWarnings("unchecked") + @Override + public T getTypedRacingProcedure(Class clazz) { + RacingProcedure procedure = getRacingProcedure(); + if (clazz.isAssignableFrom(procedure.getClass())) { + return (T) procedure; + } else { + return null; + } + } + + @Override + public RaceLogEventAuthor getAuthor() { + return author; + } + + @Override + public void setRacingProcedure(TimePoint timePoint, RacingProcedureType newType) { + raceLog.add(factory.createStartProcedureChangedEvent(timePoint, author, raceLog.getCurrentPassId(), newType)); + } + + @Override + public void requestNewStartTime(final TimePoint now, final TimePoint startTime, RacingProcedurePrerequisite.Resolver resolver) { + RacingProcedurePrerequisite.FulfillmentFunction function = new RacingProcedurePrerequisite.FulfillmentFunction() { + @Override + public void execute() { + raceLog.add(factory.createStartTimeEvent(now, author, raceLog.getCurrentPassId(), startTime)); + } + }; + + getRacingProcedure().checkPrerequisitesForStart(now, startTime, function).resolve(resolver); + } + + @Override + public void forceNewStartTime(TimePoint now, TimePoint startTime) { + raceLog.add(factory.createStartTimeEvent(now, author, raceLog.getCurrentPassId(), startTime)); + } + + @Override + public void setFinishingTime(TimePoint timePoint) { + raceLog.add(factory.createRaceStatusEvent(timePoint, author, raceLog.getCurrentPassId(), RaceLogRaceStatus.FINISHING)); + } + + @Override + public void setFinishedTime(TimePoint timePoint) { + raceLog.add(factory.createRaceStatusEvent(timePoint, author, raceLog.getCurrentPassId(), RaceLogRaceStatus.FINISHED)); + } + + @Override + public void setProtestTime(TimePoint now, TimePoint timePoint) { + raceLog.add(factory.createProtestStartTimeEvent(now, author, raceLog.getCurrentPassId(), timePoint)); + } + + @Override + public void setAdvancePass(TimePoint timePoint) { + raceLog.add(factory.createPassChangeEvent(timePoint, author, raceLog.getCurrentPassId() + 1)); + } + + @Override + public void setAborted(TimePoint timePoint, boolean isPostponed, Flags reasonFlag) { + Flags markerFlag = isPostponed ? Flags.AP : Flags.NOVEMBER; + raceLog.add(factory.createFlagEvent(timePoint, author, raceLog.getCurrentPassId(), markerFlag, reasonFlag, true)); + } + + @Override + public void setGeneralRecall(TimePoint timePoint) { + raceLog.add(factory.createFlagEvent(timePoint, author, raceLog.getCurrentPassId(), Flags.FIRSTSUBSTITUTE, Flags.NONE, true)); + } + + @Override + public void setFinishPositioningListChanged(TimePoint timePoint, CompetitorResults positionedCompetitors) { + raceLog.add(factory.createFinishPositioningListChangedEvent( + timePoint, author, raceLog.getCurrentPassId(), positionedCompetitors)); + } + + @Override + public void setFinishPositioningConfirmed(TimePoint timePoint) { + raceLog.add(factory.createFinishPositioningConfirmedEvent( + timePoint, author, raceLog.getCurrentPassId(), getFinishPositioningList())); + } + + @Override + public void setCourseDesign(TimePoint timePoint, CourseBase courseDesign) { + raceLog.add(factory.createCourseDesignChangedEvent(timePoint, author, raceLog.getCurrentPassId(), courseDesign)); + } + + @Override + public void setWindFix(TimePoint timePoint, Wind wind) { + raceLog.add(factory.createWindFixEvent(timePoint, author, raceLog.getCurrentPassId(), wind)); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java new file mode 100644 index 00000000000..7d9e70c1ef9 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/impl/ReadonlyRaceStateImpl.java @@ -0,0 +1,352 @@ +package com.sap.sailing.domain.racelog.state.impl; + +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.impl.EmptyRegattaConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.CompetitorResults; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogChangedListener; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.analyzing.impl.ConfirmedFinishPositioningListFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.FinishPositioningListFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.FinishedTimeFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.FinishingTimeFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.LastWindFixFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.ProtestStartTimeFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer.Clock; +import com.sap.sailing.domain.racelog.analyzing.impl.RacingProcedureTypeAnalyzer; +import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; +import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor; +import com.sap.sailing.domain.racelog.state.RaceStateChangedListener; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.RaceStateEventScheduler; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.ReadonlyRacingProcedureFactory; +import com.sap.sailing.domain.tracking.Wind; + +/** + * Implementation of {@link ReadonlyRaceState}. Use the static factory methods to instantiate your race state. + * + * The {@link ReadonlyRaceStateImpl} ensures that it will not change the {@link RaceLog} on its own by wrapping the + * {@link RaceLog}. This way even automatic events triggered by the attached {@link RacingProcedure}s will never add any + * events to the {@link RaceLog}. + * + * See {@link ReadonlyRaceStateImpl#update()} for the core of the algorithm. + */ +public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedListener { + + /** + * Creates a {@link ReadonlyRaceState}. + */ + public static ReadonlyRaceState create(RaceLog raceLog, + ConfigurationLoader configurationLoader) { + return new ReadonlyRaceStateImpl(raceLog, new ReadonlyRacingProcedureFactory(configurationLoader)); + } + + /** + * Creates a {@link ReadonlyRaceState} with an empty configuration ( {@link EmptyRegattaConfiguration} ). + */ + public static ReadonlyRaceState create(RaceLog raceLog) { + return new ReadonlyRaceStateImpl(raceLog, new ReadonlyRacingProcedureFactory(new EmptyRegattaConfiguration())); + } + + /** + * When trying to initialize a {@link RaceStateImpl} with an initial {@link RacingProcedureType} of + * {@link RacingProcedureType#UNKNOWN} the value of this field will be used instead. + */ + public final static RacingProcedureType fallbackInitialProcedureType = RacingProcedureType.RRS26; + + protected final RaceLog raceLog; + + private final Clock statusAnalyzerClock; + private final RacingProcedureFactory procedureFactory; + private final RaceStateChangedListeners changedListeners; + + private ReadonlyRacingProcedure racingProcedure; + private RaceStateEventScheduler scheduler; + + /** + * We need one for each change in {@link RacingProcedureType}. + */ + private RaceStatusAnalyzer statusAnalyzer; + private final RacingProcedureTypeAnalyzer racingProcedureAnalyzer; + + private final StartTimeFinder startTimeAnalyzer; + private final FinishingTimeFinder finishingTimeAnalyzer; + private final FinishedTimeFinder finishedTimeAnalyzer; + private final ProtestStartTimeFinder protestTimeAnalyzer; + + private final FinishPositioningListFinder finishPositioningListAnalyzer; + private final ConfirmedFinishPositioningListFinder confirmedFinishPositioningListAnalyzer; + + private final LastPublishedCourseDesignFinder courseDesignerAnalyzer; + private final LastWindFixFinder lastWindFixAnalyzer; + + private RacingProcedureType cachedRacingProcedureType; + private RaceLogRaceStatus cachedRaceStatus; + private int cachedPassId; + private TimePoint cachedStartTime; + private TimePoint cachedFinishingTime; + private TimePoint cachedFinishedTime; + private TimePoint cachedProtestTime; + private CompetitorResults cachedPositionedCompetitors; + private CompetitorResults cachedConfirmedPositionedCompetitors; + private CourseBase cachedCourseDesign; + private Wind cachedWindFix; + + private ReadonlyRaceStateImpl(RaceLog raceLog, RacingProcedureFactory procedureFactory) { + this(raceLog, new RaceStatusAnalyzer.StandardClock(), procedureFactory); + } + + protected ReadonlyRaceStateImpl(RaceLog raceLog, Clock analyzersClock, RacingProcedureFactory procedureFactory) { + this.raceLog = raceLog; + this.procedureFactory = procedureFactory; + this.changedListeners = new RaceStateChangedListeners(); + + this.racingProcedureAnalyzer = new RacingProcedureTypeAnalyzer(raceLog); + this.statusAnalyzerClock = analyzersClock; + // status analyzer will get initialized when racing procedure is ready + this.startTimeAnalyzer = new StartTimeFinder(raceLog); + this.finishingTimeAnalyzer = new FinishingTimeFinder(raceLog); + this.finishedTimeAnalyzer = new FinishedTimeFinder(raceLog); + this.protestTimeAnalyzer = new ProtestStartTimeFinder(raceLog); + this.finishPositioningListAnalyzer = new FinishPositioningListFinder(raceLog); + this.confirmedFinishPositioningListAnalyzer = new ConfirmedFinishPositioningListFinder(raceLog); + this.courseDesignerAnalyzer = new LastPublishedCourseDesignFinder(raceLog); + this.lastWindFixAnalyzer = new LastWindFixFinder(raceLog); + + this.cachedRacingProcedureType = determineInitialProcedureType(); + this.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN; + this.cachedPassId = raceLog.getCurrentPassId(); + + this.raceLog.addListener(new RaceLogChangedVisitor(this)); + + // We known that recreateRacingProcedure calls update() when done, therefore this RaceState + // will be fully initialized after this line + recreateRacingProcedure(); + } + + protected RacingProcedureType determineInitialProcedureType() { + // Let's ensure there is a valid RacingProcedureType set, since a RaceState cannot live without a + // RacingProcedure we need to have a fallback + RegattaConfiguration configuration = getConfiguration(); + RacingProcedureType inRaceLogType = racingProcedureAnalyzer.analyze(); + if (inRaceLogType != RacingProcedureType.UNKNOWN) { + return inRaceLogType; + } else if (configuration.getDefaultRacingProcedureType() != null + && configuration.getDefaultRacingProcedureType() != RacingProcedureType.UNKNOWN) { + return configuration.getDefaultRacingProcedureType(); + } else { + return fallbackInitialProcedureType; + } + } + + @Override + public RaceLog getRaceLog() { + return raceLog; + } + + @Override + public ReadonlyRacingProcedure getRacingProcedure() { + return racingProcedure; + } + + @SuppressWarnings("unchecked") + @Override + public T getTypedReadonlyRacingProcedure() { + return (T) getRacingProcedure(); + } + + @SuppressWarnings("unchecked") + @Override + public T getTypedReadonlyRacingProcedure(Class clazz) { + ReadonlyRacingProcedure procedure = getRacingProcedure(); + if (clazz.isAssignableFrom(procedure.getClass())) { + return (T) procedure; + } else { + return null; + } + } + + @Override + public void setStateEventScheduler(RaceStateEventScheduler scheduler) { + this.scheduler = scheduler; + if (racingProcedure != null) { + racingProcedure.setStateEventScheduler(scheduler); + racingProcedure.triggerStateEventScheduling(this); + } + } + + @Override + public RaceLogRaceStatus getStatus() { + return cachedRaceStatus; + } + + @Override + public TimePoint getStartTime() { + return cachedStartTime; + } + + @Override + public TimePoint getFinishingTime() { + return cachedFinishingTime; + } + + @Override + public TimePoint getFinishedTime() { + return cachedFinishedTime; + } + + @Override + public TimePoint getProtestTime() { + return cachedProtestTime; + } + + @Override + public CompetitorResults getFinishPositioningList() { + return cachedPositionedCompetitors; + } + + @Override + public CompetitorResults getConfirmedFinishPositioningList() { + return cachedConfirmedPositionedCompetitors; + } + + @Override + public CourseBase getCourseDesign() { + return cachedCourseDesign; + } + + @Override + public Wind getWindFix() { + return cachedWindFix; + } + + @Override + public RegattaConfiguration getConfiguration() { + return procedureFactory.getConfiguration(); + } + + @Override + public void addChangedListener(RaceStateChangedListener listener) { + changedListeners.add(listener); + } + + @Override + public void removeChangedListener(RaceStateChangedListener listener) { + changedListeners.remove(listener); + } + + @Override + public void eventAdded(RaceLogEvent event) { + update(); + } + + @Override + public boolean processStateEvent(RaceStateEvent event) { + // Always ask the procedure (might be interested in START too)! + if (racingProcedure.processStateEvent(event) || event.getEventName() == RaceStateEvents.START) { + update(); + return true; + } + return false; + } + + private void update() { + RacingProcedureType type = racingProcedureAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedRacingProcedureType, type) && type != RacingProcedureType.UNKNOWN) { + cachedRacingProcedureType = type; + recreateRacingProcedure(); + changedListeners.onRacingProcedureChanged(this); + } + + RaceLogRaceStatus status = statusAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedRaceStatus, status)) { + cachedRaceStatus = status; + changedListeners.onStatusChanged(this); + } + + int passId = raceLog.getCurrentPassId(); + if (cachedPassId != passId) { + cachedPassId = passId; + changedListeners.onAdvancePass(this); + // reset racing procedure to force recreate on next event! + cachedRacingProcedureType = null; + } + + TimePoint startTime = startTimeAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedStartTime, startTime)) { + cachedStartTime = startTime; + changedListeners.onStartTimeChanged(this); + } + + TimePoint finishingTime = finishingTimeAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedFinishingTime, finishingTime)) { + cachedFinishingTime = finishingTime; + changedListeners.onFinishingTimeChanged(this); + } + + TimePoint finishedTime = finishedTimeAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedFinishedTime, finishedTime)) { + cachedFinishedTime = finishedTime; + changedListeners.onFinishedTimeChanged(this); + } + + TimePoint protestTime = protestTimeAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedProtestTime, protestTime)) { + cachedProtestTime = protestTime; + changedListeners.onProtestTimeChanged(this); + } + + CompetitorResults positionedCompetitors = finishPositioningListAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedPositionedCompetitors, positionedCompetitors)) { + cachedPositionedCompetitors = positionedCompetitors; + changedListeners.onFinishingPositioningsChanged(this); + } + + CompetitorResults confirmedPositionedCompetitors = confirmedFinishPositioningListAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedConfirmedPositionedCompetitors, confirmedPositionedCompetitors)) { + cachedConfirmedPositionedCompetitors = confirmedPositionedCompetitors; + if (cachedConfirmedPositionedCompetitors != null) { + changedListeners.onFinishingPositionsConfirmed(this); + } + } + + CourseBase courseDesign = courseDesignerAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedCourseDesign, courseDesign)) { + cachedCourseDesign = courseDesign; + changedListeners.onCourseDesignChanged(this); + } + + Wind windFix = lastWindFixAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedWindFix, windFix)) { + cachedWindFix = windFix; + changedListeners.onWindFixChanged(this); + } + } + + private void recreateRacingProcedure() { + if (racingProcedure != null) { + removeChangedListener(racingProcedure); + racingProcedure.detach(); + } + racingProcedure = procedureFactory.createRacingProcedure(cachedRacingProcedureType, raceLog); + racingProcedure.setStateEventScheduler(scheduler); + addChangedListener(racingProcedure); + + statusAnalyzer = new RaceStatusAnalyzer(raceLog, statusAnalyzerClock, racingProcedure); + // let's do an update because status might have changed with new procedure + update(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/FlagPoleState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/FlagPoleState.java new file mode 100644 index 00000000000..5e5ad57c3ef --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/FlagPoleState.java @@ -0,0 +1,103 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.FlagPole; + +/** + * Allows you to query the current and state of displayed and remove flags. + */ +public class FlagPoleState { + + private final List currentState; + private final List nextState; + private final TimePoint nextStateValidFrom; + private final boolean hasNextState; + + public FlagPoleState(List currentState) { + this(currentState, null, null, false); + } + + public FlagPoleState(List currentState, List nextState, TimePoint nextStateValidFrom) { + this(currentState, nextState, nextStateValidFrom, true); + } + + private FlagPoleState(List currentState, List nextState, TimePoint nextStateValidFrom, boolean hasNextState) { + this.currentState = currentState; + this.nextState = nextState; + this.nextStateValidFrom = nextStateValidFrom; + this.hasNextState = hasNextState; + } + + public List getCurrentState() { + return currentState; + } + public List getNextState() { + return nextState; + } + + public TimePoint getNextStateValidFrom() { + return nextStateValidFrom; + } + + public boolean hasNextState() { + return hasNextState; + } + + public List computeUpcomingChanges() { + if (!hasNextState) { + return Collections.emptyList(); + } + + List changes = new ArrayList(); + for (FlagPole nextPole : nextState) { + boolean hasSamePoleInCurrentState = false; + for (FlagPole currentPole : currentState) { + if (nextPole.describesSame(currentPole)) { + hasSamePoleInCurrentState = true; + break; + } + } + if (!hasSamePoleInCurrentState) { + changes.add(nextPole); + } + } + return changes; + } + + public static boolean describesSameState(FlagPoleState left, FlagPoleState right) { + if (left.hasNextState() != right.hasNextState()) { + return false; + } + + if (left.getCurrentState().size() != right.getCurrentState().size()) { + return false; + } + + if (describesSamePoles(left.getCurrentState(), right.getCurrentState())) { + if (left.hasNextState()) { + + return left.getNextStateValidFrom().compareTo(right.getNextStateValidFrom()) == 0 && + describesSamePoles(left.getNextState(), right.getNextState()); + } + return true; + } + return false; + } + + private static boolean describesSamePoles(List leftPoles, List rightPoles) { + for (int i = 0; i < leftPoles.size(); i++) { + FlagPole leftPole = leftPoles.get(i); + FlagPole rightPole = rightPoles.get(i); + if (!leftPole.describesSame(rightPole)) { + return false; + } + } + return true; + } + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedure.java new file mode 100644 index 00000000000..5345ca04cd8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedure.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +import com.sap.sailing.domain.common.TimePoint; + +/** + * Base interface for all write-enabled {@link RacingProcedure}s. + */ +public interface RacingProcedure extends ReadonlyRacingProcedure { + + /** + * Displays individual recall. + */ + void displayIndividualRecall(TimePoint now); + + /** + * Removes the individual recall flag. + */ + void removeIndividualRecall(TimePoint now); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureChangedListener.java new file mode 100644 index 00000000000..d7d3eacca9a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureChangedListener.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +public interface RacingProcedureChangedListener { + + void onActiveFlagsChanged(ReadonlyRacingProcedure racingProcedure); + void onIndividualRecallDisplayed(ReadonlyRacingProcedure racingProcedure); + void onIndividualRecallRemoved(ReadonlyRacingProcedure racingProcedure); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureFactory.java new file mode 100644 index 00000000000..cd07e6e2128 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedureFactory.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; + +public interface RacingProcedureFactory { + + ReadonlyRacingProcedure createRacingProcedure(RacingProcedureType type, RaceLog raceLog); + + RegattaConfiguration getConfiguration(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedurePrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedurePrerequisite.java new file mode 100644 index 00000000000..475cc4148b6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/RacingProcedurePrerequisite.java @@ -0,0 +1,47 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl.GateLaunchTimePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl.PathfinderPrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl.StartmodePrerequisite; + +/** + * Something that has to be fulfilled before the race can start (e.g. decide which start mode flag is shown). + */ +public interface RacingProcedurePrerequisite { + + /** + * In charge of fulfilling all {@link RacingProcedurePrerequisite}. Implementations must call the + * {@link RacingProcedurePrerequisite} "fulfill" (or {@link RacingProcedurePrerequisite#fulfillWithDefault()} + * methods to let the framework decide whether there are more prerequisites or the race can start. + */ + public interface Resolver { + /** + * Called when all {@link RacingProcedurePrerequisite} are fulfilled. + */ + void onFulfilled(); + + void fulfill(PathfinderPrerequisite prerequisite); + + void fulfill(GateLaunchTimePrerequisite prerequisite); + + void fulfill(StartmodePrerequisite prerequisite); + } + + /** + * Interface used by the framework for executing the setting of a new start time after all prerequisites are + * fulfilled. + */ + public interface FulfillmentFunction { + void execute(); + } + + /** + * Resolve this {@link RacingProcedurePrerequisite} with the help of a passed {@link Resolver}. + */ + void resolve(Resolver resolver); + + /** + * Fulfill this {@link RacingProcedurePrerequisite} with reasonable defaults. + */ + void fulfillWithDefault(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ReadonlyRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ReadonlyRacingProcedure.java new file mode 100644 index 00000000000..ffc4e71bbc3 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ReadonlyRacingProcedure.java @@ -0,0 +1,108 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer; +import com.sap.sailing.domain.racelog.state.RaceState; +import com.sap.sailing.domain.racelog.state.RaceStateChangedListener; +import com.sap.sailing.domain.racelog.state.RaceStateEventProcessor; +import com.sap.sailing.domain.racelog.state.RaceStateEventScheduler; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; + +/** + * Read-only interface for every racing procedure. A racing procedure defines the starting sequence of a race various + * other properties regarding how a race is organized. + * + * A {@link ReadonlyRaceState} is always attached to its {@link RaceState}. + * + * The state of a {@link ReadonlyRacingProcedure} is driven by the {@link RaceLog} and {@link RaceLogAnalyzer}, see + * {@link ReadonlyRaceState} for a more in-detail description. + * + * See {@link ReadonlyRaceState} for an explanation on + * {@link ReadonlyRacingProcedure#setStateEventScheduler(RaceStateEventScheduler)}. + * + */ +public interface ReadonlyRacingProcedure extends RaceStateEventProcessor, RaceStateChangedListener { + + /** + * Accesses the underlying {@link RaceLog}. + */ + RaceLog getRaceLog(); + + /** + * Gets the {@link RacingProcedureType}. + */ + RacingProcedureType getType(); + + /** + * Gets the {@link RacingProcedure}'s configuration. + */ + RacingProcedureConfiguration getConfiguration(); + + /** + * Adds a {@link RacingProcedureChangedListener}. + */ + void addChangedListener(RacingProcedureChangedListener listener); + + /** + * Removes a {@link RacingProcedureChangedListener}. + */ + void removeChangedListener(RacingProcedureChangedListener listener); + + /** + * Returns true if the start phase of this {@link ReadonlyRacingProcedure is active given the current time and start time. + * @param startTime start time to be used to determine whether the start phase is active or not. + * @param now {@link TimePoint} used as the current time on determining whether the start phase is active or not. + */ + boolean isStartphaseActive(TimePoint startTime, TimePoint now); + + /** + * Returns true if this {@link ReadonlyRacingProcedure} should allow signaling individual recalls. + */ + boolean hasIndividualRecall(); + + /** + * Returns true if there is an individual recall signaled in the {@link RaceLog} right now. + */ + boolean isIndividualRecallDisplayed(); + + /** + * Gets the time the individual recall flag was removed (or null). + */ + TimePoint getIndividualRecallRemovalTime(); + + /** + * Gets the {@link FlagPoleState} active for the given start and current time. + */ + FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now); + + /** + * Sets the {@link RaceStateEventScheduler} which drives automatic events triggering calls. + * + * This method is called by the framework. You don't need to call this method. + */ + void setStateEventScheduler(RaceStateEventScheduler scheduler); + + /** + * This method is used by the framework to ensure that all automatic events are triggered when a + * {@link ReadonlyRacingProcedure} is attached to its {@link RaceState}. You don't need to call this method. + */ + void triggerStateEventScheduling(ReadonlyRaceState state); + + /** + * Returns a {@link RacingProcedurePrerequisite} object that should be resolved by + * {@link RacingProcedurePrerequisite.Resolver} to ensure that everything is set up correctly before the start of + * the race. + * + * This method is called by the parent {@link RaceState}. You don't need to call this method. + */ + RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, + RacingProcedurePrerequisite.FulfillmentFunction function); + + /** + * This method is called by the framework. You don't need to call it. + */ + void detach(); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/BasicRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/BasicRacingProcedure.java new file mode 100644 index 00000000000..8ead06a1e04 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/BasicRacingProcedure.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.basic; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + +public interface BasicRacingProcedure extends RacingProcedure { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java new file mode 100644 index 00000000000..5844b57e048 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/basic/impl/BasicRacingProcedureImpl.java @@ -0,0 +1,65 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.basic.impl; + +import java.util.Collection; +import java.util.Collections; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite.FulfillmentFunction; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.NoMorePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; + +public class BasicRacingProcedureImpl extends BaseRacingProcedure { + + public BasicRacingProcedureImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory factory, + RacingProcedureConfiguration configuration) { + super(raceLog, author, factory, configuration); + } + + @Override + public RacingProcedureType getType() { + return RacingProcedureType.BASIC; + } + + @Override + public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, + FulfillmentFunction function) { + return new NoMorePrerequisite(function); + } + + @Override + public boolean isStartphaseActive(TimePoint startTime, TimePoint now) { + return now.before(startTime); + } + + @Override + public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) { + return new FlagPoleState(Collections.emptyList()); + } + + @Override + protected RacingProcedureChangedListeners createChangedListenerContainer() { + return new RacingProcedureChangedListeners(); + } + + @Override + protected boolean hasIndividualRecallByDefault() { + return false; + } + + @Override + protected Collection createStartStateEvents(TimePoint startTime) { + return Collections.emptyList(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSChangedListener.java new file mode 100644 index 00000000000..5f6eb820b81 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSChangedListener.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.ess; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; + +public interface ESSChangedListener extends RacingProcedureChangedListener { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSRacingProcedure.java new file mode 100644 index 00000000000..230405a5f00 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ESSRacingProcedure.java @@ -0,0 +1,7 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.ess; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + +public interface ESSRacingProcedure extends ReadonlyESSRacingProcedure, RacingProcedure { + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ReadonlyESSRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ReadonlyESSRacingProcedure.java new file mode 100644 index 00000000000..c93c0a09e4d --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/ReadonlyESSRacingProcedure.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.ess; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; + + +public interface ReadonlyESSRacingProcedure extends ReadonlyRacingProcedure { + + void addChangedListener(ESSChangedListener listener); + + TimePoint getTimeLimit(TimePoint startTime); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSChangedListeners.java new file mode 100644 index 00000000000..0a940a9fc81 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSChangedListeners.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.ess.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.ess.ESSChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; + +public class ESSChangedListeners extends RacingProcedureChangedListeners implements + ESSChangedListener { + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java new file mode 100644 index 00000000000..dea4ca396f6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/ess/impl/ESSRacingProcedureImpl.java @@ -0,0 +1,163 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.ess.impl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import com.sap.sailing.domain.base.configuration.procedures.ESSConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.analyzing.impl.FinishingTimeFinder; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEventImpl; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; +import com.sap.sailing.domain.racelog.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite.FulfillmentFunction; +import com.sap.sailing.domain.racelog.state.racingprocedure.ess.ESSChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.ess.ESSRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.NoMorePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; + +public class ESSRacingProcedureImpl extends BaseRacingProcedure implements ESSRacingProcedure { + + private final static long startPhaseAPDownInterval = 4 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhaseESSThreeUpInterval = 3 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhaseESSTwoUpInterval = 2 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhaseESSOneUpInterval = 1 * 60 * 1000; // minutes * seconds * milliseconds + + @SuppressWarnings("unused") + private final ESSConfiguration configuration; + + public ESSRacingProcedureImpl(RaceLog raceLog, RaceLogEventAuthor author, + RaceLogEventFactory factory, ESSConfiguration configuration) { + super(raceLog, author, factory, configuration); + this.configuration = configuration; + update(); + } + + @Override + public RacingProcedureType getType() { + return RacingProcedureType.ESS; + } + + @Override + public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, + FulfillmentFunction function) { + return new NoMorePrerequisite(function); + } + + @Override + public boolean isStartphaseActive(TimePoint startTime, TimePoint now) { + if (now.before(startTime)) { + long timeTillStart = startTime.minus(now.asMillis()).asMillis(); + return timeTillStart < startPhaseAPDownInterval; + } + return false; + } + + @Override + protected Collection createStartStateEvents(TimePoint startTime) { + return Arrays. asList( + new RaceStateEventImpl(startTime.minus(startPhaseAPDownInterval), RaceStateEvents.ESS_AP_DOWN), + new RaceStateEventImpl(startTime.minus(startPhaseESSThreeUpInterval), RaceStateEvents.ESS_THREE_UP), + new RaceStateEventImpl(startTime.minus(startPhaseESSTwoUpInterval), RaceStateEvents.ESS_TWO_UP), + new RaceStateEventImpl(startTime.minus(startPhaseESSOneUpInterval), RaceStateEvents.ESS_ONE_UP), + new RaceStateEventImpl(startTime, RaceStateEvents.START)); + } + + @Override + public boolean processStateEvent(RaceStateEvent event) { + switch (event.getEventName()) { + case ESS_AP_DOWN: + case ESS_THREE_UP: + case ESS_TWO_UP: + case ESS_ONE_UP: + getChangedListeners().onActiveFlagsChanged(this); + return true; + default: + return super.processStateEvent(event); + } + } + + @Override + public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) { + if (now.before(startTime.minus(startPhaseAPDownInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.AP, true), new FlagPole(Flags.ESSTHREE, false)), + Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)), + startTime.minus(startPhaseAPDownInterval)); + } else if (now.before(startTime.minus(startPhaseESSThreeUpInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.AP, false), new FlagPole(Flags.ESSTHREE, false)), + Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)), + startTime.minus(startPhaseESSThreeUpInterval)); + } else if (now.before(startTime.minus(startPhaseESSTwoUpInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.ESSTHREE, true), new FlagPole(Flags.ESSTWO, false)), + Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)), + startTime.minus(startPhaseESSTwoUpInterval)); + } else if (now.before(startTime.minus(startPhaseESSOneUpInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.ESSTWO, true), new FlagPole(Flags.ESSONE, false)), + Arrays.asList(new FlagPole(Flags.ESSONE, true)), + startTime.minus(startPhaseESSOneUpInterval)); + } if (now.before(startTime)) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.ESSONE, true)), + Arrays.asList(new FlagPole(Flags.ESSONE, false)), + startTime); + } else { + if (isIndividualRecallDisplayed()) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.XRAY, true)), + Arrays.asList(new FlagPole(Flags.XRAY, false)), + getIndividualRecallRemovalTime()); + } else { + return new FlagPoleState(Collections.emptyList()); + } + } + } + + @Override + protected RacingProcedureChangedListeners createChangedListenerContainer() { + return new ESSChangedListeners(); + } + + @Override + protected ESSChangedListeners getChangedListeners() { + return (ESSChangedListeners) super.getChangedListeners(); + } + + @Override + public void addChangedListener(ESSChangedListener listener) { + getChangedListeners().add(listener); + } + + @Override + public TimePoint getTimeLimit(TimePoint startTime) { + TimePoint firstBoatTime = new FinishingTimeFinder(raceLog).analyze(); + if (firstBoatTime != null) { + return firstBoatTime.plus((long) ((firstBoatTime.asMillis() - startTime.asMillis()) * 0.75)); + } + return null; + } + + @Override + protected boolean hasIndividualRecallByDefault() { + return true; + } + + @Override + public ESSConfiguration getConfiguration() { + return (ESSConfiguration) super.getConfiguration(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartChangedListener.java new file mode 100644 index 00000000000..93d6e48e67a --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartChangedListener.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; + +public interface GateStartChangedListener extends RacingProcedureChangedListener { + + void onGateLaunchTimeChanged(ReadonlyGateStartRacingProcedure procedure); + void onPathfinderChanged(ReadonlyGateStartRacingProcedure procedure); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartRacingProcedure.java new file mode 100644 index 00000000000..7770bdd4041 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/GateStartRacingProcedure.java @@ -0,0 +1,12 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + + +public interface GateStartRacingProcedure extends ReadonlyGateStartRacingProcedure, RacingProcedure { + + void setGateLineOpeningTimes(TimePoint now, long gateLaunchTime, long golfDownInterval); + void setPathfinder(TimePoint timePoint, String sailingId); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/ReadonlyGateStartRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/ReadonlyGateStartRacingProcedure.java new file mode 100644 index 00000000000..aa3758a3079 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/ReadonlyGateStartRacingProcedure.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate; + +import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; + + +public interface ReadonlyGateStartRacingProcedure extends ReadonlyRacingProcedure { + + public final static long DefaultGateLaunchStopTime = 4 * 60 * 1000; // 4 minutes + public final static long DefaultGolfDownTime = 3 * 60 * 1000; // 3 minutes + public final static String DefaultPathfinderId = ""; + + GateStartConfiguration getConfiguration(); + + void addChangedListener(GateStartChangedListener listener); + + TimePoint getGateLaunchStopTimePoint(TimePoint startTime); + TimePoint getGateShutdownTimePoint(TimePoint startTime); + long getGateLaunchStopTime(); + long getGolfDownTime(); + + String getPathfinder(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/BaseGateStartPrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/BaseGateStartPrerequisite.java new file mode 100644 index 00000000000..499ea21b54f --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/BaseGateStartPrerequisite.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedurePrerequisite; + +/** + * Easy access to the {@link GateStartRacingProcedure}. + */ +public abstract class BaseGateStartPrerequisite extends BaseRacingProcedurePrerequisite implements RacingProcedurePrerequisite { + + public BaseGateStartPrerequisite(FulfillmentFunction function, GateStartRacingProcedure procedure, TimePoint originalNow, + TimePoint originalStartTime) { + super(function, procedure, originalNow, originalStartTime); + } + + protected GateStartRacingProcedure getProcedure() { + return (GateStartRacingProcedure) procedure; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateLaunchTimePrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateLaunchTimePrerequisite.java new file mode 100644 index 00000000000..4ac8ab8a3bc --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateLaunchTimePrerequisite.java @@ -0,0 +1,32 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartRacingProcedure; + +public class GateLaunchTimePrerequisite extends BaseGateStartPrerequisite implements RacingProcedurePrerequisite { + + private final long golfDownTime; + + public GateLaunchTimePrerequisite(FulfillmentFunction function, GateStartRacingProcedure procedure, TimePoint originalNow, + TimePoint originalStartTime, long golfDownTime) { + super(function, procedure, originalNow, originalStartTime); + this.golfDownTime = golfDownTime; + } + + @Override + protected void resolveOn(Resolver resolver) { + resolver.fulfill(this); + } + + public void fulfill(long gateLaunchTime, long golfDownTime) { + getProcedure().setGateLineOpeningTimes(originalNow, gateLaunchTime, golfDownTime); + super.fulfilled(); + } + + @Override + public void fulfillWithDefault() { + fulfill(GateStartRacingProcedure.DefaultGateLaunchStopTime, golfDownTime); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartChangedListeners.java new file mode 100644 index 00000000000..d7ebdd1c882 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartChangedListeners.java @@ -0,0 +1,25 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; + +public class GateStartChangedListeners extends RacingProcedureChangedListeners implements + GateStartChangedListener { + + @Override + public void onGateLaunchTimeChanged(ReadonlyGateStartRacingProcedure gateStartRacingProcedure) { + for (GateStartChangedListener listener : getListeners()) { + listener.onGateLaunchTimeChanged(gateStartRacingProcedure); + } + } + + @Override + public void onPathfinderChanged(ReadonlyGateStartRacingProcedure procedure) { + for (GateStartChangedListener listener : getListeners()) { + listener.onPathfinderChanged(procedure); + } + } + + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java new file mode 100644 index 00000000000..0afde8fdd14 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/GateStartRacingProcedureImpl.java @@ -0,0 +1,252 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl; + +import java.util.Arrays; +import java.util.Collection; + +import com.sap.sailing.domain.base.configuration.procedures.GateStartConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent.GateLineOpeningTimes; +import com.sap.sailing.domain.racelog.analyzing.impl.GateLineOpeningTimeFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.PathfinderFinder; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEventImpl; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; +import com.sap.sailing.domain.racelog.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite.FulfillmentFunction; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.NoMorePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; + +public class GateStartRacingProcedureImpl extends BaseRacingProcedure implements GateStartRacingProcedure { + + private final static long startPhaseClassOverGolfUpIntervall = 8 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhasePapaUpInterval = 4 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhasePapaDownInterval = 1 * 60 * 1000; // minutes * seconds * milliseconds + + private final GateLineOpeningTimeFinder gateLineOpeningTimeAnalyzer; + private final PathfinderFinder pathfinderAnalyzer;; + + private GateLineOpeningTimes cachedGateLineOpeningTimes; + private boolean gateLineOpeningTimesHasBeenSet; + private String cachedPathfinder; + + public GateStartRacingProcedureImpl(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory factory, + GateStartConfiguration configuration) { + super(raceLog, author, factory, configuration); + this.gateLineOpeningTimeAnalyzer = new GateLineOpeningTimeFinder(raceLog); + this.pathfinderAnalyzer = new PathfinderFinder(raceLog); + + long defaultGolfDownTime = getDefaultGolfDownTime(); + this.cachedGateLineOpeningTimes = new GateLineOpeningTimes(GateStartRacingProcedure.DefaultGateLaunchStopTime, defaultGolfDownTime); + this.gateLineOpeningTimesHasBeenSet = false; + + update(); + } + + protected long getDefaultGolfDownTime() { + Boolean flag = getConfiguration().hasAdditionalGolfDownTime(); + if (flag != null) { + return flag ? GateStartRacingProcedure.DefaultGolfDownTime : 0; + } + return 0; + } + + @Override + public RacingProcedureType getType() { + return RacingProcedureType.GateStart; + } + + @Override + protected boolean hasIndividualRecallByDefault() { + return false; + } + + @Override + public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, + FulfillmentFunction function) { + if (startTime.before(now)) { + if (getConfiguration().hasPathfinder() != null && getConfiguration().hasPathfinder() && getPathfinder() == null) { + return new PathfinderPrerequisite(function, this, now, startTime); + } else if (!gateLineOpeningTimesHasBeenSet) { + return new GateLaunchTimePrerequisite(function, this, now, startTime, getDefaultGolfDownTime()); + } + } + return new NoMorePrerequisite(function); + } + + @Override + public boolean isStartphaseActive(TimePoint startTime, TimePoint now) { + if (now.before(startTime)) { + long timeTillStart = startTime.minus(now.asMillis()).asMillis(); + return timeTillStart < startPhaseClassOverGolfUpIntervall; + } + return false; + } + + @Override + public void triggerStateEventScheduling(ReadonlyRaceState state) { + switch (state.getStatus()) { + case SCHEDULED: + case STARTPHASE: + case RUNNING: + rescheduleGateShutdownTime(state.getStartTime()); + break; + default: + break; + } + super.triggerStateEventScheduling(state); + } + + @Override + protected Collection createStartStateEvents(TimePoint startTime) { + return Arrays. asList( + new RaceStateEventImpl(startTime.minus(startPhaseClassOverGolfUpIntervall), + RaceStateEvents.GATE_CLASS_OVER_GOLF_UP), + new RaceStateEventImpl(startTime.minus(startPhasePapaUpInterval), RaceStateEvents.GATE_PAPA_UP), + new RaceStateEventImpl(startTime.minus(startPhasePapaDownInterval), RaceStateEvents.GATE_PAPA_DOWN), + new RaceStateEventImpl(startTime, RaceStateEvents.START)); + } + + @Override + public boolean processStateEvent(RaceStateEvent event) { + switch (event.getEventName()) { + case START: + if (!gateLineOpeningTimesHasBeenSet) { + setGateLineOpeningTimes(event.getTimePoint(), + cachedGateLineOpeningTimes.getGateLaunchStopTime(), cachedGateLineOpeningTimes.getGolfDownTime()); + } + rescheduleGateShutdownTime(event.getTimePoint()); + return true; + case GATE_CLASS_OVER_GOLF_UP: + case GATE_PAPA_UP: + case GATE_PAPA_DOWN: + case GATE_SHUTDOWN: + getChangedListeners().onActiveFlagsChanged(this); + return true; + default: + return super.processStateEvent(event); + } + } + + @Override + public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) { + Flags classFlag = getConfiguration().getClassFlag() != null ? getConfiguration().getClassFlag() : Flags.CLASS; + TimePoint gateShutdownTime = getGateShutdownTimePoint(startTime); + if (now.before(startTime.minus(startPhaseClassOverGolfUpIntervall))) { + return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, false), new FlagPole(Flags.PAPA, + false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)), + startTime.minus(startPhaseClassOverGolfUpIntervall)); + } else if (now.before(startTime.minus(startPhasePapaUpInterval))) { + return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, + false)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, true)), + startTime.minus(startPhasePapaUpInterval)); + } else if (now.before(startTime.minus(startPhasePapaDownInterval))) { + return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, + true)), Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, false)), + startTime.minus(startPhasePapaDownInterval)); + } else if (now.before(startTime)) { + return new FlagPoleState(Arrays.asList(new FlagPole(classFlag, Flags.GOLF, true), new FlagPole(Flags.PAPA, + false)), Arrays.asList(new FlagPole(classFlag, false), new FlagPole(Flags.GOLF, true)), startTime); + } else if (now.before(gateShutdownTime)) { + return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, true)), Arrays.asList(new FlagPole( + Flags.GOLF, false)), gateShutdownTime); + } else { + return new FlagPoleState(Arrays.asList(new FlagPole(Flags.GOLF, false))); + } + } + + @Override + public TimePoint getGateLaunchStopTimePoint(TimePoint startTime) { + return startTime.plus(getGateLaunchStopTime()); + } + + @Override + public TimePoint getGateShutdownTimePoint(TimePoint startTime) { + return getGateLaunchStopTimePoint(startTime).plus(getGolfDownTime()); + } + + @Override + public long getGateLaunchStopTime() { + return cachedGateLineOpeningTimes.getGateLaunchStopTime(); + } + + @Override + public long getGolfDownTime() { + return cachedGateLineOpeningTimes.getGolfDownTime(); + } + + @Override + public void setGateLineOpeningTimes(TimePoint now, long gateLaunchStopTime, long golfDownTime) { + raceLog.add(factory.createGateLineOpeningTimeEvent(now, author, raceLog.getCurrentPassId(), gateLaunchStopTime, golfDownTime)); + } + + @Override + public String getPathfinder() { + return cachedPathfinder; + } + + @Override + public void setPathfinder(TimePoint timePoint, String sailingId) { + raceLog.add(factory.createPathfinderEvent(timePoint, author, raceLog.getCurrentPassId(), sailingId)); + } + + @Override + protected RacingProcedureChangedListeners createChangedListenerContainer() { + return new GateStartChangedListeners(); + } + + @Override + protected GateStartChangedListeners getChangedListeners() { + return (GateStartChangedListeners) super.getChangedListeners(); + } + + @Override + public void addChangedListener(GateStartChangedListener listener) { + getChangedListeners().add(listener); + } + + @Override + protected void update() { + GateLineOpeningTimes gateLineOpeningTimes = gateLineOpeningTimeAnalyzer.analyze(); + if (gateLineOpeningTimes != null + && (!gateLineOpeningTimes.equals(cachedGateLineOpeningTimes) || !gateLineOpeningTimesHasBeenSet)) { + cachedGateLineOpeningTimes = gateLineOpeningTimes; + gateLineOpeningTimesHasBeenSet = true; + getChangedListeners().onGateLaunchTimeChanged(this); + } + + String pathfinder = pathfinderAnalyzer.analyze(); + if (!Util.equalsWithNull(cachedPathfinder, pathfinder)) { + cachedPathfinder = pathfinder; + getChangedListeners().onPathfinderChanged(this); + } + + super.update(); + } + + @Override + public GateStartConfiguration getConfiguration() { + return (GateStartConfiguration) super.getConfiguration(); + } + + private void rescheduleGateShutdownTime(TimePoint startTime) { + unscheduleStateEvent(RaceStateEvents.GATE_SHUTDOWN); + if (startTime != null) { + scheduleStateEvents(new RaceStateEventImpl(getGateShutdownTimePoint(startTime), + RaceStateEvents.GATE_SHUTDOWN)); + } + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/PathfinderPrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/PathfinderPrerequisite.java new file mode 100644 index 00000000000..305fb5c6288 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/gate/impl/PathfinderPrerequisite.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.GateStartRacingProcedure; + + +public class PathfinderPrerequisite extends BaseGateStartPrerequisite implements RacingProcedurePrerequisite { + + public PathfinderPrerequisite(FulfillmentFunction function, GateStartRacingProcedure procedure, + TimePoint originalNow, TimePoint originalStartTime) { + super(function, procedure, originalNow, originalStartTime); + } + + @Override + public void resolveOn(Resolver resolver) { + resolver.fulfill(this); + } + + public void fulfill(String pathfinder) { + getProcedure().setPathfinder(originalNow, pathfinder); + super.fulfilled(); + } + + @Override + public void fulfillWithDefault() { + fulfill(GateStartRacingProcedure.DefaultPathfinderId); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedure.java new file mode 100644 index 00000000000..52d74e54722 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedure.java @@ -0,0 +1,242 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import java.util.Arrays; +import java.util.Collection; + +import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogChangedListener; +import com.sap.sailing.domain.racelog.RaceLogEvent; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.RaceLogEventVisitor; +import com.sap.sailing.domain.racelog.analyzing.impl.IndividualRecallDisplayedFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.IsIndividualRecallDisplayedAnalyzer; +import com.sap.sailing.domain.racelog.impl.RaceLogChangedVisitor; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.RaceStateEventScheduler; +import com.sap.sailing.domain.racelog.state.ReadonlyRaceState; +import com.sap.sailing.domain.racelog.state.impl.BaseRaceStateChangedListener; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEventImpl; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; + +/** + * Base class for all your {@link RacingProcedure}s. + * + * It is a good idea to call {@link BaseRacingProcedure#update()} in your constructor to ensure your procedure is + * correctly initialized. + * + */ +public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener implements RacingProcedure, + RaceLogChangedListener { + + private final static long individualRecallRemovalTimeout = 4 * 60 * 1000; // minutes * seconds * milliseconds + + private RaceStateEventScheduler scheduler; + + protected final RaceLog raceLog; + protected final RaceLogEventAuthor author; + protected final RaceLogEventFactory factory; + private final RacingProcedureConfiguration configuration; + + private final RacingProcedureChangedListeners changedListeners; + private final IsIndividualRecallDisplayedAnalyzer isRecallDisplayedAnalyzer; + private final IndividualRecallDisplayedFinder recallDisplayedFinder; + private final RaceLogEventVisitor raceLogListener; + + private boolean cachedIsIndividualRecallDisplayed; + + /** + * When calling me, call {@link BaseRacingProcedure#update()} afterwards! + */ + public BaseRacingProcedure(RaceLog raceLog, RaceLogEventAuthor author, RaceLogEventFactory factory, + RacingProcedureConfiguration configuration) { + if (configuration == null) { + throw new IllegalArgumentException("configuration must not be null"); + } + + this.raceLog = raceLog; + this.author = author; + this.factory = factory; + this.configuration = configuration; + + this.changedListeners = createChangedListenerContainer(); + this.isRecallDisplayedAnalyzer = new IsIndividualRecallDisplayedAnalyzer(raceLog); + this.recallDisplayedFinder = new IndividualRecallDisplayedFinder(raceLog); + + this.raceLogListener = new RaceLogChangedVisitor(this); + this.raceLog.addListener(raceLogListener); + + this.cachedIsIndividualRecallDisplayed = false; + } + + @Override + public void detach() { + raceLog.removeListener(raceLogListener); + changedListeners.removeAll(); + } + + @Override + public void addChangedListener(RacingProcedureChangedListener listener) { + changedListeners.addBaseListener(listener); + } + + public void removeChangedListener(RacingProcedureChangedListener listener) { + changedListeners.remove(listener); + }; + + protected RacingProcedureChangedListeners getChangedListeners() { + return changedListeners; + } + + protected abstract RacingProcedureChangedListeners createChangedListenerContainer(); + + @Override + public RaceLog getRaceLog() { + return raceLog; + } + + @Override + public void setStateEventScheduler(RaceStateEventScheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public void triggerStateEventScheduling(ReadonlyRaceState state) { + switch (state.getStatus()) { + case SCHEDULED: + case STARTPHASE: + onStartTimeChanged(state); + break; + case RUNNING: + if (isIndividualRecallDisplayed()) { + rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze()); + } + break; + default: + break; + } + } + + @Override + public boolean isIndividualRecallDisplayed() { + return cachedIsIndividualRecallDisplayed; + } + + @Override + public TimePoint getIndividualRecallRemovalTime() { + if (hasIndividualRecall()) { + if (isIndividualRecallDisplayed()) { + TimePoint displayTime = recallDisplayedFinder.analyze(); + if (displayTime != null) { + return displayTime.plus(individualRecallRemovalTimeout); + } + } + + } + return null; + } + + @Override + public void displayIndividualRecall(TimePoint displayTime) { + raceLog.add(factory.createFlagEvent(displayTime, author, raceLog.getCurrentPassId(), Flags.XRAY, Flags.NONE, true)); + } + + @Override + public void removeIndividualRecall(TimePoint timePoint) { + raceLog.add(factory.createFlagEvent(timePoint, author, raceLog.getCurrentPassId(), Flags.XRAY, Flags.NONE, false)); + } + + @Override + public boolean processStateEvent(RaceStateEvent event) { + switch (event.getEventName()) { + case INDIVIDUAL_RECALL_TIMEOUT: + removeIndividualRecall(event.getTimePoint()); + return true; + default: + return false; + } + } + + @Override + public boolean hasIndividualRecall() { + return configuration.hasInidividualRecall() == null ? + hasIndividualRecallByDefault() : configuration.hasInidividualRecall(); + } + + protected abstract boolean hasIndividualRecallByDefault(); + + @Override + public void eventAdded(RaceLogEvent event) { + update(); + } + + protected void update() { + boolean isRecallDisplayed = isRecallDisplayedAnalyzer.analyze(); + if (cachedIsIndividualRecallDisplayed != isRecallDisplayed) { + cachedIsIndividualRecallDisplayed = isRecallDisplayed; + if (cachedIsIndividualRecallDisplayed) { + changedListeners.onIndividualRecallDisplayed(this); + changedListeners.onActiveFlagsChanged(this); + rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze()); + } else { + changedListeners.onIndividualRecallRemoved(this); + changedListeners.onActiveFlagsChanged(this); + unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT); + } + } + } + + private void rescheduleIndividualRecallTimeout(TimePoint displayTime) { + unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT); + scheduleStateEvents(new RaceStateEventImpl(displayTime.plus(individualRecallRemovalTimeout), RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT)); + } + + protected void scheduleStateEvents(RaceStateEvent stateEvent) { + scheduleStateEvents(Arrays.asList(stateEvent)); + } + + protected void scheduleStateEvents(Collection stateEvents) { + if (scheduler != null) { + scheduler.scheduleStateEvents(stateEvents); + } + } + + protected void unscheduleStateEvent(RaceStateEvents raceStateEventName) { + if (scheduler != null) { + scheduler.unscheduleStateEvent(raceStateEventName); + } + } + + @Override + public RacingProcedureConfiguration getConfiguration() { + return configuration; + } + + protected abstract Collection createStartStateEvents(TimePoint startTime); + + @Override + public void onStartTimeChanged(ReadonlyRaceState state) { + getChangedListeners().onActiveFlagsChanged(this); + if (scheduler != null && state.getStartTime() != null) { + scheduler.unscheduleAllEvents(); + scheduler.scheduleStateEvents(createStartStateEvents(state.getStartTime())); + } + } + + @Override + public void onAdvancePass(ReadonlyRaceState state) { + unscheduleAllEvents(); + update(); + } + + private void unscheduleAllEvents() { + if (scheduler != null) { + scheduler.unscheduleAllEvents(); + } + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java new file mode 100644 index 00000000000..a3d0ef987e1 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedureChangedListener.java @@ -0,0 +1,23 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; + +public abstract class BaseRacingProcedureChangedListener implements RacingProcedureChangedListener { + + @Override + public void onActiveFlagsChanged(ReadonlyRacingProcedure racingProcedure) { + + } + + @Override + public void onIndividualRecallDisplayed(ReadonlyRacingProcedure racingProcedure) { + + } + + @Override + public void onIndividualRecallRemoved(ReadonlyRacingProcedure racingProcedure) { + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedurePrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedurePrerequisite.java new file mode 100644 index 00000000000..8e103584490 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/BaseRacingProcedurePrerequisite.java @@ -0,0 +1,75 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; + +/** + * Base class for all {@link RacingProcedurePrerequisite}. + * + * Whenever your implementation of {@link RacingProcedurePrerequisite} is fulfilled call + * {@link BaseRacingProcedurePrerequisite#fulfilled()} to ensure that the next prerequisites is found and resolved. + */ +public abstract class BaseRacingProcedurePrerequisite implements RacingProcedurePrerequisite { + + /** + * {@link FulfillmentFunction} that is passed to the next {@link RacingProcedurePrerequisite} until all prerequisites are fulfilled. + */ + protected final FulfillmentFunction function; + + /** + * The {@link RacingProcedure} setting the {@link RacingProcedurePrerequisite}s. + */ + protected final RacingProcedure procedure; + + /** + * User-requested {@link TimePoint} depicting the current time. + */ + protected final TimePoint originalNow; + + /** + * User-requested new start time. + */ + protected final TimePoint originalStartTime; + + /** + * The resolver + */ + protected Resolver resolver; + + public BaseRacingProcedurePrerequisite(FulfillmentFunction function, RacingProcedure procedure, + TimePoint originalNow, TimePoint originalStartTime) { + this.function = function; + this.procedure = procedure; + this.originalNow = originalNow; + this.originalStartTime = originalStartTime; + } + + /** + * Call on fulfillment of your prerequisite. Will continue with next prerequisite! + */ + protected void fulfilled() { + RacingProcedurePrerequisite next = getNextPrerequisite(); + next.resolve(resolver); + } + + @Override + public void resolve(Resolver resolver) { + if (resolver == null) { + throw new IllegalArgumentException("resolver must not be null"); + } + + this.resolver = resolver; + resolveOn(resolver); + } + + /** + * Implement in your derived type for double dispatch. + */ + protected abstract void resolveOn(Resolver resolver); + + private RacingProcedurePrerequisite getNextPrerequisite() { + return procedure.checkPrerequisitesForStart(originalNow, originalStartTime, function); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/NoMorePrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/NoMorePrerequisite.java new file mode 100644 index 00000000000..89da490d1ce --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/NoMorePrerequisite.java @@ -0,0 +1,36 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; + +/** + * This special {@link RacingProcedurePrerequisite} won't check for next prerequisites. It just executes the + * {@link FulfillmentFunction}. + */ +public class NoMorePrerequisite extends BaseRacingProcedurePrerequisite implements RacingProcedurePrerequisite { + + public NoMorePrerequisite(FulfillmentFunction function) { + super(function, null, null, null); + } + + @Override + public void resolve(Resolver resolver) { + this.resolver = resolver; + setStartTime(); + } + + @Override + public void fulfillWithDefault() { + throw new UnsupportedOperationException("You should not be able to cal me!"); + } + + private void setStartTime() { + function.execute(); + resolver.onFulfilled(); + } + + @Override + protected void resolveOn(Resolver resolver) { + throw new UnsupportedOperationException("There should be no prerequisites after me!"); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureChangedListeners.java new file mode 100644 index 00000000000..c59646bb530 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureChangedListeners.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; + +public class RacingProcedureChangedListeners implements RacingProcedureChangedListener { + + private Set specificListeners; + private Set allListeners; + + public RacingProcedureChangedListeners() { + this.specificListeners = new HashSet(); + this.allListeners = new HashSet(); + } + + protected Set getListeners() { + return specificListeners; + } + + public boolean addBaseListener(RacingProcedureChangedListener listener) { + return allListeners.add(listener); + } + + public boolean add(T specificListener) { + return specificListeners.add(specificListener) && allListeners.add(specificListener); + } + + public void remove(RacingProcedureChangedListener listener) { + specificListeners.remove(listener); + allListeners.remove(listener); + } + + @Override + public void onActiveFlagsChanged(ReadonlyRacingProcedure racingProcedure) { + for (RacingProcedureChangedListener listener : allListeners) { + listener.onActiveFlagsChanged(racingProcedure); + } + } + + @Override + public void onIndividualRecallDisplayed(ReadonlyRacingProcedure racingProcedure) { + for (RacingProcedureChangedListener listener : allListeners) { + listener.onIndividualRecallDisplayed(racingProcedure); + } + } + + @Override + public void onIndividualRecallRemoved(ReadonlyRacingProcedure racingProcedure) { + for (RacingProcedureChangedListener listener : allListeners) { + listener.onIndividualRecallRemoved(racingProcedure); + } + } + + public void removeAll() { + allListeners.clear(); + specificListeners.clear(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java new file mode 100644 index 00000000000..448b5fc96a5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedureFactoryImpl.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; + +public class RacingProcedureFactoryImpl extends ReadonlyRacingProcedureFactory implements RacingProcedureFactory { + + private final RaceLogEventAuthor author; + private final RaceLogEventFactory factory; + + public RacingProcedureFactoryImpl(RaceLogEventAuthor author, RaceLogEventFactory factory, + ConfigurationLoader configuration) { + super(configuration); + this.author = author; + this.factory = factory; + } + + @Override + public ReadonlyRacingProcedure createRacingProcedure(RacingProcedureType type, RaceLog raceLog) { + return createProcedure(type, raceLog, author, factory); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedurePrerequisiteAutoResolver.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedurePrerequisiteAutoResolver.java new file mode 100644 index 00000000000..db915fdfb04 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/RacingProcedurePrerequisiteAutoResolver.java @@ -0,0 +1,34 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite.Resolver; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl.GateLaunchTimePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl.PathfinderPrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl.StartmodePrerequisite; + +/** + * Always calls {@link RacingProcedurePrerequisite#fulfillWithDefault()}. + */ +public class RacingProcedurePrerequisiteAutoResolver implements Resolver { + + @Override + public void fulfill(PathfinderPrerequisite prerequisite) { + prerequisite.fulfillWithDefault(); + } + + @Override + public void fulfill(GateLaunchTimePrerequisite prerequisite) { + prerequisite.fulfillWithDefault(); + } + + @Override + public void fulfill(StartmodePrerequisite prerequisite) { + prerequisite.fulfillWithDefault(); + } + + @Override + public void onFulfilled() { + + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/ReadonlyRacingProcedureFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/ReadonlyRacingProcedureFactory.java new file mode 100644 index 00000000000..9e160da8b62 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/impl/ReadonlyRacingProcedureFactory.java @@ -0,0 +1,65 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.impl; + +import com.sap.sailing.domain.base.configuration.ConfigurationLoader; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.impl.NoAddingRaceLogWrapper; +import com.sap.sailing.domain.racelog.impl.RaceLogEventAuthorImpl; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureFactory; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.basic.impl.BasicRacingProcedureImpl; +import com.sap.sailing.domain.racelog.state.racingprocedure.ess.impl.ESSRacingProcedureImpl; +import com.sap.sailing.domain.racelog.state.racingprocedure.gate.impl.GateStartRacingProcedureImpl; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl.RRS26RacingProcedureImpl; + +/** + * Factory for creating {@link ReadOnlyRacingProcedure}s. + * + * The {@link ReadonlyRacingProcedureFactory} uses a {@link ConfigurationLoader} to ensure that any newly created + * {@link RacingProcedure} is passed a recent and immutable configuration. + */ +public class ReadonlyRacingProcedureFactory implements RacingProcedureFactory { + + protected final ConfigurationLoader configuration; + + public ReadonlyRacingProcedureFactory(ConfigurationLoader configuration) { + this.configuration = configuration; + } + + @Override + public RegattaConfiguration getConfiguration() { + return configuration.load(); + } + + protected ReadonlyRacingProcedure createProcedure(RacingProcedureType type, RaceLog raceLog, + RaceLogEventAuthor author, RaceLogEventFactory factory) { + RegattaConfiguration loadedConfiguration = configuration.load(); + switch (type) { + case ESS: + return new ESSRacingProcedureImpl(raceLog, author, factory, loadedConfiguration.getESSConfiguration()); + case GateStart: + return new GateStartRacingProcedureImpl(raceLog, author, factory, + loadedConfiguration.getGateStartConfiguration()); + case RRS26: + return new RRS26RacingProcedureImpl(raceLog, author, factory, loadedConfiguration.getRRS26Configuration()); + case BASIC: + return new BasicRacingProcedureImpl(raceLog, author, factory, loadedConfiguration.getBasicConfiguration()); + default: + throw new UnsupportedOperationException("Unknown racing procedure " + type.toString()); + } + } + + @Override + public ReadonlyRacingProcedure createRacingProcedure(RacingProcedureType type, RaceLog raceLog) { + // Just a mock author since we will never add anything to the racelog + RaceLogEventAuthor author = new RaceLogEventAuthorImpl("Illegal Author", 128); + // Wrap the racelog to disable adding... + RaceLog wrappedRaceLog = new NoAddingRaceLogWrapper(raceLog); + return createProcedure(type, wrappedRaceLog, author, RaceLogEventFactory.INSTANCE); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26ChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26ChangedListener.java new file mode 100644 index 00000000000..89840631bd0 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26ChangedListener.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26; + +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; + +public interface RRS26ChangedListener extends RacingProcedureChangedListener { + + void onStartmodeChanged(ReadonlyRRS26RacingProcedure racingProcedure); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26RacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26RacingProcedure.java new file mode 100644 index 00000000000..6ba1fb310d8 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/RRS26RacingProcedure.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedure; + +public interface RRS26RacingProcedure extends ReadonlyRRS26RacingProcedure, RacingProcedure { + + void setStartModeFlag(TimePoint timePoint, Flags startMode); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/ReadonlyRRS26RacingProcedure.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/ReadonlyRRS26RacingProcedure.java new file mode 100644 index 00000000000..7057c79aaeb --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/ReadonlyRRS26RacingProcedure.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26; + +import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.state.racingprocedure.ReadonlyRacingProcedure; + +public interface ReadonlyRRS26RacingProcedure extends ReadonlyRacingProcedure { + + public final static Flags DefaultStartMode = Flags.PAPA; + + void addChangedListener(RRS26ChangedListener listener); + + RRS26Configuration getConfiguration(); + + Flags getStartModeFlag(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/BaseRRS26Prerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/BaseRRS26Prerequisite.java new file mode 100644 index 00000000000..89abb715bcc --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/BaseRRS26Prerequisite.java @@ -0,0 +1,22 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.RRS26RacingProcedure; + +/** + * Easy access to the {@link RRS26RacingProcedure}. + */ +public abstract class BaseRRS26Prerequisite extends BaseRacingProcedurePrerequisite implements RacingProcedurePrerequisite { + + public BaseRRS26Prerequisite(FulfillmentFunction function, RRS26RacingProcedure procedure, TimePoint originalNow, + TimePoint originalStartTime) { + super(function, procedure, originalNow, originalStartTime); + } + + protected RRS26RacingProcedure getProcedure() { + return (RRS26RacingProcedure) procedure; + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26ChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26ChangedListeners.java new file mode 100644 index 00000000000..05dcb1687e6 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26ChangedListeners.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl; + +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.RRS26ChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.ReadonlyRRS26RacingProcedure; + +public class RRS26ChangedListeners extends RacingProcedureChangedListeners implements + RRS26ChangedListener { + + @Override + public void onStartmodeChanged(ReadonlyRRS26RacingProcedure racingProcedure) { + for (RRS26ChangedListener listener : getListeners()) { + listener.onStartmodeChanged(racingProcedure); + } + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java new file mode 100644 index 00000000000..5089870d47c --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/RRS26RacingProcedureImpl.java @@ -0,0 +1,204 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.domain.racelog.RaceLog; +import com.sap.sailing.domain.racelog.RaceLogEventAuthor; +import com.sap.sailing.domain.racelog.RaceLogEventFactory; +import com.sap.sailing.domain.racelog.analyzing.impl.RRS26StartModeFlagFinder; +import com.sap.sailing.domain.racelog.analyzing.impl.RacingProcedureTypeAnalyzer; +import com.sap.sailing.domain.racelog.state.RaceStateEvent; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEventImpl; +import com.sap.sailing.domain.racelog.state.impl.RaceStateEvents; +import com.sap.sailing.domain.racelog.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedureChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite.FulfillmentFunction; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.BaseRacingProcedure; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.NoMorePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.impl.RacingProcedureChangedListeners; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.RRS26ChangedListener; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.RRS26RacingProcedure; + +public class RRS26RacingProcedureImpl extends BaseRacingProcedure implements RRS26RacingProcedure { + + private final static long startPhaseClassUpInterval = 5 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhaseStartModeUpInterval = 4 * 60 * 1000; // minutes * seconds * milliseconds + private final static long startPhaseStartModeDownInterval = 1 * 60 * 1000; // minutes * seconds * milliseconds + + private final RRS26StartModeFlagFinder startModeFlagAnalyzer; + + private Flags cachedStartmodeFlag; + private boolean startmodeFlagHasBeenSet; + + public RRS26RacingProcedureImpl(RaceLog raceLog, RaceLogEventAuthor author, + RaceLogEventFactory factory, RRS26Configuration configuration) { + super(raceLog, author, factory, configuration); + + RacingProcedureTypeAnalyzer procedureAnalyzer = new RacingProcedureTypeAnalyzer(raceLog); + if (configuration.getStartModeFlags() != null) { + this.startModeFlagAnalyzer = new RRS26StartModeFlagFinder(procedureAnalyzer, raceLog, configuration.getStartModeFlags()); + } else { + this.startModeFlagAnalyzer = new RRS26StartModeFlagFinder(procedureAnalyzer, raceLog); + } + + this.cachedStartmodeFlag = RRS26RacingProcedure.DefaultStartMode; + this.startmodeFlagHasBeenSet = false; + + update(); + } + + @Override + public RacingProcedureType getType() { + return RacingProcedureType.RRS26; + } + + @Override + public boolean hasIndividualRecall() { + boolean hasRecall = super.hasIndividualRecall(); + if (!hasRecall) { + return false; + } else if (startmodeFlagHasBeenSet) { + return cachedStartmodeFlag != Flags.BLACK; + } else { + return hasRecall; + } + } + + @Override + protected boolean hasIndividualRecallByDefault() { + return true; + } + + @Override + public RacingProcedurePrerequisite checkPrerequisitesForStart(TimePoint now, TimePoint startTime, + FulfillmentFunction function) { + if (startTime.minus(startPhaseStartModeUpInterval).before(now) && !startmodeFlagHasBeenSet) { + return new StartmodePrerequisite(function, this, now, startTime); + } + return new NoMorePrerequisite(function); + } + + @Override + public boolean isStartphaseActive(TimePoint startTime, TimePoint now) { + if (now.before(startTime)) { + long timeTillStart = startTime.minus(now.asMillis()).asMillis(); + return timeTillStart < startPhaseClassUpInterval; + } + return false; + } + + @Override + protected Collection createStartStateEvents(TimePoint startTime) { + return Arrays. asList( + new RaceStateEventImpl(startTime.minus(startPhaseClassUpInterval), RaceStateEvents.RRS26_CLASS_UP), + new RaceStateEventImpl(startTime.minus(startPhaseStartModeUpInterval), RaceStateEvents.RRS26_STARTMODE_UP), + new RaceStateEventImpl(startTime.minus(startPhaseStartModeDownInterval), RaceStateEvents.RRS26_STARTMODE_DOWN), + new RaceStateEventImpl(startTime, RaceStateEvents.START)); + } + + @Override + public boolean processStateEvent(RaceStateEvent event) { + switch (event.getEventName()) { + case RRS26_STARTMODE_UP: + if (!startmodeFlagHasBeenSet) { + setStartModeFlag(event.getTimePoint(), cachedStartmodeFlag); + } + case RRS26_CLASS_UP: + case RRS26_STARTMODE_DOWN: + getChangedListeners().onActiveFlagsChanged(this); + return true; + default: + return super.processStateEvent(event); + } + } + + @Override + public FlagPoleState getActiveFlags(TimePoint startTime, TimePoint now) { + Flags classFlag = getConfiguration().getClassFlag() != null ? + getConfiguration().getClassFlag() : Flags.CLASS; + if (now.before(startTime.minus(startPhaseClassUpInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)), + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)), + startTime.minus(startPhaseClassUpInterval)); + } else if (now.before(startTime.minus(startPhaseStartModeUpInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)), + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)), + startTime.minus(startPhaseStartModeUpInterval)); + } else if (now.before(startTime.minus(startPhaseStartModeDownInterval))) { + return new FlagPoleState( + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, true)), + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)), + startTime.minus(startPhaseStartModeDownInterval)); + } else if (now.before(startTime)) { + return new FlagPoleState( + Arrays.asList(new FlagPole(classFlag, true), new FlagPole(cachedStartmodeFlag, false)), + Arrays.asList(new FlagPole(classFlag, false), new FlagPole(cachedStartmodeFlag, false)), + startTime); + } else { + if (isIndividualRecallDisplayed()) { + return new FlagPoleState( + Arrays.asList(new FlagPole(Flags.XRAY, true)), + Arrays.asList(new FlagPole(Flags.XRAY, false)), + getIndividualRecallRemovalTime()); + } else { + return new FlagPoleState(Collections.emptyList()); + } + } + } + + @Override + protected RacingProcedureChangedListeners createChangedListenerContainer() { + return new RRS26ChangedListeners(); + } + + @Override + protected RRS26ChangedListeners getChangedListeners() { + return (RRS26ChangedListeners) super.getChangedListeners(); + } + + @Override + public void addChangedListener(RRS26ChangedListener listener) { + getChangedListeners().add(listener); + } + + @Override + public void setStartModeFlag(TimePoint timePoint, Flags startMode) { + raceLog.add(factory.createFlagEvent(timePoint, author, raceLog.getCurrentPassId(), startMode, Flags.NONE, true)); + } + + @Override + public Flags getStartModeFlag() { + return cachedStartmodeFlag; + } + + public boolean startmodeFlagHasBeenSet() { + return startmodeFlagHasBeenSet; + } + + @Override + protected void update() { + Flags startmodeFlag = startModeFlagAnalyzer.analyze(); + if (startmodeFlag != null && (!startmodeFlag.equals(cachedStartmodeFlag) || !startmodeFlagHasBeenSet)) { + cachedStartmodeFlag = startmodeFlag; + startmodeFlagHasBeenSet = true; + getChangedListeners().onStartmodeChanged(this); + } + super.update(); + } + + @Override + public RRS26Configuration getConfiguration() { + return (RRS26Configuration) super.getConfiguration(); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/StartmodePrerequisite.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/StartmodePrerequisite.java new file mode 100644 index 00000000000..e141ce89033 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelog/state/racingprocedure/rrs26/impl/StartmodePrerequisite.java @@ -0,0 +1,30 @@ +package com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.impl; + +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.racelog.state.racingprocedure.RacingProcedurePrerequisite; +import com.sap.sailing.domain.racelog.state.racingprocedure.rrs26.RRS26RacingProcedure; + +public class StartmodePrerequisite extends BaseRRS26Prerequisite implements RacingProcedurePrerequisite { + + public StartmodePrerequisite(FulfillmentFunction function, RRS26RacingProcedure procedure, TimePoint originalNow, + TimePoint originalStartTime) { + super(function, procedure, originalNow, originalStartTime); + } + + @Override + protected void resolveOn(Resolver resolver) { + resolver.fulfill(this); + } + + public void fulfill(Flags startmodeFlag) { + getProcedure().setStartModeFlag(originalNow, startmodeFlag); + super.fulfilled(); + } + + @Override + public void fulfillWithDefault() { + fulfill(RRS26RacingProcedure.DefaultStartMode); + } + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java index 97dccc0c9df..bb4cd50d099 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java @@ -104,4 +104,24 @@ public interface Track extends Serializable { */ Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); + /** + * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see + * also {@link #getFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns a descending iterator starting at the first raw fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java index 9cf2c18c2ad..9be471d4602 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java @@ -253,6 +253,14 @@ public class TrackImpl implements Track { return result; } + @Override + public Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalFixes().headSet( + getDummyFix(startingAt), inclusive).descendingIterator(); + return result; + } + /** * Creates a dummy fix that conforms to FixType. This in particular means that subclasses * instantiating FixType with a specific class need to redefine this method so as to return @@ -274,4 +282,21 @@ public class TrackImpl implements Track { return result; } + @Override + public Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { + assertReadLock(); + Iterator result = (Iterator) getInternalRawFixes().headSet( + getDummyFix(startingAt), inclusive).descendingIterator(); + return result; + } + + protected boolean add(FixType fix) { + lockForWrite(); + try { + return getInternalRawFixes().add(fix); + } finally { + unlockAfterWrite(); + } + } + } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 32a12eea7be..5f91ee763b6 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -11,6 +11,7 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorStore; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.RaceDefinition; @@ -126,7 +127,8 @@ public class DomainFactoryImpl implements DomainFactory { @Override public Competitor getOrCreateCompetitor(com.sap.sailing.domain.swisstimingadapter.Competitor competitor, RaceType raceType) { Competitor result = getCompetitorByBoatIDAndRaceType(competitor.getBoatID(), raceType); - if (result == null || baseDomainFactory.isCompetitorToUpdateDuringGetOrCreate(result)) { + CompetitorStore competitorStore = baseDomainFactory.getCompetitorStore(); + if (result == null || competitorStore.isCompetitorToUpdateDuringGetOrCreate(result)) { DynamicBoat boat = new BoatImpl(competitor.getName(), raceType.getBoatClass(), competitor.getBoatID()); List teamMembers = new ArrayList(); for (String teamMemberName : competitor.getName().split("[-+&]")) { @@ -134,8 +136,8 @@ public class DomainFactoryImpl implements DomainFactory { /* dateOfBirth */ null, teamMemberName.trim())); } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); - result = baseDomainFactory.getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceType), - competitor.getName(), team, boat); + result = competitorStore.getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceType), + competitor.getName(), null /*displayColor*/, team, boat); } return result; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index edb8fffa2cb..b1feeddb4e0 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -21,6 +21,7 @@ import org.junit.Before; import org.junit.Test; import com.sap.sailing.domain.common.Cloner; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; @@ -196,7 +197,7 @@ public class LeaderboardDTODiffingTest { @Test public void testCompetitorListChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(13, somebodyNew); // insert a competitor; this should mess up all others' indexes; check if this works CompetitorDTO wolfgang = getPreviousCompetitorByName("HUNGER +JESS"); @@ -216,7 +217,7 @@ public class LeaderboardDTODiffingTest { Util.addAll(newVersion.getSuppressedCompetitors(), newSuppressedCompetitors); newVersion.setSuppressedCompetitors(newSuppressedCompetitors); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.setSuppressed(newVersion.competitors.get(13), true); // suppress an existing competitor; compaction should reduce this to a single number only newVersion.setSuppressed(somebodyNew, true); // check that mixed mode with existing and new competitors works as well @@ -238,7 +239,7 @@ public class LeaderboardDTODiffingTest { @Test public void testDisplayNameChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newVersion.competitorDisplayNames = new HashMap(newVersion.competitorDisplayNames); @@ -267,7 +268,7 @@ public class LeaderboardDTODiffingTest { List newOrdering = new ArrayList(newVersion.getCompetitorsFromBestToWorst(r9)); newVersion.setCompetitorsFromBestToWorst(r9, newOrdering); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version - CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", "DE", "GER", "Germany", "GER 1234", "912p09871203987", + CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "DE", "GER", "Germany", "GER 1234", "912p09871203987", new BoatClassDTO("505", 5.05)); newVersion.competitors.add(somebodyNew); newOrdering.add(somebodyNew); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java index f3d47f07fc7..90f3ddccd9c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractLeaderboardTest.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.test; +import java.io.Serializable; import java.util.Collections; import com.sap.sailing.domain.base.impl.BoatClassImpl; @@ -8,10 +9,20 @@ import com.sap.sailing.domain.base.impl.CompetitorImpl; import com.sap.sailing.domain.base.impl.NationalityImpl; import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Color; public abstract class AbstractLeaderboardTest { public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(123, competitorName, new TeamImpl("STG", Collections.singleton( + return new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null)); + } + + public static CompetitorImpl createCompetitor(String competitorName, Serializable id) { + return new CompetitorImpl(id, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java index 34c5cad1a96..306ab14bd8a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/AbstractTracTracLiveTest.java @@ -18,6 +18,8 @@ import java.util.logging.Logger; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.Timeout; import com.maptrack.client.io.TypeController; import com.sap.sailing.domain.common.NauticalSide; @@ -50,6 +52,8 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest impl private Thread ioThread; private DataController controller; + + @Rule public Timeout AbstractTracTracLiveTestTimeout = new Timeout(2 * 60 * 1000); protected AbstractTracTracLiveTest() throws URISyntaxException, MalformedURLException { receivers = new HashSet(); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java index 0219fb6699a..18186bc1ded 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OfflineSerializationTest.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.UUID; import org.junit.Test; @@ -25,6 +26,7 @@ import com.sap.sailing.domain.base.impl.CourseAreaImpl; import com.sap.sailing.domain.base.impl.DomainFactoryImpl; import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -97,7 +99,7 @@ public class OfflineSerializationTest extends AbstractSerializationTest { // see bug 1605 @Test public void testSerializingOverallLeaderboardWithFactorOnColumn() throws ClassNotFoundException, IOException { - Leaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 5 }), new HighPoint(), new CourseAreaImpl("Alpha", "Alpha")); + Leaderboard leaderboard = new FlexibleLeaderboardImpl("Test Leaderboard", new ThresholdBasedResultDiscardingRuleImpl(new int[] { 3, 5 }), new HighPoint(), new CourseAreaImpl("Alpha", UUID.randomUUID())); LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("LeaderboardGroup", "Test Leaderboard Group", /* displayGroupsInReverseOrder */ false, Arrays.asList(new Leaderboard[] { leaderboard })); final LeaderboardGroupMetaLeaderboard overallLeaderboard = new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPoint(), new ThresholdBasedResultDiscardingRuleImpl(new int[0])); leaderboardGroup.setOverallLeaderboard(overallLeaderboard); @@ -156,7 +158,7 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory senderDomainFactory = new DomainFactoryImpl(); DomainFactory receiverDomainFactory = new DomainFactoryImpl(); String competitorName = "Tina Maximiliane Lutz"; - Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, new TeamImpl("STG", Collections.singleton( + Competitor sendersCompetitor1 = new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, senderDomainFactory.getOrCreateNationality("GER"), /* dateOfBirth */ null, "This is famous "+competitorName)), new PersonImpl("Rigo van Maas", senderDomainFactory.getOrCreateNationality("GER"), diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java index 786a163d1e9..1d6d1b6f49e 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PolarSheetGenerationTest.java @@ -27,6 +27,7 @@ import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.PolarSheetsData; import com.sap.sailing.domain.common.PolarSheetsHistogramData; @@ -85,7 +86,7 @@ public class PolarSheetGenerationTest { settings, executor); BoatClass forelle = new BoatClassImpl("Forelle", true); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", new TeamImpl("SAP", null, null), + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); PerRaceAndCompetitorPolarSheetGenerationWorker task = new PerRaceAndCompetitorPolarSheetGenerationWorker(race, diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogTest.java deleted file mode 100644 index 67c2a551331..00000000000 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/RaceLogTest.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.sap.sailing.domain.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.UUID; - -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.TimePoint; -import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.domain.racelog.RaceLog; -import com.sap.sailing.domain.racelog.RaceLogEvent; -import com.sap.sailing.domain.racelog.RaceLogEventFactory; -import com.sap.sailing.domain.racelog.RaceLogFlagEvent; -import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; -import com.sap.sailing.domain.racelog.impl.RaceLogImpl; - -public class RaceLogTest { - - @Test - public void testAddingTwoEventsOfDifferentPassesButSameTimestamps() { - TimePoint t1 = MillisecondsTimePoint.now(); - int passId = 0; - RaceLog raceLog = new RaceLogImpl("RaceLogTest", "test-identifier"); - - RaceLogFlagEvent rcEvent1 = RaceLogEventFactory.INSTANCE.createFlagEvent(t1, UUID.randomUUID(), new ArrayList(), passId, Flags.CLASS, Flags.NONE, false); - RaceLogFlagEvent rcEvent2 = RaceLogEventFactory.INSTANCE.createFlagEvent(t1, UUID.randomUUID(), new ArrayList(), ++passId, Flags.CLASS, Flags.NONE, false); - - raceLog.add(rcEvent1); - raceLog.add(rcEvent2); - - raceLog.lockForRead(); - try { - assertEquals(2, Util.size(raceLog.getRawFixes())); - assertEquals(1, Util.size(raceLog.getFixes())); - } finally { - raceLog.unlockAfterRead(); - } - } - - @Test - public void testAddOfEvent() { - RaceLog rcEventTrack = new RaceLogImpl("RaceLogTest", "test-identifier"); - TimePoint t1 = MillisecondsTimePoint.now(); - int passId = 0; - boolean isDisplayed = true; - RaceLogFlagEvent rcEvent = RaceLogEventFactory.INSTANCE.createFlagEvent(t1, passId, Flags.CLASS, Flags.NONE, - isDisplayed); - rcEventTrack.add(rcEvent); - - rcEventTrack.lockForRead(); - try { - Iterator iterator = rcEventTrack.getFixes().iterator(); - int count = 0; - - do { - assertSame(iterator.next(), rcEvent); - count++; - } while (iterator.hasNext()); - - assertEquals(count, 1); - - } finally { - rcEventTrack.unlockAfterRead(); - } - } - - @Test - public void testTrackAPIForEventLogUsage() { - RaceLog rcEventTrack = new RaceLogImpl("RaceLog", "test-identifier"); - List expectedOrderingList = new ArrayList(); - TimePoint t1 = MillisecondsTimePoint.now(); - TimePoint t2 = new MillisecondsTimePoint(t1.asMillis() - 1000); - TimePoint t3 = new MillisecondsTimePoint(t1.asMillis() - 4000); - TimePoint t4 = new MillisecondsTimePoint(t1.asMillis() - 5000); - TimePoint t5 = new MillisecondsTimePoint(t1.asMillis() - 10000); - - TimePoint tx = new MillisecondsTimePoint(t1.asMillis() - 7000); - - int passId = 0; - RaceLogFlagEvent rcEvent1 = RaceLogEventFactory.INSTANCE.createFlagEvent(t1, passId, Flags.CLASS, Flags.NONE, - false); - RaceLogFlagEvent rcEvent2 = RaceLogEventFactory.INSTANCE.createFlagEvent(t2, passId, Flags.PAPA, Flags.NONE, - false); - RaceLogFlagEvent rcEvent3 = RaceLogEventFactory.INSTANCE.createFlagEvent(t3, passId, Flags.PAPA, Flags.NONE, - true); - RaceLogFlagEvent rcEvent4 = RaceLogEventFactory.INSTANCE.createFlagEvent(t4, passId, Flags.CLASS, Flags.NONE, - true); - RaceLogStartTimeEvent rcEvent5 = RaceLogEventFactory.INSTANCE.createStartTimeEvent(t5, passId, - t1); - - rcEventTrack.add(rcEvent5); - rcEventTrack.add(rcEvent3); - rcEventTrack.add(rcEvent2); - rcEventTrack.add(rcEvent1); - rcEventTrack.add(rcEvent4); - - expectedOrderingList.add(rcEvent5); - expectedOrderingList.add(rcEvent4); - expectedOrderingList.add(rcEvent3); - expectedOrderingList.add(rcEvent2); - expectedOrderingList.add(rcEvent1); - - rcEventTrack.lockForRead(); - try { - - Iterator iterator = rcEventTrack.getFixes().iterator(); - checkOrderingAndListLength(expectedOrderingList, iterator, 0, 5); - - iterator = rcEventTrack.getRawFixes().iterator(); - checkOrderingAndListLength(expectedOrderingList, iterator, 0, 5); - - RaceLogEvent event = rcEventTrack.getFirstRawFix(); - checkEquality(rcEvent5, event); - - event = rcEventTrack.getLastRawFix(); - checkEquality(rcEvent1, event); - - event = rcEventTrack.getFirstFixAfter(t3); - checkEquality(rcEvent2, event); - - event = rcEventTrack.getFirstFixAtOrAfter(t3); - checkEquality(rcEvent3, event); - - event = rcEventTrack.getFirstRawFixAfter(t2); - checkEquality(rcEvent1, event); - - event = rcEventTrack.getFirstRawFixAtOrAfter(t2); - checkEquality(rcEvent2, event); - - event = rcEventTrack.getLastFixBefore(t3); - checkEquality(rcEvent4, event); - - event = rcEventTrack.getLastFixAtOrBefore(t3); - checkEquality(rcEvent3, event); - - event = rcEventTrack.getLastRawFixBefore(t4); - checkEquality(rcEvent5, event); - - event = rcEventTrack.getLastRawFixAtOrBefore(t4); - checkEquality(rcEvent4, event); - - iterator = rcEventTrack.getFixesIterator(tx, true); - checkOrderingAndListLength(expectedOrderingList, iterator, 1, 4); - - iterator = rcEventTrack.getRawFixesIterator(tx, true); - checkOrderingAndListLength(expectedOrderingList, iterator, 1, 4); - - } finally { - rcEventTrack.unlockAfterRead(); - } - } - - private void checkOrderingAndListLength(List expectedOrderingList, Iterator iterator, - int countOffset, int expectedLength) { - int count = 0; - - while (iterator.hasNext()) { - RaceLogEvent event = iterator.next(); - assertEquals(event, expectedOrderingList.get(count + countOffset)); - count++; - } - - // for Iterables the size of the list can also be retrieved via Util.size(Iterable i) - assertEquals(count, expectedLength); - } - - private void checkEquality(RaceLogEvent knownRcEvent, RaceLogEvent trackEvent) { - if (trackEvent != null) { - assertEquals(trackEvent, knownRcEvent); - } else { - fail("Returned event was null"); - } - } -} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SpeedUnitTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SpeedUnitTest.java index d8fbaed5342..6934237ec84 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SpeedUnitTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/SpeedUnitTest.java @@ -5,6 +5,9 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.MeterPerSecondSpeedImpl; public class SpeedUnitTest { @@ -13,4 +16,30 @@ public class SpeedUnitTest { Speed s = new MeterPerSecondSpeedImpl(1852./3600.); assertEquals(1, s.getKnots(), 0.0000001); } + + @Test + public void testSpeedWithBearingAddInverse() { + SpeedWithBearing s1 = new KnotSpeedWithBearingImpl(12., new DegreeBearingImpl(0)); + SpeedWithBearing s2 = new KnotSpeedWithBearingImpl(12., new DegreeBearingImpl(180)); + SpeedWithBearing sum = s1.add(s2); + assertEquals(0.0, sum.getKnots(), 0.0000001); + } + + @Test + public void testSpeedWithBearingAddCollinear() { + SpeedWithBearing s1 = new KnotSpeedWithBearingImpl(12., new DegreeBearingImpl(123)); + SpeedWithBearing s2 = new KnotSpeedWithBearingImpl(15., new DegreeBearingImpl(123)); + SpeedWithBearing sum = s1.add(s2); + assertEquals(27.0, sum.getKnots(), 0.01); + assertEquals(123, sum.getBearing().getDegrees(), 0.0000001); + } + + @Test + public void testSpeedWithBearingAddOrthogonal() { + SpeedWithBearing s1 = new KnotSpeedWithBearingImpl(12., new DegreeBearingImpl(20)); + SpeedWithBearing s2 = new KnotSpeedWithBearingImpl(12., new DegreeBearingImpl(110)); + SpeedWithBearing sum = s1.add(s2); + assertEquals(Math.sqrt(12*12+12*12), sum.getKnots(), 0.01); + assertEquals(65., sum.getBearing().getDegrees(), 0.0000001); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java index a4b0331eb22..a5ce16333ba 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StoredTrackBasedTest.java @@ -24,6 +24,7 @@ import com.sap.sailing.domain.base.impl.DynamicPerson; import com.sap.sailing.domain.base.impl.DynamicTeam; import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; @@ -116,7 +117,7 @@ public abstract class StoredTrackBasedTest extends TrackBasedTest { for (String competitorName : getCompetitorNamesOfStoredTracks(KIELER_WOCHE)) { DynamicPerson p = new PersonImpl(competitorName, /* nationality */ null, /* dateOfBirth */ null, /* description */ null); DynamicTeam t = new TeamImpl(competitorName, Collections.singleton(p), /* coach */ null); - Competitor c = new CompetitorImpl(competitorName, competitorName, t, new BoatImpl(competitorName, + Competitor c = new CompetitorImpl(competitorName, competitorName, Color.RED, t, new BoatImpl(competitorName, new BoatClassImpl("505", /* typicallyStartsUpwind */ true), null)); DynamicGPSFixTrack track = readTrack(c, KIELER_WOCHE); if (track != null) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java index d7b4bd61320..6d666d7b039 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TackTest.java @@ -15,6 +15,7 @@ import com.sap.sailing.domain.base.impl.CompetitorImpl; import com.sap.sailing.domain.base.impl.NationalityImpl; import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; @@ -33,7 +34,7 @@ public class TackTest extends StoredTrackBasedTestWithTrackedRace { @Override @Before public void setUp() { - competitor = new CompetitorImpl(123, "Wolfgang Hunger", new TeamImpl("STG", Collections.singleton( + competitor = new CompetitorImpl(123, "Wolfgang Hunger", Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl("Wolfgang Hunger", new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous Wolfgang Hunger")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index 5582374fd17..1be4f9ad15e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -26,6 +26,7 @@ import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSourceType; @@ -57,7 +58,7 @@ public abstract class TrackBasedTest { } public static CompetitorImpl createCompetitor(String competitorName) { - return new CompetitorImpl(123, competitorName, new TeamImpl("STG", Collections.singleton( + return new CompetitorImpl(123, competitorName, Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl(competitorName, new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous " + competitorName)), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java index 860e6ad66ce..123ab6d83d1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java @@ -49,13 +49,12 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.impl.DistanceCache; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; -import com.sap.sailing.domain.tracking.impl.DynamicTrackImpl; import com.sap.sailing.domain.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.tracking.impl.TrackImpl; public class TrackTest { - private DynamicTrackImpl track; + private DynamicGPSFixTrack track; private GPSFixMovingImpl gpsFix1; private GPSFixMovingImpl gpsFix2; private GPSFixMovingImpl gpsFix3; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index a5565e97586..5b9df842d7d 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -15,10 +15,10 @@ import java.util.Set; import org.junit.Test; import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Mark; 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.Mark; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.impl.BoatClassImpl; @@ -30,6 +30,7 @@ import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; @@ -255,7 +256,7 @@ public class WindTest { Waypoint w1 = domainFactory.createWaypoint(startFinish, /*passingSide*/ null); Waypoint w2 = domainFactory.createWaypoint(top, /*passingSide*/ null); Waypoint w3 = domainFactory.createWaypoint(startFinish, /*passingSide*/ null); - Competitor competitor = new CompetitorImpl(123, "Test Competitor", new TeamImpl("STG", Collections.singleton( + Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, new TeamImpl("STG", Collections.singleton( new PersonImpl("Test Competitor", new NationalityImpl("GER"), /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 359246e2df3..266e1308476 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -18,6 +18,7 @@ import com.sap.sailing.domain.base.RegattaListener; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Position; @@ -430,6 +431,18 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub return null; } + + @Override + public RegattaConfiguration getRegattaConfiguration() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setRegattaConfiguration(RegattaConfiguration configuration) { + // TODO Auto-generated method stub + + } }; } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapterFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapterFactory.java index e1d6af938e0..4adadbf6742 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapterFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapterFactory.java @@ -1,7 +1,13 @@ package com.sap.sailing.domain.tractracadapter; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.tractracadapter.impl.TracTracAdapterFactoryImpl; public interface TracTracAdapterFactory { + /** + * A default instance to be used only for testing! + */ + TracTracAdapterFactory INSTANCE = new TracTracAdapterFactoryImpl(); + TracTracAdapter getOrCreateTracTracAdapter(DomainFactory baseDomainFactory); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index ec3b721af78..81ea6edfac3 100644 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -21,6 +21,7 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CompetitorStore; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.Course; import com.sap.sailing.domain.base.Mark; @@ -235,8 +236,9 @@ public class DomainFactoryImpl implements DomainFactory { @Override public Competitor getOrCreateCompetitor(final UUID competitorId, final String competitorClassName, final String nationalityAsString, final String name, final String shortName) { - Competitor result = baseDomainFactory.getExistingCompetitorById(competitorId); - if (result == null || baseDomainFactory.isCompetitorToUpdateDuringGetOrCreate(result)) { + CompetitorStore competitorStore = baseDomainFactory.getCompetitorStore(); + Competitor result = competitorStore.getExistingCompetitorById(competitorId); + if (result == null || competitorStore.isCompetitorToUpdateDuringGetOrCreate(result)) { BoatClass boatClass = getOrCreateBoatClass(competitorClassName); Nationality nationality; try { @@ -248,7 +250,7 @@ public class DomainFactoryImpl implements DomainFactory { } DynamicTeam team = getOrCreateTeam(name, nationality, competitorId); DynamicBoat boat = new BoatImpl(shortName, boatClass, shortName); - result = baseDomainFactory.getOrCreateCompetitor(competitorId, name, team, boat); + result = competitorStore.getOrCreateCompetitor(competitorId, name, null /*displayColor*/, team, boat); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java index 8e6f0b3d87c..2cfd6d10a98 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/DomainFactory.java @@ -2,13 +2,9 @@ package com.sap.sailing.domain.base; import java.io.IOException; import java.io.InputStream; -import java.io.Serializable; import java.util.List; -import java.util.UUID; 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; @@ -31,62 +27,6 @@ public interface DomainFactory extends SharedDomainFactory { */ static DomainFactory INSTANCE = new DomainFactoryImpl(); - /** - * Looks up or, if not found, creates a {@link Nationality} object and re-uses threeLetterIOCCode also as the - * nationality's name. - */ - Nationality getOrCreateNationality(String threeLetterIOCCode); - - /** - * The name will also be used as the mark's ID. If you have a unique ID, use {@link #getOrCreateMark(Serializable, String)} instead. - */ - Mark getOrCreateMark(String name); - - /** - * Since some ID types, such as {@link UUID}, cannot be serialized as objects to a GWT client, only the - * {@link Object#toString()} representations of those IDs are serialized to the clients. When a client then requests - * to identify a mark again, only the ID's string representation will be submitted to the server and now needs to be - * mapped to the actual ID. This domain factory keeps a mapping of all mark ID's string representations to the - * actual ID for all marks ever managed through any of the getOrCreateMark(...) overloads. - *

- * - * This method first looks up the actual ID whose string representation is toStringRepresentationOfID - * and then calls {@link #getOrCreateMark(Serializable, String)} with the result and the name - * parameter, or with ToStringRepresentationOfID and name in case the string - * representation of the ID is not known. So in the latter case, the string is used as the ID for the new mark. - */ - Mark getOrCreateMark(String toStringRepresentationOfID, String name); - - Mark getOrCreateMark(Serializable id, String name); - - /** - * If the single mark with ID id already exists, it is returned. Its color may differ from color - * in that case. Otherwise, a new {@link Mark} is created with color as its {@link Mark#getColor()} - * and shape as its {@link Mark#getShape()}. - */ - Mark getOrCreateMark(Serializable id, String name, MarkType type, String color, String shape, String pattern); - - /** - * @param name also uses the name as the gate's ID; if you have a real ID, use {@link #createGate(Serializable, Mark, Mark, String)} instead - */ - Gate createGate(Mark left, Mark right, String name); - - Gate createGate(Serializable id, Mark left, Mark right, String name); - - /** - * The waypoint created is weakly cached so that when requested again by - * {@link #getExistingWaypointById(Waypoint)} it is found. - */ - Waypoint createWaypoint(ControlPoint controlPoint, NauticalSide passingSide); - - Waypoint getExistingWaypointById(Waypoint waypointPrototype); - - /** - * Atomically checks if a waypoint by an equal {@link Waypoint#getId()} as waypoint exists in this domain factory's - * waypoint cache. If so, the cached waypoint is returned. Otherwise, waypoint is added to the cache and returned. - */ - Waypoint getExistingWaypointByIdOrCache(Waypoint waypoint); - MarkPassing createMarkPassing(TimePoint timePoint, Waypoint waypoint, Competitor competitor); /** @@ -118,6 +58,4 @@ public interface DomainFactory extends SharedDomainFactory { */ void updateRaceDTOWithTrackedRaceData(TrackedRace trackedRace, RaceDTO raceDTO); - CompetitorStore getCompetitorStore(); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index e830214bc29..969487f43ad 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.base; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.Named; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -30,6 +31,17 @@ public interface Regatta extends Named, WithID { * @param newCourseArea {@link CourseArea} to be set. */ void setDefaultCourseArea(CourseArea newCourseArea); + + /** + * Gets the {@link RegattaConfiguration} associated with this {@link Regatta}'s races. + */ + RegattaConfiguration getRegattaConfiguration(); + + /** + * Sets the {@link RegattaConfiguration} associated with this {@link Regatta}'s races. + * @param configuration + */ + void setRegattaConfiguration(RegattaConfiguration configuration); /** * A regatta consists of one or more series. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java index 1a03d0e7ee2..770abfb6306 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/BearingWithConfidenceImpl.java @@ -3,7 +3,6 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.impl.RadianBearingImpl; import com.sap.sailing.domain.confidence.IsScalable; import com.sap.sailing.domain.confidence.ScalableValue; @@ -19,58 +18,4 @@ implements BearingWithConfidence, IsScalable { public ScalableValue getScalableValue() { return new ScalableBearing(getObject()); } - - private static class ScalableBearing implements ScalableValue { - private final double sin; - private final double cos; - - public ScalableBearing(Bearing bearing) { - this.sin = Math.sin(bearing.getRadians()); - this.cos = Math.cos(bearing.getRadians()); - } - - private ScalableBearing(double sin, double cos) { - this.sin = sin; - this.cos = cos; - } - - @Override - public ScalableValue multiply(double factor) { - DoublePair pair = getValue(); - return new ScalableBearing(factor*pair.getA(), factor*pair.getB()); - } - - @Override - public ScalableValue add(ScalableValue t) { - DoublePair value = getValue(); - DoublePair tValue = t.getValue(); - return new ScalableBearing(value.getA()+tValue.getA(), value.getB()+tValue.getB()); - } - - /** - * If the combined confidence was 0.0, no {@link Bearing} object can reasonably be computed; hence, null - * is returned in such cases. - */ - @Override - public Bearing divide(double divisor) { - Bearing result; - if (sin == 0 && cos == 0) { - result = null; - } else { - double angle; - if (cos == 0) { - angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2; - } else { - angle = Math.atan2(sin, cos); - } - result = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); - } - return result; - } - - @Override - public DoublePair getValue() { - return new DoublePair(sin, cos); - } - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index 967dd1889ce..c6490602736 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -2,43 +2,24 @@ package com.sap.sailing.domain.base.impl; import java.io.IOException; import java.io.InputStream; -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.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; -import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CompetitorStore; -import com.sap.sailing.domain.base.ControlPoint; -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.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.Waypoint; 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.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.PlacemarkDTO; @@ -64,204 +45,18 @@ 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 { +public class DomainFactoryImpl extends SharedDomainFactoryImpl implements DomainFactory { private static Logger logger = Logger.getLogger(DomainFactoryImpl.class.getName()); - /** - * Ensure that the same string is used as key that is also used to set the {@link Nationality} - * object's {@link Nationality#getThreeLetterIOCAcronym() IOC code}. - */ - private final Map nationalityCache; - - private final Map markCache; - - /** - * For all marks ever created by this factory, the mark {@link WithID#getId() ID}'s string representation - * is mapped here to the actual ID. This allows clients to send only the string representation to the server - * and still be able to identify a mark uniquely this way. - */ - private final Map markIdCache; - - private final Map boatClassCache; - - private final CompetitorStore competitorStore; - - private final Map courseAreaCache; - - /** - * Weakly references the waypoints. If a waypoint is no longer strongly referenced, the corresponding reference contained - * as value will have its referred object be null. In this case, the methods reading from this cache will purge - * the record and behave as if the record hadn't existed at the time of the read operation. - */ - private final ConcurrentHashMap waypointCache; - - private final ReferenceQueue waypointCacheReferenceQueue; - - /** - * Weak references to {@link Waypoint} objects of this type are registered with - * {@link DomainFactoryImpl#waypointCacheReferenceQueue} upon construction so that when their referents are no - * longer strongly referenced and the reference was nulled, they are entered into that queue. - * Methods managing the {@link #waypointCache} can poll the queue and then remove cache entries based on - * the {@link #id} stored in the reference. - * - * @author Axel Uhl (D043530) - * - */ - private class WeakWaypointReference extends WeakReference { - private final Serializable id; - - public WeakWaypointReference(Waypoint waypoint) { - super(waypoint, waypointCacheReferenceQueue); - this.id = waypoint.getId(); - } - - public void removeCacheEntry() { - waypointCache.remove(id); - } - } - - private final Set mayStartWithNoUpwindLeg; - /** * Uses a transient competitor store */ public DomainFactoryImpl() { - this(new TransientCompetitorStoreImpl()); + super(new TransientCompetitorStoreImpl()); } public DomainFactoryImpl(CompetitorStore competitorStore) { - waypointCacheReferenceQueue = new ReferenceQueue(); - nationalityCache = new HashMap(); - markCache = new HashMap(); - markIdCache = new HashMap<>(); - boatClassCache = new HashMap(); - this.competitorStore = competitorStore; - waypointCache = new ConcurrentHashMap(); - mayStartWithNoUpwindLeg = new HashSet(Arrays.asList(new String[] { "extreme40", "ess", "ess40" })); - courseAreaCache = new HashMap<>(); - } - - @Override - public Nationality getOrCreateNationality(String threeLetterIOCCode) { - synchronized (nationalityCache) { - Nationality result = nationalityCache.get(threeLetterIOCCode); - if (result == null) { - result = new NationalityImpl(threeLetterIOCCode); - nationalityCache.put(threeLetterIOCCode, result); - } - return result; - } - } - - @Override - public Mark getOrCreateMark(String name) { - return getOrCreateMark(name, name); - } - - @Override - public Mark getOrCreateMark(Serializable id, String name) { - Mark result = markCache.get(id); - if (result == null) { - result = new MarkImpl(id, name); - cacheMark(id, result); - } - return result; - } - - @Override - public Mark getOrCreateMark(String toStringRepresentationOfID, String name) { - final Mark result; - if (markIdCache.containsKey(toStringRepresentationOfID)) { - Serializable id = markIdCache.get(toStringRepresentationOfID); - result = getOrCreateMark(id, name); - } else { - result = new MarkImpl(toStringRepresentationOfID, name); - cacheMark(toStringRepresentationOfID, result); - } - return result; - } - - private void cacheMark(Serializable id, Mark result) { - markCache.put(id, result); - markIdCache.put(id.toString(), id); - } - - - - @Override - public Mark getOrCreateMark(Serializable id, String name, MarkType type, String color, String shape, String pattern) { - Mark result = markCache.get(id); - if (result == null) { - result = new MarkImpl(id, name, type, color, shape, pattern); - cacheMark(id, result); - } - return result; - } - - @Override - public Gate createGate(Mark left, Mark right, String name) { - return new GateImpl(left, right, name); - } - - @Override - public Gate createGate(Serializable id, Mark left, Mark right, String name) { - return new GateImpl(id, left, right, name); - } - - @Override - public Waypoint createWaypoint(ControlPoint controlPoint, NauticalSide passingSide) { - synchronized (waypointCache) { - expungeStaleWaypointCacheEntries(); - Waypoint result = new WaypointImpl(controlPoint, passingSide); - waypointCache.put(result.getId(), new WeakWaypointReference(result)); - return result; - } - } - - @Override - public Waypoint getExistingWaypointById(Waypoint waypointPrototype) { - synchronized (waypointCache) { - expungeStaleWaypointCacheEntries(); - Waypoint result = null; - Reference ref = waypointCache.get(waypointPrototype.getId()); - if (ref != null) { - result = ref.get(); - if (result == null) { - // waypoint was finalized; remove entry from cache - waypointCache.remove(waypointPrototype.getId()); - } - } - return result; - } - } - - @Override - public Waypoint getExistingWaypointByIdOrCache(Waypoint waypoint) { - synchronized (waypointCache) { - expungeStaleWaypointCacheEntries(); - Waypoint result = null; - Reference ref = waypointCache.get(waypoint.getId()); - if (ref != null) { - result = ref.get(); - if (result == null) { - // waypoint was finalized; remove entry from cache and add anew - result = waypoint; - waypointCache.put(waypoint.getId(), new WeakWaypointReference(waypoint)); - } // else, result is the waypoint found in the cache; return it - } else { - // No entry found in the cache; not even a stale, finalized one. Create a new entry: - result = waypoint; - waypointCache.put(waypoint.getId(), new WeakWaypointReference(waypoint)); - } - return result; - } - } - - private void expungeStaleWaypointCacheEntries() { - Reference ref; - while ((ref=waypointCacheReferenceQueue.poll()) != null) { - ((WeakWaypointReference) ref).removeCacheEntry(); - } + super(competitorStore); } @Override @@ -269,43 +64,6 @@ public class DomainFactoryImpl implements DomainFactory { return new MarkPassingImpl(timePoint, waypoint, competitor); } - @Override - public BoatClass getOrCreateBoatClass(String name, boolean typicallyStartsUpwind) { - synchronized (boatClassCache) { - BoatClass result = boatClassCache.get(name); - if (result == null) { - result = new BoatClassImpl(name, typicallyStartsUpwind); - boatClassCache.put(name, result); - } - return result; - } - } - - @Override - public BoatClass getOrCreateBoatClass(String name) { - return getOrCreateBoatClass(name, /* typicallyStartsUpwind */!mayStartWithNoUpwindLeg.contains(name.toLowerCase())); - } - - @Override - public Competitor getExistingCompetitorById(Serializable competitorId) { - return competitorStore.getExistingCompetitorById(competitorId); - } - - @Override - public boolean isCompetitorToUpdateDuringGetOrCreate(Competitor competitor) { - return competitorStore.isCompetitorToUpdateDuringGetOrCreate(competitor); - } - - @Override - public synchronized Competitor getOrCreateCompetitor(Serializable competitorId, String name, DynamicTeam team, DynamicBoat boat) { - return competitorStore.getOrCreateCompetitor(competitorId, name, team, boat); - } - - @Override - public DynamicCompetitor getOrCreateDynamicCompetitor(UUID fromString, String name, DynamicTeam team, DynamicBoat boat) { - return (DynamicCompetitor) getOrCreateCompetitor(fromString, name, team, boat); - } - @Override public ObjectInputStreamResolvingAgainstDomainFactory createObjectInputStreamResolvingAgainstThisFactory(InputStream inputStream) throws IOException { return new ObjectInputStreamResolvingAgainstDomainFactoryImpl(inputStream, this); @@ -463,24 +221,4 @@ public class DomainFactoryImpl implements DomainFactory { return result; } - @Override - public CourseArea getOrCreateCourseArea(Serializable courseAreaId, String name) { - CourseArea result = getExistingCourseAreaById(courseAreaId); - if (result == null) { - result = new CourseAreaImpl(name, courseAreaId); - courseAreaCache.put(courseAreaId, result); - } - return result; - } - - @Override - public CourseArea getExistingCourseAreaById(Serializable courseAreaId) { - return courseAreaCache.get(courseAreaId); - } - - @Override - public CompetitorStore getCompetitorStore() { - return competitorStore; - } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java index 3ddf963589a..5c814750cdd 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DynamicCompetitorStore.java @@ -3,13 +3,14 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; import com.sap.sailing.domain.base.CompetitorStore; +import com.sap.sailing.domain.common.Color; public interface DynamicCompetitorStore extends CompetitorStore { @Override DynamicCompetitor getExistingCompetitorById(Serializable competitorId); @Override - DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, DynamicTeam team, DynamicBoat boat); + DynamicCompetitor getOrCreateCompetitor(Serializable competitorId, String name, Color displayColor, DynamicTeam team, DynamicBoat boat); @Override Iterable getCompetitors(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KilometersPerHourSpeedWithBearingImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KilometersPerHourSpeedWithBearingImpl.java index 53ba376f3cf..1cb58678dd7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KilometersPerHourSpeedWithBearingImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/KilometersPerHourSpeedWithBearingImpl.java @@ -29,6 +29,11 @@ public class KilometersPerHourSpeedWithBearingImpl extends KilometersPerHourSpee return pos.translateGreatCircle(getBearing(), this.travel(from, to)); } + @Override + public SpeedWithBearing add(SpeedWithBearing other) { + return AbstractSpeedWithBearingImpl.add(this, other); + } + @Override public SpeedWithBearing applyCourseChange(CourseChange courseChange) { return AbstractSpeedWithBearingImpl.applyCourseChange(this, courseChange); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index a25da8d7ff6..c3df16634f1 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -19,6 +19,7 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.RegattaListener; import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -50,8 +51,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene private final Serializable id; private transient RaceLogStore raceLogStore; - // Can be changed... private CourseArea defaultCourseArea; + private RegattaConfiguration configuration; /** * Regattas may be constructed as implicit default regattas in which case they won't need to be stored @@ -108,7 +109,8 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene } this.persistent = persistent; this.scoringScheme = scoringScheme; - this.defaultCourseArea = courseArea; + this.defaultCourseArea = courseArea; + this.configuration = null; } private void registerRaceLogsOnRaceColumns(Series series) { @@ -356,6 +358,16 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene public void setDefaultCourseArea(CourseArea newCourseArea) { this.defaultCourseArea = newCourseArea; } + + @Override + public RegattaConfiguration getRegattaConfiguration() { + return configuration; + } + + @Override + public void setRegattaConfiguration(RegattaConfiguration configuration) { + this.configuration = configuration; + } /** * @return whether this regatta defines its local per-series result discarding rules; if so, any leaderboard based diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java new file mode 100755 index 00000000000..5fb345832fc --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableBearing.java @@ -0,0 +1,60 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.base.BearingWithConfidence.DoublePair; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.impl.RadianBearingImpl; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class ScalableBearing implements ScalableValue { + private final double sin; + private final double cos; + + public ScalableBearing(Bearing bearing) { + this.sin = Math.sin(bearing.getRadians()); + this.cos = Math.cos(bearing.getRadians()); + } + + private ScalableBearing(double sin, double cos) { + this.sin = sin; + this.cos = cos; + } + + @Override + public ScalableValue multiply(double factor) { + DoublePair pair = getValue(); + return new ScalableBearing(factor*pair.getA(), factor*pair.getB()); + } + + @Override + public ScalableValue add(ScalableValue t) { + DoublePair value = getValue(); + DoublePair tValue = t.getValue(); + return new ScalableBearing(value.getA()+tValue.getA(), value.getB()+tValue.getB()); + } + + /** + * If the combined confidence was 0.0, no {@link Bearing} object can reasonably be computed; hence, null + * is returned in such cases. + */ + @Override + public Bearing divide(double divisor) { + Bearing result; + if (sin == 0 && cos == 0) { + result = null; + } else { + double angle; + if (cos == 0) { + angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2; + } else { + angle = Math.atan2(sin, cos); + } + result = new RadianBearingImpl(angle < 0 ? angle + 2 * Math.PI : angle); + } + return result; + } + + @Override + public DoublePair getValue() { + return new DoublePair(sin, cos); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java new file mode 100755 index 00000000000..9093d34f8f8 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/ScalableSpeed.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.base.impl; + +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.impl.KnotSpeedImpl; +import com.sap.sailing.domain.confidence.ScalableValue; + +public class ScalableSpeed implements ScalableValue { + private final double knots; + + public ScalableSpeed(Speed speed) { + this.knots = speed.getKnots(); + } + + private ScalableSpeed(double knots) { + this.knots = knots; + } + + @Override + public ScalableValue multiply(double factor) { + return new ScalableSpeed(factor*knots); + } + + @Override + public ScalableValue add(ScalableValue t) { + return new ScalableSpeed(knots+t.getValue()); + } + + @Override + public Speed divide(double divisor) { + return new KnotSpeedImpl(knots / divisor); + } + + @Override + public Double getValue() { + return knots; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java index f12d23d9096..1e4d7815607 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SpeedWithConfidenceImpl.java @@ -2,7 +2,6 @@ package com.sap.sailing.domain.base.impl; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.confidence.ScalableValue; public class SpeedWithConfidenceImpl extends HasConfidenceImpl implements SpeedWithConfidence { @@ -16,36 +15,4 @@ public class SpeedWithConfidenceImpl extends HasConfidenceImpl getScalableValue() { return new ScalableSpeed(getObject()); } - - private static class ScalableSpeed implements ScalableValue { - private final double knots; - - public ScalableSpeed(Speed speed) { - this.knots = speed.getKnots(); - } - - private ScalableSpeed(double knots) { - this.knots = knots; - } - - @Override - public ScalableValue multiply(double factor) { - return new ScalableSpeed(factor*knots); - } - - @Override - public ScalableValue add(ScalableValue t) { - return new ScalableSpeed(knots+t.getValue()); - } - - @Override - public Speed divide(double divisor) { - return new KnotSpeedImpl(knots / divisor); - } - - @Override - public Double getValue() { - return knots; - } - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/RegattaMasterData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/RegattaMasterData.java index d571a15d575..50887bc34e7 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/RegattaMasterData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/RegattaMasterData.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.masterdataimport; import java.io.Serializable; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; public class RegattaMasterData { private Serializable id; @@ -10,13 +11,14 @@ public class RegattaMasterData { private String boatClassName; private String scoringSchemeType; private Iterable series; + private final RegattaConfiguration configuration; private boolean isPersistent; private String regattaName; private Iterable raceIdsAsStrings; - public RegattaMasterData(Serializable id, String baseName, String defaultCourseAreaId, String boatClassName, String scoringSchemeType, Iterable series, boolean isPersistent, String regattaName, - Iterable raceIdsAsStrings) { + Iterable raceIdsAsStrings, + RegattaConfiguration configuration) { this.id = id; this.baseName = baseName; this.defaultCourseAreaId = defaultCourseAreaId; @@ -26,6 +28,7 @@ public class RegattaMasterData { this.isPersistent = isPersistent; this.regattaName = regattaName; this.raceIdsAsStrings = raceIdsAsStrings; + this.configuration = configuration; } public Iterable getRaceIdsAsStrings() { @@ -43,6 +46,10 @@ public class RegattaMasterData { public String getDefaultCourseAreaId() { return defaultCourseAreaId; } + + public RegattaConfiguration getRegattaConfiguration() { + return configuration; + } public String getBoatClassName() { return boatClassName; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicGPSFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicGPSFixTrack.java index 9655846a169..94ab8b1a769 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicGPSFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicGPSFixTrack.java @@ -10,8 +10,7 @@ import java.util.ConcurrentModificationException; * * @author Axel Uhl (d043530) */ -public interface DynamicGPSFixTrack extends - GPSFixTrack { +public interface DynamicGPSFixTrack extends GPSFixTrack, DynamicTrack { /** * Adds the gpsFix (or an object equal to it) to this track. Note: depending on the implementation, * it's not the same object actually added to the track but only an equal one. This is particularly diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrack.java new file mode 100755 index 00000000000..5cb5ee68c15 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrack.java @@ -0,0 +1,10 @@ +package com.sap.sailing.domain.tracking; + +import com.sap.sailing.domain.base.Timed; + +public interface DynamicTrack extends Track { + /** + * @return true if the element was added, false otherwise. + */ + boolean add(FixType fix); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java index bf61d4bbfce..4a5c0fb1550 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/WindTrack.java @@ -4,11 +4,9 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.impl.Util.Pair; -public interface WindTrack extends Track { +public interface WindTrack extends DynamicTrack { static final long DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND = 30000; - void add(Wind wind); - /** * Estimates a wind force and direction based on tracked wind data.

* diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixMovingImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixMovingImpl.java index bc46b9e5297..4b0a9c58173 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixMovingImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CompactGPSFixMovingImpl.java @@ -54,6 +54,11 @@ public class CompactGPSFixMovingImpl extends CompactGPSFixImpl implements GPSFix return AbstractSpeedWithBearingImpl.getCourseChangeRequiredToReach(getSpeed(), targetSpeedWithBearing); } + @Override + public SpeedWithBearing add(SpeedWithBearing other) { + return AbstractSpeedWithAbstractBearingImpl.add(this, other); + } + @Override public String toString() { return super.toString()+" to "+getBearing().getDegrees()+"°"; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java index bc7fcb07c6c..1986dd1d884 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CrossTrackErrorCache.java @@ -98,8 +98,8 @@ public class CrossTrackErrorCache extends AbstractRaceChangeListener { } } - public void add(CrossTrackErrorSumAndNumberOfFixes entry) { - getInternalRawFixes().add(entry); + public boolean add(CrossTrackErrorSumAndNumberOfFixes entry) { + return getInternalRawFixes().add(entry); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java index 3c17217043d..a8dbb98f9a4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DummyMarkPassingWithTimePointOnly.java @@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.IsManagedBySharedDomainFactory; import com.sap.sailing.domain.base.SharedDomainFactory; import com.sap.sailing.domain.base.Team; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.tracking.MarkPassing; @@ -57,6 +58,11 @@ public class DummyMarkPassingWithTimePointOnly implements MarkPassing { public IsManagedBySharedDomainFactory resolve(SharedDomainFactory domainFactory) { return this; } + + @Override + public Color getColor() { + return null; + } }; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java index 747c62afe26..822e04f9f75 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicGPSFixMovingTrackImpl.java @@ -19,9 +19,10 @@ import com.sap.sailing.domain.confidence.ConfidenceBasedAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; import com.sap.sailing.domain.confidence.HasConfidence; import com.sap.sailing.domain.confidence.Weigher; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixMoving; -public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl { +public class DynamicGPSFixMovingTrackImpl extends GPSFixTrackImpl implements DynamicGPSFixTrack{ private static final long serialVersionUID = 9111448573301259784L; private static final double MAX_SPEED_FACTOR_COMPARED_TO_MEASURED_SPEED_FOR_FILTERING = 2; @@ -42,7 +43,12 @@ public class DynamicGPSFixMovingTrackImpl extends DynamicTrackImpl extends DynamicTrackImpl extends DynamicTrackImpl { +public class DynamicGPSFixTrackImpl extends GPSFixTrackImpl implements DynamicGPSFixTrack { private static final long serialVersionUID = 4035953954507697564L; public DynamicGPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { super(trackedItem, millisecondsOverWhichToAverage); } - @Override public void addGPSFix(GPSFix gpsFix) { - super.addGPSFix(new CompactGPSFixImpl(gpsFix)); + add(gpsFix); + } + + @Override + public boolean add(GPSFix fix) { + return super.add(new CompactGPSFixImpl(fix)); + } + + @Override + public void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { + super.setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverage); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java index 226cb5ac576..91592e62e1d 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackImpl.java @@ -1,62 +1,21 @@ package com.sap.sailing.domain.tracking.impl; -import java.util.logging.Level; -import java.util.logging.Logger; +import com.sap.sailing.domain.base.Timed; +import com.sap.sailing.domain.tracking.DynamicTrack; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSTrackListener; - -public class DynamicTrackImpl extends - GPSFixTrackImpl implements DynamicGPSFixTrack { +public class DynamicTrackImpl extends TrackImpl implements DynamicTrack { private static final long serialVersionUID = 917778209274148097L; - private static final Logger logger = Logger.getLogger(DynamicTrackImpl.class.getName()); - public DynamicTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { - super(trackedItem, millisecondsOverWhichToAverage); - } - - public DynamicTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage, Speed maxSpeedForSmoothening) { - super(trackedItem, millisecondsOverWhichToAverage, maxSpeedForSmoothening); + public DynamicTrackImpl(String nameForReadWriteLock) { + super(nameForReadWriteLock); } + /** + * Simply makes the superclass method public as now we need to expose it via the {@link DynamicTrack} interface. + */ @Override - public void addGPSFix(FixType gpsFix) { - lockForWrite(); - try { - getInternalRawFixes().add(gpsFix); - invalidateValidityAndDistanceCaches(gpsFix); - } finally { - unlockAfterWrite(); - } - if (logger.isLoggable(Level.FINEST)) { - FixType last; - lockForRead(); - try { - logger.finest("GPS fix "+gpsFix+" for "+getTrackedItem()+", isValid="+isValid(getInternalRawFixes(), gpsFix)+ - ", time/distance/speed from last: "+ - ((last=getInternalRawFixes().lower(gpsFix))==null - ? "null" - : (gpsFix.getTimePoint().asMillis()-last.getTimePoint().asMillis()+"ms/"+ - gpsFix.getPosition().getDistance(last.getPosition())) + "/"+ - gpsFix.getPosition().getDistance(last.getPosition()).inTime(gpsFix.getTimePoint().asMillis()-last.getTimePoint().asMillis()))); - } finally { - unlockAfterRead(); - } - } - for (GPSTrackListener listener : getListeners()) { - listener.gpsFixReceived(gpsFix, getTrackedItem()); - } + public boolean add(FixType fix) { + return super.add(fix); } - @Override - public void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { - long oldMillis = getMillisecondsOverWhichToAverage(); - super.setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverage); - for (GPSTrackListener listener : getListeners()) { - listener.speedAveragingChanged(oldMillis, millisecondsOverWhichToAverage); - } - } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java index 0076d90e540..d36592b10cf 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -8,28 +8,19 @@ import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.racelog.RaceLog; -import com.sap.sailing.domain.racelog.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.racelog.RaceLogCourseDesignChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogEventVisitor; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningConfirmedEvent; -import com.sap.sailing.domain.racelog.RaceLogFinishPositioningListChangedEvent; -import com.sap.sailing.domain.racelog.RaceLogFlagEvent; -import com.sap.sailing.domain.racelog.RaceLogGateLineOpeningTimeEvent; import com.sap.sailing.domain.racelog.RaceLogPassChangeEvent; -import com.sap.sailing.domain.racelog.RaceLogPathfinderEvent; -import com.sap.sailing.domain.racelog.RaceLogProtestStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogRaceStatusEvent; -import com.sap.sailing.domain.racelog.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.racelog.RaceLogStartTimeEvent; import com.sap.sailing.domain.racelog.RaceLogWindFixEvent; import com.sap.sailing.domain.racelog.analyzing.impl.LastPublishedCourseDesignFinder; -import com.sap.sailing.domain.racelog.analyzing.impl.RaceStatusAnalyzer; import com.sap.sailing.domain.racelog.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.racelog.analyzing.impl.WindFixesFinder; +import com.sap.sailing.domain.racelog.impl.BaseRaceLogEventVisitor; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.Wind; -public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { +public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { private static final Logger logger = Logger.getLogger(DynamicTrackedRaceLogListener.class.getName()); @@ -37,7 +28,6 @@ public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { private final WindSource raceCommitteeWindSource; - private RaceStatusAnalyzer statusAnalyzer; private LastPublishedCourseDesignFinder courseDesignFinder; private StartTimeFinder startTimeFinder; @@ -51,7 +41,6 @@ public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { trackedRace.invalidateStartTime(); trackedRace.invalidateEndTime(); courseDesignFinder = new LastPublishedCourseDesignFinder(raceLog); - statusAnalyzer = new RaceStatusAnalyzer(raceLog); startTimeFinder = new StartTimeFinder(raceLog); initializeWindTrack(raceLog); analyze(); @@ -99,7 +88,7 @@ public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { } private void analyzeStatus() { - /* RaceLogRaceStatus newStatus = */statusAnalyzer.analyze(); + /* RaceLogRaceStatus newStatus = statusAnalyzer.analyze();*/ // TODO: What can we do with the status? Should we use DynamicTrackedRace.setStatus? } @@ -159,46 +148,6 @@ public class DynamicTrackedRaceLogListener implements RaceLogEventVisitor { analyzeCourseDesign(event.getCourseDesign()); } - @Override - public void visit(RaceLogFlagEvent event) { - - } - - @Override - public void visit(RaceLogCourseAreaChangedEvent event) { - - } - - @Override - public void visit(RaceLogFinishPositioningListChangedEvent event) { - // score correction is handled by the leaderboard - } - - @Override - public void visit(RaceLogFinishPositioningConfirmedEvent event) { - // score correction is handled by the leaderboard - } - - @Override - public void visit(RaceLogPathfinderEvent event) { - - } - - @Override - public void visit(RaceLogGateLineOpeningTimeEvent event) { - - } - - @Override - public void visit(RaceLogStartProcedureChangedEvent event) { - - } - - @Override - public void visit(RaceLogProtestStartTimeEvent event) { - - } - @Override public void visit(RaceLogWindFixEvent event) { // add the wind fix to the race committee WindTrack diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 1a42f540cb1..9f8e9f25cbf 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -14,6 +14,8 @@ import java.util.LinkedList; import java.util.List; import java.util.NavigableSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import com.sap.sailing.domain.base.BearingWithConfidence; import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; @@ -45,6 +47,7 @@ import com.sap.sailing.domain.tracking.WithValidityCache; import com.sap.sailing.util.impl.ArrayListNavigableSet; public class GPSFixTrackImpl extends TrackImpl implements GPSFixTrack { + private static final Logger logger = Logger.getLogger(GPSFixTrackImpl.class.getName()); private static final long serialVersionUID = -7282869695818293745L; private static final Speed DEFAULT_MAX_SPEED_FOR_SMOOTHING = new KnotSpeedImpl(50); protected final Speed maxSpeedForSmoothing; @@ -246,10 +249,6 @@ public class GPSFixTrackImpl extends TrackImpl return result; } - protected void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { - this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; - } - @Override public ItemType getTrackedItem() { return trackedItem; @@ -1005,4 +1004,44 @@ public class GPSFixTrackImpl extends TrackImpl return distanceCache; } + @Override + protected boolean add(FixType fix) { + final boolean result; + lockForWrite(); + try { + result = super.add(fix); + invalidateValidityAndDistanceCaches(fix); + } finally { + unlockAfterWrite(); + } + if (logger.isLoggable(Level.FINEST)) { + FixType last; + try { + lockForRead(); + if (logger.isLoggable(Level.FINEST)) { + logger.finest("GPS fix "+fix+" for "+getTrackedItem()+", isValid="+isValid(getInternalRawFixes(), fix)+ + ", time/distance/speed from last: "+ + ((last=getInternalRawFixes().lower(fix))==null + ? "null" + : (fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()+"ms/"+ + fix.getPosition().getDistance(last.getPosition())) + "/"+ + fix.getPosition().getDistance(last.getPosition()).inTime(fix.getTimePoint().asMillis()-last.getTimePoint().asMillis()))); + } + } finally { + unlockAfterRead(); + } + } + for (GPSTrackListener listener : getListeners()) { + listener.gpsFixReceived(fix, getTrackedItem()); + } + return result; + } + + protected void setMillisecondsOverWhichToAverage(long millisecondsOverWhichToAverage) { + long oldMillis = getMillisecondsOverWhichToAverage(); + this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; + for (GPSTrackListener listener : getListeners()) { + listener.speedAveragingChanged(oldMillis, millisecondsOverWhichToAverage); + } + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java index 7ef555ae350..5aa0d2c8379 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WindTrackImpl.java @@ -41,7 +41,7 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { private final static Logger logger = Logger.getLogger(WindTrackImpl.class.getName()); - private final static double DEFAULT_BASE_CONFIDENCE = 0.9; + protected final static double DEFAULT_BASE_CONFIDENCE = 0.9; private final double baseConfidence; @@ -97,15 +97,11 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { } @Override - public void add(Wind wind) { - CompactWindImpl compactWind = new CompactWindImpl(wind); - lockForWrite(); - try { - getInternalRawFixes().add(compactWind); - } finally { - unlockAfterWrite(); - } + public boolean add(Wind wind) { + final CompactWindImpl compactWind = new CompactWindImpl(wind); + final boolean result = super.add(compactWind); notifyListenersAboutReceive(compactWind); + return result; } private void notifyListenersAboutReceive(Wind wind) { @@ -345,6 +341,10 @@ public class WindTrackImpl extends TrackImpl implements WindTrack { return null; } @Override + public SpeedWithBearing add(SpeedWithBearing other) { + return null; + } + @Override public int compareTo(Speed o) { return 0; } diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index c1d6acec4c5..2a3c53211fd 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -24,7 +24,9 @@ import java.util.TimeZone; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import com.sap.sailing.declination.Declination; import com.sap.sailing.declination.DeclinationService; @@ -44,6 +46,8 @@ import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver; public class UDPExpeditionReceiverTest { + @Rule public Timeout TestTimeout = new Timeout(60 * 1000); + private String[] validLines; private String[] someValidWithFourInvalidLines; final List messages = new ArrayList(); diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPMirrorTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPMirrorTest.java index 7e82877c344..2194df721cd 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPMirrorTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPMirrorTest.java @@ -11,11 +11,15 @@ import java.util.Random; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import com.sap.sailing.expeditionconnector.UDPMirror; public class UDPMirrorTest { + @Rule public Timeout TestTimeout = new Timeout(60 * 1000); + private static final int MIRROR_PORT = 33795; private static final int RECEIVING_PORT = 33796; private Thread mirrorThread; diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index 177facccb05..bbbd960acf7 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -52,6 +52,8 @@ + + diff --git a/java/com.sap.sailing.feature.runtime/feature.xml b/java/com.sap.sailing.feature.runtime/feature.xml index 2c04c5de481..2e62c3b8b84 100644 --- a/java/com.sap.sailing.feature.runtime/feature.xml +++ b/java/com.sap.sailing.feature.runtime/feature.xml @@ -94,5 +94,19 @@ install-size="0" version="0.0.0" unpack="false"/> - + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/.gitignore b/java/com.sap.sailing.gwt.ui.test/.gitignore index 83178462e56..cca910ac7ad 100755 --- a/java/com.sap.sailing.gwt.ui.test/.gitignore +++ b/java/com.sap.sailing.gwt.ui.test/.gitignore @@ -1,3 +1,4 @@ bin +war gwt-unitCache www-test diff --git a/java/com.sap.sailing.gwt.ui.test/GwtTestCaseColumnToggling (proxy).launch b/java/com.sap.sailing.gwt.ui.test/GwtTestCases (no proxy).launch similarity index 78% rename from java/com.sap.sailing.gwt.ui.test/GwtTestCaseColumnToggling (proxy).launch rename to java/com.sap.sailing.gwt.ui.test/GwtTestCases (no proxy).launch index e7af0c187ca..1f22228f648 100755 --- a/java/com.sap.sailing.gwt.ui.test/GwtTestCaseColumnToggling (proxy).launch +++ b/java/com.sap.sailing.gwt.ui.test/GwtTestCases (no proxy).launch @@ -1,23 +1,24 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/GwtTestCases (proxy).launch b/java/com.sap.sailing.gwt.ui.test/GwtTestCases (proxy).launch new file mode 100755 index 00000000000..f277ad15558 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/GwtTestCases (proxy).launch @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index b94b17749f5..cc2929cfa54 100755 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -8,7 +8,9 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Fragment-Host: com.sap.sailing.gwt.ui Require-Bundle: com.sap.sailing.server, org.hamcrest.core;bundle-version="1.1.0", - com.sap.sailing.expeditionconnector + com.sap.sailing.expeditionconnector, + org.mockito.mockito-core;bundle-version="1.9.5", + org.objenesis;bundle-version="1.3.0" Bundle-ClassPath: . Import-Package: javax.jms;version="1.1.0", javax.servlet;version="2.6.0", diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/ColorMapTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/ColorMapTest.java index 1d3b20a3949..49b68bc79ab 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/ColorMapTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/ColorMapTest.java @@ -7,18 +7,18 @@ import java.util.List; import org.junit.Test; -import com.sap.sailing.gwt.ui.client.ColorMap; +import com.sap.sailing.domain.common.impl.ColorMapImpl; public class ColorMapTest { @Test public void testHundredDistinctColors() { - ColorMap colorMap = new ColorMap(); - List existingColors = new ArrayList(); + ColorMapImpl colorMap = new ColorMapImpl(); + List existingColors = new ArrayList(); int amountOfDistinctColorsToCreate = 100; - for(int i = 1; i <= amountOfDistinctColorsToCreate; i++) { - String colorByID = colorMap.getColorByID(i); - if(!existingColors.contains(colorByID)) { + for (int i = 1; i <= amountOfDistinctColorsToCreate; i++) { + String colorByID = colorMap.getColorByID(i).getAsHtml(); + if (!existingColors.contains(colorByID)) { existingColors.add(colorByID); } } @@ -30,7 +30,7 @@ public class ColorMapTest { * It creates an HTML file (as string) with 100 distinct colors on top of the water color of the google map. */ public void createColorMapAsHtml() { - ColorMap colorMap = new ColorMap(); + ColorMapImpl colorMap = new ColorMapImpl(); int amountOfDistinctColorsToCreate = 100; String colorMapAsHtml = ""; diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index 078cc260910..697c347bc3e 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -1,9 +1,12 @@ package com.sap.sailing.gwt.ui.test; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; +import java.util.Set; + +import org.junit.Test; import com.google.gwt.core.client.GWT; import com.google.gwt.junit.client.GWTTestCase; @@ -27,15 +30,15 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { //These objects should be created by calling GWT.create(Class c); private LeaderboardPanelMock leaderboardPanel; private MyTestSailingServiceAsync service; - private StringMessages sc; + private StringMessages stringMessages; //Test data. private final String LEADERBOARD_NAME = "test"; private static final String DEFAULT_FLEET_NAME = "Default"; private final String COLUMN1_NAME = "r1"; private final String EVENT_NAME = "Sailing Team Germany (STG)"; - protected static final boolean tractracTunnel = true; // Boolean.valueOf(System.getProperty("tractrac.tunnel", "false")); - protected static final String tractracTunnelHost = "10.18.206.73"; // System.getProperty("tractrac.tunnel.host", "localhost"); + protected static final boolean tractracTunnel = Boolean.valueOf(System.getProperty("tractrac.tunnel", "false")); + protected static final String tractracTunnelHost = System.getProperty("tractrac.tunnel.host", "localhost"); private final String JSON_URL= "http://" + TracTracConnectionConstants.HOST_NAME + "/events/event_20110505_SailingTea/jsonservice.php"; private final String TRACKED_RACE = "schwerttest"; private final String COURSE_DESIGN_UPDATE_URI = "http://tracms.traclive.dk/update_course"; @@ -52,18 +55,17 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { protected void gwtSetUp() throws Exception { super.gwtSetUp(); service = GWT.create(MyTestSailingService.class); - sc = GWT.create(StringMessages.class); + stringMessages = GWT.create(StringMessages.class); } + @Test public void testColumnDeleting() { delayTestFinish(1000000); - listRacesInEvent(); - } private void listRacesInEvent(){ - service.listTracTracRacesInEvent(JSON_URL, false, new AsyncCallback>>() { + service.listTracTracRacesInEvent(JSON_URL, /* hidden */ true, new AsyncCallback>>() { @Override public void onFailure(Throwable caught) { @@ -78,7 +80,6 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { rrDao = rr; } } - //assertNotNull("rrDao != null",rrDao); assertNotNull(rrDao); trackRace(); } @@ -86,7 +87,9 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { } private void trackRace(){ - service.trackWithTracTrac(/* regattaToAddTo */null, Collections.singleton(rrDao), tractracTunnel ? "tcp://" + tractracTunnelHost + ":" + final Set rrDAOs = new HashSet(); + rrDAOs.add(rrDao); + service.trackWithTracTrac(/* regattaToAddTo */null, rrDAOs, tractracTunnel ? "tcp://" + tractracTunnelHost + ":" + TracTracConnectionConstants.PORT_TUNNEL_LIVE : "tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_LIVE, tractracTunnel ? "tcp://" + tractracTunnelHost + ":" + TracTracConnectionConstants.PORT_TUNNEL_STORED : "tcp://" + TracTracConnectionConstants.HOST_NAME @@ -108,30 +111,39 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { } private void createLeaderboard(){ - service.createFlexibleLeaderboard(LEADERBOARD_NAME, null, new int[] { 1, 2 }, ScoringSchemeType.LOW_POINT, null, - new AsyncCallback() { - @Override - public void onSuccess(StrippedLeaderboardDTO result) { - System.out.println("Created Leaderboard "+result.name); - addColumnToLeaderboard(); - } + service.removeLeaderboard(LEADERBOARD_NAME, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + fail("Failed to remove Leaderboard "+LEADERBOARD_NAME+": " + caught.getLocalizedMessage()); + finishTest(); + } - @Override - public void onFailure(Throwable caught) { - fail("Failed to create Leaderboard." + caught.getLocalizedMessage()); - finishTest(); - } - }); + @Override + public void onSuccess(Void result) { + service.createFlexibleLeaderboard(LEADERBOARD_NAME, null, new int[] { 1, 2 }, ScoringSchemeType.LOW_POINT, null, + new AsyncCallback() { + @Override + public void onSuccess(StrippedLeaderboardDTO result) { + System.out.println("Created Leaderboard "+result.name); + addColumnToLeaderboard(); + } + + @Override + public void onFailure(Throwable caught) { + fail("Failed to create Leaderboard." + caught.getLocalizedMessage()); + } + }); + } + }); } private void addColumnToLeaderboard() { - leaderboardPanel = new LeaderboardPanelMock(service, LEADERBOARD_NAME, null, sc); + leaderboardPanel = new LeaderboardPanelMock(service, LEADERBOARD_NAME, null, stringMessages); service.addColumnToLeaderboard(COLUMN1_NAME, LEADERBOARD_NAME, false, new AsyncCallback() { @Override public void onFailure(Throwable caught) { fail("Failed to add column to leaderboard."); - finishTest(); } @Override @@ -158,12 +170,12 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { @Override public void onSuccess(Boolean result) { System.out.println("Success of linking race to column: "+result); - getLeaderboard(); + getLeaderboardByNameAndDeleteColumn(); } }); } - private void getLeaderboard(){ + private void getLeaderboardByNameAndDeleteColumn(){ ArrayList al = new ArrayList(); al.add(COLUMN1_NAME); service.getLeaderboardByName(LEADERBOARD_NAME, new Date(), al, /* previousLeaderboardId */ null, new AsyncCallback() { @@ -176,7 +188,6 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { @Override public void onSuccess(IncrementalOrFullLeaderboardDTO result) { System.out.println("Got leaderboard."); - leaderboard = result.getLeaderboardDTO(/* previousVersion */ null); leaderboardPanel.updateLeaderboard(leaderboard); for (int i = 0; i < leaderboardPanel.getLeaderboardTable() @@ -197,7 +208,6 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { private void removeColumnAndAssert(){ service.removeLeaderboardColumn(LEADERBOARD_NAME, COLUMN1_NAME, new AsyncCallback() { - @Override public void onFailure(Throwable caught) { fail("Failed to remoce column."); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java index 52749e11685..2531f7d1713 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/LeaderboardPanelMock.java @@ -3,10 +3,12 @@ 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.actions.AsyncActionsExecutor; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.UserAgentDetails; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; import com.sap.sailing.gwt.ui.leaderboard.SortableColumn; @@ -16,10 +18,10 @@ public class LeaderboardPanelMock extends LeaderboardPanel { public LeaderboardPanelMock(SailingServiceAsync sailingService, String leaderboardName, ErrorReporter errorReporter, - StringMessages stringConstants) { - super(sailingService, null, LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( + StringMessages stringMessages) { + super(sailingService, new AsyncActionsExecutor(), LeaderboardSettingsFactory.getInstance().createNewDefaultSettings( /* racesToShow */null, /* namesOfRacesToShow */ null, null, /* autoExpandFirstRace */ false), new CompetitorSelectionModel( - /* hasMultiSelection */true), leaderboardName, errorReporter, stringConstants, null, /* showRaceDetails */ true); + /* hasMultiSelection */true), leaderboardName, errorReporter, stringMessages, new UserAgentDetails("gecko1_8"), /* showRaceDetails */ true); } @Override diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index 0a35200a210..2a1b39d2c12 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.base.RegattaListener; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Position; @@ -431,6 +432,18 @@ public class MockedTrackedRace implements DynamicTrackedRace { // TODO Auto-generated method stub return null; } + + @Override + public RegattaConfiguration getRegattaConfiguration() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setRegattaConfiguration(RegattaConfiguration configuration) { + // TODO Auto-generated method stub + + } }; } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MyTestSailingServiceImpl.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MyTestSailingServiceImpl.java index 2343ea6720c..abfc6e3ff1c 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MyTestSailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MyTestSailingServiceImpl.java @@ -1,32 +1,10 @@ package com.sap.sailing.gwt.ui.test; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; -import com.sap.sailing.gwt.ui.server.SailingServiceImpl; -import com.sap.sailing.server.RacingEventService; -import com.sap.sailing.server.impl.RacingEventServiceImpl; - -public class MyTestSailingServiceImpl extends SailingServiceImpl implements MyTestSailingService { +public class MyTestSailingServiceImpl extends SailingServiceImplMock implements MyTestSailingService { private static final long serialVersionUID = 3647254510616623048L; - private RacingEventService service; - public MyTestSailingServiceImpl(){ super(); - service = new RacingEventServiceImpl(); } - - public RacingEventService getService(){ - return service; - } - - @Override - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - // TODO Auto-generated method stub - return null; - } - - } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java index 427f440c2b4..db44811d05b 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PolarSheetGenerationServiceTest.java @@ -9,8 +9,6 @@ import java.util.concurrent.ConcurrentSkipListSet; import junit.framework.Assert; import org.junit.Test; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; @@ -23,13 +21,13 @@ import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.PolarSheetGenerationResponse; import com.sap.sailing.domain.common.PolarSheetGenerationSettings; import com.sap.sailing.domain.common.PolarSheetsData; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; -import com.sap.sailing.domain.common.ScoreCorrectionProvider; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WindSource; @@ -43,8 +41,6 @@ import com.sap.sailing.domain.common.impl.Util; import com.sap.sailing.domain.common.impl.Util.Pair; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.domain.common.impl.WindSteppingWithMaxDistance; -import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; -import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFixMoving; @@ -57,12 +53,9 @@ import com.sap.sailing.domain.tracking.impl.MarkPassingByTimeComparator; import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sailing.domain.tracking.impl.WindImpl; import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; -import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; import com.sap.sailing.gwt.ui.client.SailingService; -import com.sap.sailing.gwt.ui.server.SailingServiceImpl; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.impl.RacingEventServiceImpl; -import com.sap.sailing.server.replication.ReplicationService; public class PolarSheetGenerationServiceTest { @@ -100,50 +93,12 @@ public class PolarSheetGenerationServiceTest { @SuppressWarnings("serial") - private class MockSailingServiceForPolarSheetGeneration extends SailingServiceImpl { - + private class MockSailingServiceForPolarSheetGeneration extends SailingServiceImplMock { @Override protected RacingEventService getService() { RacingEventService service = new MockRacingEventServiceForPolarSheetGeneration(); return service; } - - @Override - protected ServiceTracker createAndOpenRacingEventServiceTracker( - BundleContext context) { - return null; - } - - @Override - protected ServiceTracker createAndOpenReplicationServiceTracker( - BundleContext context) { - return null; - } - - @Override - protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( - BundleContext bundleContext) {; - return null; - } - - @Override - protected ServiceTracker createAndOpenTracTracAdapterTracker( - BundleContext context) { - return null; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingAdapterTracker( - BundleContext context) { - return null; - } - - @Override - protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( - BundleContext context) { - return null; - } - } private class MockRacingEventServiceForPolarSheetGeneration extends RacingEventServiceImpl { @@ -209,7 +164,7 @@ public class PolarSheetGenerationServiceTest { waypoints.add(new WaypointImpl(null)); waypoints.add(new WaypointImpl(null)); ArrayList competitors = new ArrayList(); - Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", new TeamImpl("SAP", null, null), + Competitor competitor = new CompetitorImpl(UUID.randomUUID(), "Hans Frantz", Color.RED, new TeamImpl("SAP", null, null), new BoatImpl("Schnelle Forelle", forelle, "GER000")); competitors.add(competitor); RaceDefinition race = new RaceDefinitionImpl("Forelle1", new CourseImpl("ForelleCourse", waypoints), forelle, competitors); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index d8f010b18b9..58146114a09 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -1,15 +1,40 @@ package com.sap.sailing.gwt.ui.test; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.util.List; + import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.common.RegattaIdentifier; +import com.sap.sailing.domain.common.ScoreCorrectionProvider; +import com.sap.sailing.domain.racelog.RaceLogStore; +import com.sap.sailing.domain.swisstimingadapter.RaceSpecificMessageLoader; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; +import com.sap.sailing.domain.swisstimingadapter.SwissTimingFactory; +import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayService; +import com.sap.sailing.domain.swisstimingreplayadapter.SwissTimingReplayServiceFactory; +import com.sap.sailing.domain.tracking.RacesHandle; +import com.sap.sailing.domain.tracking.TrackerManager; +import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; import com.sap.sailing.gwt.ui.server.SailingServiceImpl; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.impl.RacingEventServiceImpl; +import com.sap.sailing.server.replication.ReplicationService; +import com.sap.sailing.server.replication.impl.ReplicationInstancesManager; +import com.sap.sailing.server.replication.impl.ReplicationServiceImpl; public class SailingServiceImplMock extends SailingServiceImpl { private static final long serialVersionUID = 8564037671550730455L; - private final RacingEventService service; + private RacingEventService service; public SailingServiceImplMock() { super(); @@ -19,11 +44,123 @@ public class SailingServiceImplMock extends SailingServiceImpl { @Override protected ServiceTracker createAndOpenRacingEventServiceTracker( BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(new RacingEventServiceImpl()); + return result; + } + + @Override + protected ServiceTracker createAndOpenReplicationServiceTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + try { + final ReplicationServiceImpl replicationService = new ReplicationServiceImpl("test exchange", "localhost", new ReplicationInstancesManager()) { + @Override + protected ServiceTracker getRacingEventServiceTracker() { + @SuppressWarnings("unchecked") + ServiceTracker result = (ServiceTracker) mock(ServiceTracker.class); + doReturn("Humba Humba").when(result).toString(); + when(result.getService()).thenReturn(new RacingEventServiceImpl()); + return result; + } + }; + when(result.getService()).thenReturn(replicationService); + } catch (IOException e) { + throw new RuntimeException(); + } + return result; + } + + @Override + protected ServiceTracker createAndOpenScoreCorrectionProviderServiceTracker( + BundleContext bundleContext) { return null; } + @Override + protected ServiceTracker createAndOpenTracTracAdapterTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(TracTracAdapterFactory.INSTANCE); + return result; + } + + @Override + protected ServiceTracker createAndOpenSwissTimingAdapterTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + when(result.getService()).thenReturn(new SwissTimingAdapterFactory() { + @Override + public SwissTimingAdapter getOrCreateSwissTimingAdapter(DomainFactory baseDomainFactory, + RaceSpecificMessageLoader persistence) { + return new SwissTimingAdapter() { + @Override + public List getSwissTimingRaceRecords( + String hostname, int port, boolean canSendRequests) throws InterruptedException, + UnknownHostException, IOException, ParseException { + // TODO Auto-generated method stub + return null; + } + + @Override + public RacesHandle addSwissTimingRace(TrackerManager trackerManager, + RegattaIdentifier regattaToAddTo, String raceID, String hostname, int port, + boolean canSendRequests, RaceLogStore logStore, long timeoutInMilliseconds) + throws InterruptedException, UnknownHostException, IOException, ParseException, + Exception { + // TODO Auto-generated method stub + return null; + } + + @Override + public SwissTimingFactory getSwissTimingFactory() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void storeSwissTimingDummyRace(String racMessage, String stlMesssage, String ccgMessage) + throws IllegalArgumentException { + // TODO Auto-generated method stub + + } + + @Override + public com.sap.sailing.domain.swisstimingadapter.DomainFactory getSwissTimingDomainFactory() { + return com.sap.sailing.domain.swisstimingadapter.DomainFactory.INSTANCE; + } + }; + } + }); + return result; + } + + @Override + protected ServiceTracker createAndOpenSwissTimingReplayServiceTracker( + BundleContext context) { + @SuppressWarnings("unchecked") + ServiceTracker result = mock(ServiceTracker.class); + final SwissTimingReplayServiceFactory swissTimingReplayService = new SwissTimingReplayServiceFactory() { + @Override + public SwissTimingReplayService createSwissTimingReplayService( + com.sap.sailing.domain.swisstimingadapter.DomainFactory domainFactory) { + // TODO Auto-generated method stub + return null; + } + }; + when(result.getService()).thenReturn(swissTimingReplayService); + return result; + } + @Override protected RacingEventService getService() { + if (service == null) { + service = new RacingEventServiceImpl(); + } return service; } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java index cac96b9a817..0294a67fd0b 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/TestColumnSwapping.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.test; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -42,6 +43,7 @@ public class TestColumnSwapping { public void prepareColumnSwapping() { service = new SailingServiceImplMock(); int[] disc = { 5, 8, 9, 0, 7, 5, 43 }; + service.removeLeaderboard(LEADERBOARDNAME); service.createFlexibleLeaderboard(LEADERBOARDNAME, null, disc, ScoringSchemeType.LOW_POINT, null); service.addColumnToLeaderboard("Race1", LEADERBOARDNAME, true); service.addColumnToLeaderboard("Race2", LEADERBOARDNAME, true); @@ -59,9 +61,9 @@ public class TestColumnSwapping { return UUID.randomUUID().toString(); } }); - leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 1., DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 1., DEFAULT_FLEET, true, null, null); - leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 1., DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race1", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race3", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); + leaderboardOriginalDTO.addRace("Race2", /* explicitFactor */ null, 2., DEFAULT_FLEET, true, null, null); } catch (Exception e) { e.printStackTrace(); } @@ -79,6 +81,7 @@ public class TestColumnSwapping { sailingService = new SailingServiceImplMock(); assertNotNull("Sailingservice != NULL", sailingService); int td[] = { 5, 8 }; + sailingService.removeLeaderboard(TEST_LEADERBOARD_NAME); sailingService.createFlexibleLeaderboard(TEST_LEADERBOARD_NAME, null, td, ScoringSchemeType.LOW_POINT, null); for (int i = 0; i < races.length; i++) sailingService.addColumnToLeaderboard(races[i], TEST_LEADERBOARD_NAME, isMedalRace[i]); @@ -107,7 +110,7 @@ public class TestColumnSwapping { boolean[] medalRace = { true, false, false }; for (int i = 0; i < lb.getRaceList().size(); i++) { - assertTrue("Race[" + i + "] == " + raceNames[i], lb.getRaceList().get(i).equals(raceNames[i])); + assertEquals("Race[" + i + "] == " + raceNames[i], raceNames[i], lb.getRaceList().get(i).getName()); assertTrue("Race[" + i + "] is " + (medalRace[i] ? "" : "no ") + "medalrace.", lb.raceIsMedalRace(races[i]) == medalRace[i]); } @@ -127,8 +130,9 @@ public class TestColumnSwapping { lb.addRace("3", /* explicitFactor */ null, 1., DEFAULT_FLEET, true, null, null); lb.moveRaceDown("1"); String[] s = new String[] { "2", "1", "3" }; - for (int i = 0; i < lb.getRaceList().size(); i++) - assertTrue("Race[" + i + "] = " + s[i], lb.getRaceList().get(i).equals(s[i])); + for (int i = 0; i < lb.getRaceList().size(); i++) { + assertEquals("Race[" + i + "] = " + s[i], s[i], lb.getRaceList().get(i).getName()); + } } @Test diff --git a/java/com.sap.sailing.gwt.ui/.classpath b/java/com.sap.sailing.gwt.ui/.classpath index f20594dd0e9..43b189330c4 100755 --- a/java/com.sap.sailing.gwt.ui/.classpath +++ b/java/com.sap.sailing.gwt.ui/.classpath @@ -1,12 +1,13 @@ + + - - - - - + + + + diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.css b/java/com.sap.sailing.gwt.ui/AdminConsole.css index 578315b4853..2fcaf5f94d3 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.css +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.css @@ -188,3 +188,21 @@ select { .paddedPanel { padding: 10px; } + +.gwt-SuggestBoxPopup { + text-align: left; + cursor: pointer; + cursor: hand; + border: 1px solid #666; + border-top: 0; + background-color: #fff; + z-index: 999; +} + +.gwt-SuggestBoxPopup .item { + border: 1px dotted #aaa; +} + +.gwt-SuggestBoxPopup .item-selected { + background-color: #ffc; +} diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.html b/java/com.sap.sailing.gwt.ui/AdminConsole.html index 7fe957dfabe..507b3bc6972 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.html +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.html @@ -20,11 +20,14 @@ SAP Sailing Analytics Administration Console - + + + + diff --git a/java/com.sap.sailing.gwt.ui/AdminConsoleDateTimePicker.css b/java/com.sap.sailing.gwt.ui/AdminConsoleDateTimePicker.css new file mode 100644 index 00000000000..f93eb68fa0b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/AdminConsoleDateTimePicker.css @@ -0,0 +1,88 @@ +/*! + * Extracted from Bootstrap v3.0.2 by @fat and @mdo + * Copyright 2013 Twitter, Inc. + * Licensed under http://www.apache.org/licenses/LICENSE-2.0 + */ + +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} + +.dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} + +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333333; + white-space: nowrap; +} + +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} + +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #ffffff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} + +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999999; +} + +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 8749a6c5e16..710b6c6c104 100755 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -42,5 +42,6 @@ Web-ContextPath: /gwt Bundle-ClassPath: WEB-INF/classes/, WEB-INF/lib/gwt-servlet.jar, WEB-INF/lib/gwt-maps-api-3.9.0-build-17.jar, - WEB-INF/lib/gwt-ajaxloader.jar + WEB-INF/lib/gwt-ajaxloader.jar, + WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar TrackLifecycle: true diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar b/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar new file mode 100644 index 00000000000..ed9cab41ba1 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/WEB-INF/lib/gwt-bootstrap-2.3.2.0.jar differ diff --git a/java/com.sap.sailing.gwt.ui/build.properties b/java/com.sap.sailing.gwt.ui/build.properties index 88c81e2e761..8c5a915583c 100755 --- a/java/com.sap.sailing.gwt.ui/build.properties +++ b/java/com.sap.sailing.gwt.ui/build.properties @@ -46,5 +46,7 @@ bin.includes = META-INF/,\ PolarSheets.css,\ PolarSheets.html,\ ViewerToolbar.css,\ - CommonGwtControls.css + CommonGwtControls.css,\ + AdminConsoleDateTimePicker.css,\ + qrcode/ output.. = WEB-INF/classes/ diff --git a/java/com.sap.sailing.gwt.ui/pom.xml b/java/com.sap.sailing.gwt.ui/pom.xml index 95ab316fb71..36490d9ca81 100644 --- a/java/com.sap.sailing.gwt.ui/pom.xml +++ b/java/com.sap.sailing.gwt.ui/pom.xml @@ -13,6 +13,15 @@ 2.5.1 + + + sonatype + http://oss.sonatype.org/content/repositories/snapshots + true + false + + + @@ -39,6 +48,12 @@ 1.0.0-SNAPSHOT sources + + com.github.gwtbootstrap + gwt-bootstrap + 2.3.2.0-SNAPSHOT + provided + diff --git a/java/com.sap.sailing.gwt.ui/qrcode/LICENSE b/java/com.sap.sailing.gwt.ui/qrcode/LICENSE new file mode 100644 index 00000000000..93c33233fa5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/qrcode/LICENSE @@ -0,0 +1,14 @@ +The MIT License (MIT) +--------------------- +Copyright (c) 2012 davidshimjs + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/qrcode/README.md b/java/com.sap.sailing.gwt.ui/qrcode/README.md new file mode 100644 index 00000000000..a06117e65a9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/qrcode/README.md @@ -0,0 +1,40 @@ +# QRCode.js +QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM. +QRCode.js has no dependencies. + +## Basic Usages +``` +

+ +``` + +or with some options + +``` +var qrcode = new QRCode("test", { + text: "http://jindo.dev.naver.com/collie", + width: 128, + height: 128, + colorDark : "#000000", + colorLight : "#ffffff", + correctLevel : QRCode.CorrectLevel.H +}); +``` + +and you can use some methods + +``` +qrcode.clear(); // clear the code. +qrcode.makeCode("http://naver.com"); // make another code. +``` + +## Browser Compatibility +IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC. + +## License +MIT License + +## Contact +twitter @davidshimjs \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/qrcode/qrcode.min.js b/java/com.sap.sailing.gwt.ui/qrcode/qrcode.min.js new file mode 100644 index 00000000000..993e88f3966 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/qrcode/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java index 40a383d3f23..3dede9035f8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleEntryPoint.java @@ -4,150 +4,185 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.RootPanel; -import com.google.gwt.user.client.ui.TabPanel; -import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.TabLayoutPanel; +import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; import com.sap.sailing.gwt.ui.client.AbstractEntryPoint; import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.RegattaRefresher; +import com.sap.sailing.gwt.ui.client.shared.controls.ScrolledTabLayoutPanel; import com.sap.sailing.gwt.ui.client.shared.panels.SystemInformationPanel; import com.sap.sailing.gwt.ui.client.shared.panels.UserStatusPanel; import com.sap.sailing.gwt.ui.masterdataimport.MasterDataImportPanel; +import com.sap.sailing.gwt.ui.shared.BetterDateTimeBox; import com.sap.sailing.gwt.ui.shared.RegattaDTO; public class AdminConsoleEntryPoint extends AbstractEntryPoint implements RegattaRefresher { private Set regattaDisplayers; - + + private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); - RootPanel rootPanel = RootPanel.get(); - VerticalPanel verticalPanel = new VerticalPanel(); - rootPanel.add(verticalPanel); - rootPanel.setSize("100%", "100%"); - verticalPanel.setSize("100%", "100%"); + BetterDateTimeBox.initialize(); + RootLayoutPanel rootPanel = RootLayoutPanel.get(); + DockLayoutPanel dockPanel = new DockLayoutPanel(Unit.EM); + rootPanel.add(dockPanel); + HorizontalPanel topInformationPanel = new HorizontalPanel(); topInformationPanel.setSize("100%", "95%"); UserStatusPanel userStatusPanel = new UserStatusPanel(userManagementService, this); userStatusPanel.ensureDebugId("UserStatus"); topInformationPanel.add(userStatusPanel); topInformationPanel.add(persistentAlertLabel); - - verticalPanel.add(topInformationPanel); - - TabPanel tabPanel = new TabPanel(); + + dockPanel.addNorth(topInformationPanel, 2.5); + + TabLayoutPanel tabPanel = new ScrolledTabLayoutPanel(2.5, Unit.EM, resources.arrowLeft(), resources.arrowRight(), 200); tabPanel.ensureDebugId("AdministrationTabs"); - tabPanel.setAnimationEnabled(true); - tabPanel.setSize("99%", "95%"); - verticalPanel.add(tabPanel); //, 10, 10); - - regattaDisplayers = new HashSet(); + tabPanel.setAnimationDuration(500); - SailingEventManagementPanel sailingEventManagementPanel = new SailingEventManagementPanel(sailingService, this, stringMessages); - tabPanel.add(sailingEventManagementPanel, stringMessages.events(), false); + regattaDisplayers = new HashSet(); - RegattaStructureManagementPanel eventStructureManagementPanel = new RegattaStructureManagementPanel(sailingService, this, stringMessages, this); - //eventStructureManagementPanel.ensureDebugId("RegattaStructureManagement"); - tabPanel.add(eventStructureManagementPanel, stringMessages.regattas()); + SailingEventManagementPanel sailingEventManagementPanel = new SailingEventManagementPanel(sailingService, this, + stringMessages); + sailingEventManagementPanel.setSize("90%", "90%"); + addScrollableTab(tabPanel, sailingEventManagementPanel, stringMessages.events()); + + RegattaStructureManagementPanel eventStructureManagementPanel = new RegattaStructureManagementPanel( + sailingService, this, stringMessages, this); + // eventStructureManagementPanel.ensureDebugId("RegattaStructureManagement"); + eventStructureManagementPanel.setSize("90%", "90%"); + addScrollableTab(tabPanel, eventStructureManagementPanel, stringMessages.regattas()); regattaDisplayers.add(eventStructureManagementPanel); - - TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, this, this, stringMessages); + + TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, + this, this, stringMessages); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); tractracEventManagementPanel.setSize("90%", "90%"); - tabPanel.add(tractracEventManagementPanel, stringMessages.tracTracEvents(), false); + addScrollableTab(tabPanel, tractracEventManagementPanel, stringMessages.tracTracEvents()); regattaDisplayers.add(tractracEventManagementPanel); - - SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel(sailingService, this, this, stringMessages); + + SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel( + sailingService, this, this, stringMessages); swissTimingReplayConnectorPanel.setSize("90%", "90%"); - tabPanel.add(swissTimingReplayConnectorPanel, stringMessages.swissTimingArchiveConnector(), false); + addScrollableTab(tabPanel, swissTimingReplayConnectorPanel, stringMessages.swissTimingArchiveConnector()); regattaDisplayers.add(swissTimingReplayConnectorPanel); - - SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel(sailingService, this, this, stringMessages); - //swisstimingEventManagementPanel.ensureDebugId("SwissTimingEventManagement"); + + SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel( + sailingService, this, this, stringMessages); + // swisstimingEventManagementPanel.ensureDebugId("SwissTimingEventManagement"); swisstimingEventManagementPanel.setSize("90%", "90%"); - tabPanel.add(swisstimingEventManagementPanel, stringMessages.swissTimingEvents(), false); + addScrollableTab(tabPanel, swisstimingEventManagementPanel, stringMessages.swissTimingEvents()); regattaDisplayers.add(swisstimingEventManagementPanel); - - CreateSwissTimingRacePanel createSwissTimingRacePanel = new CreateSwissTimingRacePanel(sailingService, this, stringMessages); - //createSwissTimingRacePanel.ensureDebugId("CreateSwissTimingRace"); + + CreateSwissTimingRacePanel createSwissTimingRacePanel = new CreateSwissTimingRacePanel(sailingService, this, + stringMessages); + // createSwissTimingRacePanel.ensureDebugId("CreateSwissTimingRace"); createSwissTimingRacePanel.setSize("90%", "90%"); - tabPanel.add(createSwissTimingRacePanel,"Create SwissTiming race",false); - - TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, this, stringMessages); - //trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); + addScrollableTab(tabPanel, createSwissTimingRacePanel, "Create SwissTiming race"); + + TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, + this, stringMessages); + // trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); trackedRacesManagementPanel.setSize("90%", "90%"); - tabPanel.add(trackedRacesManagementPanel, stringMessages.trackedRaces(),false); + addScrollableTab(tabPanel, trackedRacesManagementPanel, stringMessages.trackedRaces()); regattaDisplayers.add(trackedRacesManagementPanel); - + final CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, stringMessages, this); - //trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); + // trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); trackedRacesManagementPanel.setSize("90%", "90%"); - tabPanel.add(competitorPanel, stringMessages.competitors(), false); - - RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, stringMessages); - //raceCourseManagementPanel.ensureDebugId("RaceCourseManagement"); + addScrollableTab(tabPanel, competitorPanel, stringMessages.competitors()); + + RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, + stringMessages); + // raceCourseManagementPanel.ensureDebugId("RaceCourseManagement"); raceCourseManagementPanel.setSize("90%", "90%"); - tabPanel.add(raceCourseManagementPanel, stringMessages.courseLayout(),false); + addScrollableTab(tabPanel, raceCourseManagementPanel, stringMessages.courseLayout()); regattaDisplayers.add(raceCourseManagementPanel); final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); - + WindPanel windPanel = new WindPanel(sailingService, asyncActionsExecutor, this, this, stringMessages); - //windPanel.ensureDebugId("WindManagement"); + // windPanel.ensureDebugId("WindManagement"); regattaDisplayers.add(windPanel); windPanel.setSize("90%", "90%"); - tabPanel.add(windPanel, stringMessages.wind(), /* asHTML */ false); - - final LeaderboardGroupConfigPanel leaderboardGroupConfigPanel = new LeaderboardGroupConfigPanel(sailingService, this, this, stringMessages); - //leaderboardGroupConfigPanel.ensureDebugId("LeaderboardGroupConfiguration"); + addScrollableTab(tabPanel, windPanel, stringMessages.wind()); + + final LeaderboardGroupConfigPanel leaderboardGroupConfigPanel = new LeaderboardGroupConfigPanel(sailingService, + this, this, stringMessages); + // leaderboardGroupConfigPanel.ensureDebugId("LeaderboardGroupConfiguration"); leaderboardGroupConfigPanel.setSize("90%", "90%"); - tabPanel.add(leaderboardGroupConfigPanel, stringMessages.leaderboardGroupConfiguration(), /*asHTML*/ false); + addScrollableTab(tabPanel, leaderboardGroupConfigPanel, stringMessages.leaderboardGroupConfiguration()); regattaDisplayers.add(leaderboardGroupConfigPanel); - - final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, stringMessages, - /* showRaceDetails */ true); + + final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, + stringMessages, + /* showRaceDetails */true); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); leaderboardConfigPanel.setSize("90%", "90%"); - tabPanel.add(leaderboardConfigPanel, stringMessages.leaderboardConfiguration(), /* asHTML */ false); + addScrollableTab(tabPanel, leaderboardConfigPanel, stringMessages.leaderboardConfiguration()); regattaDisplayers.add(leaderboardConfigPanel); - - tabPanel.add(new ResultImportUrlsManagementPanel(sailingService, this, stringMessages), stringMessages.resultImportUrls(), /* asHTML */ false); - + + addScrollableTab(tabPanel, new ResultImportUrlsManagementPanel(sailingService, this, stringMessages), + stringMessages.resultImportUrls()); + ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, stringMessages); - //replicationPanel.ensureDebugId("ReplicationManagement"); - tabPanel.add(replicationPanel, stringMessages.replication(), /* asHTML */ false); + // replicationPanel.ensureDebugId("ReplicationManagement"); + addScrollableTab(tabPanel, replicationPanel, stringMessages.replication()); final MediaPanel mediaPanel = new MediaPanel(mediaService, this, stringMessages); - tabPanel.add(mediaPanel, stringMessages.mediaPanel(), /* asHTML */ false); - - final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(stringMessages, sailingService, this, sailingEventManagementPanel, leaderboardGroupConfigPanel); - tabPanel.add(masterDataImportPanel, stringMessages.masterDataImportPanel(), /* asHTML */ false); + addScrollableTab(tabPanel, mediaPanel, stringMessages.mediaPanel()); + + final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(stringMessages, sailingService, + this, sailingEventManagementPanel, leaderboardGroupConfigPanel); + addScrollableTab(tabPanel, masterDataImportPanel, stringMessages.masterDataImportPanel()); + + /*final DeviceConfigurationPanel deviceConfigurationAdminPanel = new DeviceConfigurationPanel(sailingService, + stringMessages, this); + addScrollableTab(tabPanel, deviceConfigurationAdminPanel, stringMessages.deviceConfiguration() + " (admin)");*/ + final DeviceConfigurationPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(sailingService, + stringMessages, this); + addScrollableTab(tabPanel, deviceConfigurationUserPanel, stringMessages.deviceConfiguration()); + tabPanel.selectTab(0); tabPanel.addSelectionHandler(new SelectionHandler() { @Override public void onSelection(SelectionEvent event) { if (leaderboardConfigPanel.isVisible()) { leaderboardConfigPanel.loadLeaderboards(); - } + } if (mediaPanel.isVisible()) { mediaPanel.onShow(); } if (competitorPanel.isVisible()) { competitorPanel.refreshCompetitorList(); - } + } } }); fillRegattas(); - + SystemInformationPanel sysinfoPanel = new SystemInformationPanel(sailingService, this); sysinfoPanel.ensureDebugId("SystemInformation"); - verticalPanel.add(sysinfoPanel); + dockPanel.addSouth(sysinfoPanel, 2.0); + dockPanel.add(tabPanel); + } + + private void addScrollableTab(TabLayoutPanel tabPanel, Widget widget, String tabTitle) { + ScrollPanel widgetScroller = new ScrollPanel(); + widgetScroller.add(widget); + tabPanel.add(widgetScroller, tabTitle, false); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java index 8c510120ee5..a3a1409754b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleResources.java @@ -34,10 +34,25 @@ interface AdminConsoleResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/dice.png") ImageResource scoresIcon(); - + + @Source("com/sap/sailing/gwt/ui/client/images/users.png") + ImageResource competitorsIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/blackdot.png") ImageResource blackdotIcon(); @Source("com/sap/sailing/gwt/ui/client/images/reddot.png") ImageResource reddotIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/arrow_left.png") + ImageResource arrowLeft(); + + @Source("com/sap/sailing/gwt/ui/client/images/arrow_right.png") + ImageResource arrowRight(); + + @Source("com/sap/sailing/gwt/ui/client/images/help.png") + ImageResource help(); + + @Source("com/sap/sailing/gwt/ui/client/images/clock.png") + ImageResource clock(); } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css index 46efb2bea1b..1cfb8080acb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AdminConsoleTable.css @@ -55,7 +55,7 @@ border-left: none; border-top: none !important; border-bottom: 1px solid #c9caca !important; - padding: 5px 17px 5px !important; + padding: 5px 10px 5px !important; white-space: nowrap; } @@ -69,7 +69,7 @@ border-right: 1px solid #c9caca !important; border-top: none !important; border-bottom: 1px solid #c9caca !important; - padding: 5px 17px 5px !important; + padding: 5px 10px 5px !important; white-space: nowrap; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index fcb703c20eb..047df14eed4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -10,11 +10,14 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Color; import com.sap.sailing.domain.common.CountryCode; import com.sap.sailing.domain.common.CountryCodeFactory; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.CompetitorDTOImpl; +import com.sap.sailing.domain.common.impl.RGBColor; import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.domain.common.impl.Util.Triple; import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -30,6 +33,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; public class CompetitorEditDialog extends DataEntryDialog { private final CompetitorDTO competitorToEdit; private final TextBox name; + private final TextBox displayColorTextBox; private final ListBox threeLetterIocCountryCode; private final TextBox sailId; private final StringMessages stringMessages; @@ -40,13 +44,16 @@ public class CompetitorEditDialog extends DataEntryDialog { .cancel(), new Validator() { @Override public String getErrorMessage(CompetitorDTO valueToValidate) { - final String result; + String result = null; if (valueToValidate.getName() == null || valueToValidate.getName().isEmpty()) { result = stringMessages.pleaseEnterAName(); } else if (valueToValidate.getSailID() == null || valueToValidate.getSailID().isEmpty()) { result = stringMessages.pleaseEnterASailNumber(); - } else { - result = null; + } else if (valueToValidate.getColor() != null) { + Color displayColor = valueToValidate.getColor(); + if (displayColor instanceof InvalidColor) { + result = displayColor.getAsHtml(); + } } return result; } @@ -54,6 +61,7 @@ public class CompetitorEditDialog extends DataEntryDialog { this.stringMessages = stringMessages; this.competitorToEdit = competitorToEdit; this.name = createTextBox(competitorToEdit.getName()); + this.displayColorTextBox = createTextBox(competitorToEdit.getColor() == null ? "" : competitorToEdit.getColor().getAsHtml()); this.threeLetterIocCountryCode = createListBox(/* isMultipleSelect */ false); CountryCodeFactory ccf = CountryCodeFactory.INSTANCE; int i=0; @@ -90,26 +98,64 @@ public class CompetitorEditDialog extends DataEntryDialog { super.show(); name.setFocus(true); } + + /** + * Encodes an invalid color; can be used + * @author Axel Uhl (D043530) + * + */ + private class InvalidColor implements Color { + private static final long serialVersionUID = 4012986110898149543L; + private final Exception exception; + + protected InvalidColor(Exception exception) { + this.exception = exception; + } + + @Override + public Triple getAsRGB() { + return null; + } + + @Override + public Triple getAsHSV() { + return null; + } + + @Override + public String getAsHtml() { + return stringMessages.invalidColor(exception.getMessage()); + } + + } @Override protected CompetitorDTO getResult() { - CompetitorDTO result = new CompetitorDTOImpl(name.getText(), + Color color; + try { + color = new RGBColor(displayColorTextBox.getText()); + } catch (IllegalArgumentException iae) { + color = new InvalidColor(iae); + } + CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, /* twoLetterIsoCountryCode */ null, threeLetterIocCountryCode.getValue(threeLetterIocCountryCode.getSelectedIndex()), - /* countryName */null, sailId.getText(), competitorToEdit.getIdAsString(), + /* countryName */ null, sailId.getText(), competitorToEdit.getIdAsString(), competitorToEdit.getBoatClass()); return result; } @Override protected Widget getAdditionalWidget() { - Grid result = new Grid(3, 2); + Grid result = new Grid(4, 2); result.setWidget(0, 0, new Label(stringMessages.name())); result.setWidget(0, 1, name); result.setWidget(1, 0, new Label(stringMessages.sailNumber())); result.setWidget(1, 1, sailId); result.setWidget(2, 0, new Label(stringMessages.nationality())); result.setWidget(2, 1, threeLetterIocCountryCode); + result.setWidget(3, 0, new Label(stringMessages.color())); + result.setWidget(3, 1, displayColorTextBox); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 29c50c1a841..9db69bc7737 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -1,276 +1,320 @@ -package com.sap.sailing.gwt.ui.adminconsole; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import com.google.gwt.cell.client.FieldUpdater; -import com.google.gwt.cell.client.SafeHtmlCell; -import com.google.gwt.core.client.GWT; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.safehtml.shared.SafeHtml; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.CellTable; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; -import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.ImageResourceRenderer; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.VerticalPanel; -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.SelectionChangeEvent.Handler; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.impl.NaturalComparator; -import com.sap.sailing.domain.common.impl.Util; -import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; -import com.sap.sailing.gwt.ui.client.ErrorReporter; -import com.sap.sailing.gwt.ui.client.FlagImageResolver; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.panels.AbstractFilterablePanel; - -/** - * Allows an administrator to view and edit the set of competitors currently maintained by the server. - * - * @author Axel Uhl (d043530) - * - */ -public class CompetitorPanel extends SimplePanel { - private final SailingServiceAsync sailingService; - private final StringMessages stringMessages; - private final ErrorReporter errorReporter; - private CellTable competitorTable; - private MultiSelectionModel competitorSelectionModel; - private ListDataProvider competitorProvider; - private List allCompetitors; - private AbstractFilterablePanel filterField; - private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - - public CompetitorPanel(final SailingServiceAsync sailingService, final StringMessages stringMessages, - final ErrorReporter errorReporter) { - super(); - this.sailingService = sailingService; - this.stringMessages = stringMessages; - this.errorReporter = errorReporter; - competitorProvider = new ListDataProvider(); - ListHandler competitorColumnListHandler = new ListHandler(competitorProvider.getList()); - VerticalPanel mainPanel = new VerticalPanel(); - this.setWidget(mainPanel); - mainPanel.setWidth("100%"); - HorizontalPanel competitorsPanel = new HorizontalPanel(); - competitorsPanel.setSpacing(5); - mainPanel.add(competitorsPanel); - HorizontalPanel buttonPanel = new HorizontalPanel(); - buttonPanel.setSpacing(5); - Button refreshButton = new Button(stringMessages.refresh()); - refreshButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - refreshCompetitorList(); - } - }); - buttonPanel.add(refreshButton); - final Button allowReloadButton = new Button(stringMessages.allowReload()); - allowReloadButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - allowUpdate(competitorSelectionModel.getSelectedSet()); - } - }); - buttonPanel.add(allowReloadButton); - competitorsPanel.add(buttonPanel); - - // competitors table - TextColumn competitorNameColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getName(); - } - }; - competitorNameColumn.setSortable(true); - competitorColumnListHandler.setComparator(competitorNameColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - - TextColumn boatClassColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getBoatClass() != null ? competitor.getBoatClass().getName() : ""; - } - }; - boatClassColumn.setSortable(true); - competitorColumnListHandler.setComparator(boatClassColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getBoatClass().getName(), o2.getBoatClass().getName()); - } - }); - - Column sailIdColumn = new Column(new SafeHtmlCell()) { - @Override - public SafeHtml getValue(CompetitorDTO competitor) { - SafeHtmlBuilder sb = new SafeHtmlBuilder(); - ImageResourceRenderer renderer = new ImageResourceRenderer(); - final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); - final ImageResource flagImageResource; - if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { - flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); - } else { - flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); - } - if (flagImageResource != null) { - sb.append(renderer.render(flagImageResource)); - sb.appendHtmlConstant(" "); - } - sb.appendEscaped(competitor.getSailID()); - return sb.toSafeHtml(); - } - }; - sailIdColumn.setSortable(true); - competitorColumnListHandler.setComparator(sailIdColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getSailID(), o2.getSailID()); - } - }); - - ImagesBarColumn competitorActionColumn = new ImagesBarColumn( - new CompetitorConfigImagesBarCell(stringMessages)); - competitorActionColumn.setFieldUpdater(new FieldUpdater() { - @Override - public void update(int index, final CompetitorDTO competitor, String value) { - if (CompetitorConfigImagesBarCell.ACTION_EDIT.equals(value)) { - openEditCompetitorDialog(competitor); - } else if (CompetitorConfigImagesBarCell.ACTION_REFRESH.equals(value)) { - allowUpdate(Collections.singleton(competitor)); - } - } - - }); - - TextColumn competitorIdColumn = new TextColumn() { - @Override - public String getValue(CompetitorDTO competitor) { - return competitor.getIdAsString(); - } - - }; - - competitorIdColumn.setSortable(true); - competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { - @Override - public int compare(CompetitorDTO o1, CompetitorDTO o2) { - return new NaturalComparator(false).compare(o1.getIdAsString(), o2.getIdAsString()); - } - }); - competitorTable = new CellTable(10000, tableRes); - filterField = new AbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), - allCompetitors, competitorTable, competitorProvider) { - - @Override - public Iterable getSearchableStrings(CompetitorDTO t) { - List string = new ArrayList(); - string.add(t.getName()); - string.add(t.getSailID()); - string.add(t.getBoatClass().getName()); - return string; - } - }; - - mainPanel.add(filterField); - competitorProvider.addDataDisplay(competitorTable); - competitorTable.addColumnSortHandler(competitorColumnListHandler); - competitorTable.addColumn(sailIdColumn, stringMessages.sailNumber()); - competitorTable.addColumn(competitorNameColumn, stringMessages.name()); - competitorTable.addColumn(boatClassColumn, stringMessages.boatClass()); - competitorTable.addColumn(competitorIdColumn, stringMessages.id()); - competitorTable.addColumn(competitorActionColumn, stringMessages.actions()); - competitorSelectionModel = new MultiSelectionModel(); - competitorTable.setSelectionModel(competitorSelectionModel); - mainPanel.add(competitorTable); - competitorSelectionModel.addSelectionChangeHandler(new Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); - } - }); - allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); - } - - private void allowUpdate(final Iterable competitors) { - List serializableSingletonList = new ArrayList(); - Util.addAll(competitors, serializableSingletonList); - sailingService.allowCompetitorResetToDefaults(serializableSingletonList, new AsyncCallback() { - @Override public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to allow resetting competitors "+competitors - +" to defaults: "+caught.getMessage()); - } - @Override public void onSuccess(Void result) { - Window.alert(stringMessages.successfullyAllowedCompetitorReset(competitors.toString())); - } - }); - } - - private void openEditCompetitorDialog(CompetitorDTO competitor) { - new CompetitorEditDialog(stringMessages, competitor, new DialogCallback() { - @Override - public void ok(CompetitorDTO competitor) { - sailingService.updateCompetitor(competitor, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error trying to update competitor: "+caught.getMessage()); - } - - @Override - public void onSuccess(CompetitorDTO updatedCompetitor) { - // replace updated competitor in competitor list - for (int i=0; i>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); - } - - @Override - public void onSuccess(Iterable result) { - allCompetitors = new ArrayList(); - for (CompetitorDTO c : result) { - allCompetitors.add(c); - } - filterField.updateAll(allCompetitors); - } - }); - } - - -} +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.cell.client.FieldUpdater; +import com.google.gwt.cell.client.SafeHtmlCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.ImageResourceRenderer; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +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.SelectionChangeEvent.Handler; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.impl.NaturalComparator; +import com.sap.sailing.domain.common.impl.Util; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.panels.AbstractFilterablePanel; + +/** + * Allows an administrator to view and edit the set of competitors currently maintained by the server. + * + * @author Axel Uhl (d043530) + * + */ +public class CompetitorPanel extends SimplePanel { + private final SailingServiceAsync sailingService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private CellTable competitorTable; + private MultiSelectionModel competitorSelectionModel; + private ListDataProvider competitorProvider; + private List allCompetitors; + private final String leaderboardName; + private AbstractFilterablePanel filterField; + + private final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + + public CompetitorPanel(final SailingServiceAsync sailingService, final StringMessages stringMessages, + final ErrorReporter errorReporter) { + this(sailingService, null, stringMessages, errorReporter); + } + + public CompetitorPanel(final SailingServiceAsync sailingService, String leaderboardName, final StringMessages stringMessages, + final ErrorReporter errorReporter) { + super(); + this.sailingService = sailingService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + this.leaderboardName = leaderboardName; + competitorProvider = new ListDataProvider(); + ListHandler competitorColumnListHandler = new ListHandler(competitorProvider.getList()); + VerticalPanel mainPanel = new VerticalPanel(); + this.setWidget(mainPanel); + mainPanel.setWidth("100%"); + HorizontalPanel competitorsPanel = new HorizontalPanel(); + competitorsPanel.setSpacing(5); + mainPanel.add(competitorsPanel); + HorizontalPanel buttonPanel = new HorizontalPanel(); + buttonPanel.setSpacing(5); + Button refreshButton = new Button(stringMessages.refresh()); + refreshButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + refreshCompetitorList(); + } + }); + buttonPanel.add(refreshButton); + final Button allowReloadButton = new Button(stringMessages.allowReload()); + allowReloadButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + allowUpdate(competitorSelectionModel.getSelectedSet()); + } + }); + buttonPanel.add(allowReloadButton); + competitorsPanel.add(buttonPanel); + + // competitors table + TextColumn competitorNameColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getName(); + } + }; + competitorNameColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorNameColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + TextColumn boatClassColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getBoatClass() != null ? competitor.getBoatClass().getName() : ""; + } + }; + boatClassColumn.setSortable(true); + competitorColumnListHandler.setComparator(boatClassColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getBoatClass().getName(), o2.getBoatClass().getName()); + } + }); + + Column sailIdColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(CompetitorDTO competitor) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + ImageResourceRenderer renderer = new ImageResourceRenderer(); + final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); + final ImageResource flagImageResource; + if (twoLetterIsoCountryCode==null || twoLetterIsoCountryCode.isEmpty()) { + flagImageResource = FlagImageResolver.getEmptyFlagImageResource(); + } else { + flagImageResource = FlagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); + } + if (flagImageResource != null) { + sb.append(renderer.render(flagImageResource)); + sb.appendHtmlConstant(" "); + } + sb.appendEscaped(competitor.getSailID()); + return sb.toSafeHtml(); + } + }; + sailIdColumn.setSortable(true); + competitorColumnListHandler.setComparator(sailIdColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getSailID(), o2.getSailID()); + } + }); + + Column displayColorColumn = new Column(new SafeHtmlCell()) { + @Override + public SafeHtml getValue(CompetitorDTO competitor) { + SafeHtmlBuilder sb = new SafeHtmlBuilder(); + if (competitor.getColor() != null) { + sb.appendHtmlConstant(""); + sb.appendHtmlConstant(competitor.getColor().getAsHtml()); + sb.appendHtmlConstant(""); + } else { + sb.appendHtmlConstant(" "); + } + return sb.toSafeHtml(); + } + }; + + ImagesBarColumn competitorActionColumn = new ImagesBarColumn( + new CompetitorConfigImagesBarCell(stringMessages)); + competitorActionColumn.setFieldUpdater(new FieldUpdater() { + @Override + public void update(int index, final CompetitorDTO competitor, String value) { + if (CompetitorConfigImagesBarCell.ACTION_EDIT.equals(value)) { + openEditCompetitorDialog(competitor); + } else if (CompetitorConfigImagesBarCell.ACTION_REFRESH.equals(value)) { + allowUpdate(Collections.singleton(competitor)); + } + } + + }); + + TextColumn competitorIdColumn = new TextColumn() { + @Override + public String getValue(CompetitorDTO competitor) { + return competitor.getIdAsString(); + } + + }; + + competitorIdColumn.setSortable(true); + competitorColumnListHandler.setComparator(competitorIdColumn, new Comparator() { + @Override + public int compare(CompetitorDTO o1, CompetitorDTO o2) { + return new NaturalComparator(false).compare(o1.getIdAsString(), o2.getIdAsString()); + } + }); + competitorTable = new CellTable(10000, tableRes); + filterField = new AbstractFilterablePanel(new Label(stringMessages.filterCompetitors()), + allCompetitors, competitorTable, competitorProvider) { + + @Override + public Iterable getSearchableStrings(CompetitorDTO t) { + List string = new ArrayList(); + string.add(t.getName()); + string.add(t.getSailID()); + string.add(t.getBoatClass().getName()); + return string; + } + }; + + mainPanel.add(filterField); + competitorProvider.addDataDisplay(competitorTable); + competitorTable.addColumnSortHandler(competitorColumnListHandler); + competitorTable.addColumn(sailIdColumn, stringMessages.sailNumber()); + competitorTable.addColumn(competitorNameColumn, stringMessages.name()); + competitorTable.addColumn(boatClassColumn, stringMessages.boatClass()); + competitorTable.addColumn(displayColorColumn, stringMessages.color()); + competitorTable.addColumn(competitorIdColumn, stringMessages.id()); + competitorTable.addColumn(competitorActionColumn, stringMessages.actions()); + competitorSelectionModel = new MultiSelectionModel(); + competitorTable.setSelectionModel(competitorSelectionModel); + mainPanel.add(competitorTable); + competitorSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); + } + }); + allowReloadButton.setEnabled(!competitorSelectionModel.getSelectedSet().isEmpty()); + + if(leaderboardName != null) { + refreshCompetitorList(); + } + } + + private void allowUpdate(final Iterable competitors) { + List serializableSingletonList = new ArrayList(); + Util.addAll(competitors, serializableSingletonList); + sailingService.allowCompetitorResetToDefaults(serializableSingletonList, new AsyncCallback() { + @Override public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to allow resetting competitors "+competitors + +" to defaults: "+caught.getMessage()); + } + @Override public void onSuccess(Void result) { + Window.alert(stringMessages.successfullyAllowedCompetitorReset(competitors.toString())); + } + }); + } + + private void openEditCompetitorDialog(CompetitorDTO competitor) { + new CompetitorEditDialog(stringMessages, competitor, new DialogCallback() { + @Override + public void ok(CompetitorDTO competitor) { + sailingService.updateCompetitor(competitor, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error trying to update competitor: "+caught.getMessage()); + } + + @Override + public void onSuccess(CompetitorDTO updatedCompetitor) { + // replace updated competitor in competitor list + for (int i=0; i>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); + } + + @Override + public void onSuccess(Iterable result) { + getFilteredCompetitors(result); + } + }); + } else { + sailingService.getCompetitors(new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Remote Procedure Call getCompetitors() - Failure: " + caught.getMessage()); + } + + @Override + public void onSuccess(Iterable result) { + getFilteredCompetitors(result); + } + }); + } + } + + private void getFilteredCompetitors(Iterable result) { + allCompetitors = new ArrayList(); + for (CompetitorDTO c : result) { + allCompetitors.add(c); + } + filterField.updateAll(allCompetitors); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java new file mode 100644 index 00000000000..5ca450779eb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateMatcherDialog.java @@ -0,0 +1,179 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.PushButton; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationCreateMatcherDialog extends DataEntryDialog { + + public static class MatcherValidator implements Validator { + + private List allMatchers; + + public MatcherValidator(List allMatchers) { + this.allMatchers = allMatchers; + } + + @Override + public String getErrorMessage(DeviceConfigurationMatcherDTO valueToValidate) { + for (DeviceConfigurationMatcherDTO existingMatcher : allMatchers) { + if (existingMatcher.type.equals(valueToValidate.type)) { + switch (valueToValidate.type) { + case SINGLE: + if (existingMatcher.clients.containsAll(valueToValidate.clients)) { + return "There is already a configuration for such a matcher."; + } + break; + case MULTI: + if (existingMatcher.clients.containsAll(valueToValidate.clients) && + valueToValidate.clients.containsAll(existingMatcher.clients)) { + return "There is already a configuration for such a matcher."; + } + break; + default: + break; + } + } + } + for (String identifier : valueToValidate.clients) { + if (identifier.isEmpty()) { + return "Enter an identifier name"; + } + } + return null; + } + + } + + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private ListBox typeBox; + private List devicesBoxes; + + public DeviceConfigurationCreateMatcherDialog(StringMessages stringMessages, + Validator validator, + com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback callback) { + super("Create Device Configuration", "Specify for which devices the new configuration should apply", + "Create", stringMessages.cancel(), validator, callback); + this.devicesBoxes = new ArrayList(); + } + + @Override + protected Widget getAdditionalWidget() { + final Grid valueGrid = new Grid(2, 2); + + valueGrid.setWidget(0, 0, createLabel("Matcher type")); + typeBox = createListBox(false); + typeBox.addItem(DeviceConfigurationMatcherType.SINGLE.name()); + typeBox.addItem(DeviceConfigurationMatcherType.MULTI.name()); + valueGrid.setWidget(0, 1, typeBox); + valueGrid.setWidget(1, 0, createLabel("Matching devices")); + + final Grid devicesGrid = new Grid(2, 2); + TextBox firstDeviceBox = createTextBox(""); + devicesGrid.setWidget(0, 0, firstDeviceBox); + devicesBoxes.add(firstDeviceBox); + + final Button addDeviceButton = new Button("Add Device"); + addDeviceButton.setVisible(false); + addDeviceButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + devicesGrid.insertRow(0); + TextBox deviceBox = createTextBox(""); + devicesBoxes.add(deviceBox); + devicesGrid.setWidget(0, 0, deviceBox); + devicesGrid.setWidget(0, 1, createRemoveButton(devicesGrid)); + } + }); + devicesGrid.setWidget(1, 0, addDeviceButton); + valueGrid.setWidget(1, 1, devicesGrid); + + typeBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + DeviceConfigurationMatcherType type = DeviceConfigurationMatcherType.valueOf(typeBox.getValue(typeBox.getSelectedIndex())); + switch (type) { + case SINGLE: + valueGrid.getWidget(1, 0).setVisible(true); + valueGrid.getWidget(1, 1).setVisible(true); + devicesGrid.getWidget(0, 0).setVisible(true); + devicesGrid.getWidget(1, 0).setVisible(false); + for (int i = 0; i < devicesGrid.getRowCount() - 2; i++) { + devicesGrid.getWidget(i, 0).setVisible(false); + devicesGrid.getWidget(i, 1).setVisible(false); + } + break; + case MULTI: + valueGrid.getWidget(1, 0).setVisible(true); + valueGrid.getWidget(1, 1).setVisible(true); + devicesGrid.getWidget(0, 0).setVisible(true); + devicesGrid.getWidget(1, 0).setVisible(true); + for (int i = 0; i < devicesGrid.getRowCount() - 2; i++) { + devicesGrid.getWidget(i, 0).setVisible(true); + devicesGrid.getWidget(i, 1).setVisible(true); + } + break; + default: + valueGrid.getWidget(1, 0).setVisible(false); + valueGrid.getWidget(1, 1).setVisible(false); + break; + } + } + }); + + return valueGrid; + } + + @Override + protected DeviceConfigurationMatcherDTO getResult() { + DeviceConfigurationMatcherDTO dto = new DeviceConfigurationMatcherDTO(); + dto.type = DeviceConfigurationMatcherType.valueOf(typeBox.getValue(typeBox.getSelectedIndex())); + switch (dto.type) { + case SINGLE: + dto.clients = Arrays.asList(devicesBoxes.get(0).getText()); + break; + case MULTI: + dto.clients = new ArrayList(); + for (TextBox textBox : devicesBoxes) { + dto.clients.add(textBox.getText()); + } + break; + default: + dto.clients = Collections.emptyList(); + break; + } + return dto; + } + + private PushButton createRemoveButton(final Grid devicesGrid) { + PushButton removeButton = new PushButton(new Image(resources.removeIcon())); + removeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + int rowToBeRemoved = devicesGrid.getCellForEvent(event).getRowIndex(); + devicesBoxes.remove(devicesGrid.getWidget(rowToBeRemoved, 0)); + devicesGrid.removeRow(rowToBeRemoved); + } + }); + return removeButton; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateSingleMatcherDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateSingleMatcherDialog.java new file mode 100644 index 00000000000..37d0a172d92 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationCreateSingleMatcherDialog.java @@ -0,0 +1,40 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; + +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.configuration.DeviceConfigurationMatcherType; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationCreateSingleMatcherDialog extends DataEntryDialog { + + private TextBox identifierBox; + + public DeviceConfigurationCreateSingleMatcherDialog(StringMessages stringMessages, + Validator validator, + com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback callback) { + super("Create Device Configuration", "Specify for which device the new configuration should apply", + "Create", stringMessages.cancel(), validator, callback); + this.identifierBox = createTextBox(""); + } + + @Override + protected Widget getAdditionalWidget() { + Grid grid = new Grid(1, 2); + grid.setWidget(0, 0, createLabel("Identifier")); + grid.setWidget(0, 1, identifierBox); + return grid; + } + + @Override + protected DeviceConfigurationMatcherDTO getResult() { + DeviceConfigurationMatcherDTO matcher = new DeviceConfigurationMatcherDTO(); + matcher.type = DeviceConfigurationMatcherType.SINGLE; + matcher.clients = Arrays.asList(identifierBox.getValue()); + return matcher; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java new file mode 100644 index 00000000000..570a91bedc9 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationDetailComposite.java @@ -0,0 +1,311 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Image; +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.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; +import com.sap.sailing.gwt.ui.shared.ListEditorComposite; +import com.sap.sailing.gwt.ui.shared.StringListEditorComposite; + +public class DeviceConfigurationDetailComposite extends Composite { + + public interface DeviceConfigurationCloneListener { + void onCloneRequested(DeviceConfigurationMatcherDTO matcher, DeviceConfigurationDTO configuration); + } + + private static List suggestedCourseAreaNames = Arrays.asList("Alpha", "Bravo", "Charly", "Delta", + "Foxtrott", "Stadium", "Offshore"); + private static List suggestedCourseNames = Arrays.asList("Upwind", "Downwind"); + + private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + protected final SailingServiceAsync sailingService; + protected final ErrorReporter errorReporter; + protected final StringMessages stringMessages; + + private final DeviceConfigurationCloneListener cloneListener; + + private CaptionPanel captionPanel; + private VerticalPanel contentPanel; + private Button cloneButton; + private Button updateButton; + + private DeviceConfigurationMatcherDTO matcher; + private DeviceConfigurationDTO originalConfiguration; + + protected TextBox identifierBox; + private ListEditorComposite allowedCourseAreasList; + private TextBox mailRecipientBox; + private ListEditorComposite courseNamesList; + + private CheckBox overwriteRegattaConfigurationBox; + private RegattaConfigurationDTO currentRegattaConfiguration; + + + public DeviceConfigurationDetailComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages, + final DeviceConfigurationCloneListener listener) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + + this.cloneListener = listener; + + this.matcher = null; + this.originalConfiguration = null; + this.currentRegattaConfiguration = null; + + captionPanel = new CaptionPanel(stringMessages.configuration()); + VerticalPanel verticalPanel = new VerticalPanel(); + contentPanel = new VerticalPanel(); + cloneButton = new Button(stringMessages.save() + " + " + "Clone"); + cloneButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + updateConfiguration(); + cloneListener.onCloneRequested(matcher, getResult()); + } + }); + updateButton = new Button(stringMessages.save()); + updateButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + updateConfiguration(); + } + }); + verticalPanel.add(contentPanel); + + verticalPanel.add(new HTML("
")); + + HorizontalPanel actionPanel = new HorizontalPanel(); + actionPanel.add(cloneButton); + actionPanel.add(updateButton); + verticalPanel.add(actionPanel); + + captionPanel.add(verticalPanel); + initWidget(captionPanel); + } + + public void setConfiguration(final DeviceConfigurationMatcherDTO matcher) { + if (matcher == null) { + clearUi(); + } else { + sailingService.getDeviceConfiguration(matcher, new AsyncCallback() { + @Override + public void onSuccess(DeviceConfigurationDTO result) { + setupUi(matcher, result); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + } + + private void setupUi(DeviceConfigurationMatcherDTO matcher, DeviceConfigurationDTO result) { + clearUi(); + this.matcher = matcher; + this.originalConfiguration = result; + this.currentRegattaConfiguration = result.regattaConfiguration; + + setupGeneral(); + setupRegattaConfiguration(); + } + + private void setupRegattaConfiguration() { + Grid grid = new Grid(1, 3); + + final Button editButton = new Button(stringMessages.edit()); + overwriteRegattaConfigurationBox = new CheckBox(stringMessages.overwriteRacingProceduresConfiguration()); + overwriteRegattaConfigurationBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + editButton.setEnabled(event.getValue()); + } + }); + grid.setWidget(0, 0, overwriteRegattaConfigurationBox); + + editButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new RegattaConfigurationDialog(currentRegattaConfiguration == null ? new RegattaConfigurationDTO() : currentRegattaConfiguration, + stringMessages, new DialogCallback() { + @Override + public void ok(RegattaConfigurationDTO newConfiguration) { + currentRegattaConfiguration = newConfiguration; + markAsDrity(true); + } + + @Override + public void cancel() { + } + }).show();; + } + }); + + overwriteRegattaConfigurationBox.setValue(currentRegattaConfiguration != null); + editButton.setEnabled(currentRegattaConfiguration != null); + + Image helpImage = new Image(resources.help()); + helpImage.setAltText(stringMessages.overwriteRacingProceduresConfigurationHelpText()); + helpImage.setTitle(stringMessages.overwriteRacingProceduresConfigurationHelpText()); + + grid.setWidget(0, 1, editButton); + grid.setWidget(0, 2, helpImage); + contentPanel.add(grid); + } + + private void setupGeneral() { + Grid grid = new Grid(4, 2); + int row = 0; + setupIdentifier(grid, row++); + setupCourseAreasBox(grid, row++); + setupRecipientBox(grid, row++); + setupCourseNameBox(grid, row++); + + contentPanel.add(grid); + } + + protected void setupIdentifier(Grid grid, int gridRow) { + identifierBox = new TextBox(); + identifierBox.setWidth("80%"); + identifierBox.setText(DeviceConfigurationPanel.renderIdentifiers(matcher.clients)); + identifierBox.setReadOnly(true); + + grid.setWidget(gridRow, 0, new Label("Identifier")); + grid.setWidget(gridRow, 1, identifierBox); + } + + private void setupCourseAreasBox(Grid grid, int gridRow) { + List initialValues = originalConfiguration.allowedCourseAreaNames == null ? Collections + . emptyList() : originalConfiguration.allowedCourseAreaNames; + + allowedCourseAreasList = StringListEditorComposite.createCollapsed(initialValues, stringMessages, stringMessages.courseAreas(), resources.removeIcon(), suggestedCourseAreaNames); + allowedCourseAreasList.setWidth("80%"); + allowedCourseAreasList.addValueChangeHandler(dirtyValueMarker); + + grid.setWidget(gridRow, 0, new Label(stringMessages.allowedCourseAreas())); + grid.setWidget(gridRow, 1, allowedCourseAreasList); + } + + private void setupCourseNameBox(Grid grid, int gridRow) { + List initialValues = originalConfiguration.byNameDesignerCourseNames == null ? Collections + . emptyList() : originalConfiguration.byNameDesignerCourseNames; + + courseNamesList = StringListEditorComposite.createCollapsed(initialValues, stringMessages, stringMessages.courseNames(), resources.removeIcon(), suggestedCourseNames); + courseNamesList.setWidth("80%"); + courseNamesList.addValueChangeHandler(dirtyValueMarker); + grid.setWidget(gridRow, 0, new Label(stringMessages.courseNames())); + grid.setWidget(gridRow, 1, courseNamesList); + } + + private void setupRecipientBox(Grid grid, int gridRow) { + mailRecipientBox = new TextBox(); + mailRecipientBox.setWidth("80%"); + mailRecipientBox.addKeyUpHandler(dirtyMarker); + if (originalConfiguration.resultsMailRecipient != null) { + mailRecipientBox.setText(originalConfiguration.resultsMailRecipient); + } + grid.setWidget(gridRow, 0, new Label(stringMessages.resultsMailRecipient())); + grid.setWidget(gridRow, 1, mailRecipientBox); + } + + private void clearUi() { + this.matcher = null; + this.originalConfiguration = null; + contentPanel.clear(); + } + + private void markAsDrity(boolean dirty) { + if (dirty && !(captionPanel.getTitle() == stringMessages.configuration())) { + captionPanel.setCaptionText(stringMessages.configuration() + "* (CHANGED)"); + } else { + captionPanel.setCaptionText(stringMessages.configuration()); + } + } + + private DeviceConfigurationDTO getResult() { + DeviceConfigurationDTO result = new DeviceConfigurationDTO(); + + if (!allowedCourseAreasList.getValue().isEmpty()) { + result.allowedCourseAreaNames = allowedCourseAreasList.getValue(); + } + + if (!mailRecipientBox.getText().isEmpty()) { + result.resultsMailRecipient = mailRecipientBox.getText().isEmpty() ? null : mailRecipientBox.getText(); + } + + if (!courseNamesList.getValue().isEmpty()) { + result.byNameDesignerCourseNames = courseNamesList.getValue(); + } + + if (overwriteRegattaConfigurationBox.getValue()) { + result.regattaConfiguration = currentRegattaConfiguration; + } + + return result; + } + + private void updateConfiguration() { + if (matcher == null || originalConfiguration == null) { + errorReporter.reportError("Invalid state."); + return; + } + DeviceConfigurationDTO dto = getResult(); + sailingService.createOrUpdateDeviceConfiguration(matcher, dto, + new AsyncCallback() { + @Override + public void onSuccess(DeviceConfigurationMatcherDTO matcher) { + markAsDrity(false); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + + private KeyUpHandler dirtyMarker = new KeyUpHandler() { + @Override + public void onKeyUp(KeyUpEvent event) { + markAsDrity(true); + } + }; + + private ValueChangeHandler> dirtyValueMarker = new ValueChangeHandler>() { + @Override + public void onValueChange(ValueChangeEvent> event) { + markAsDrity(true); + } + }; + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java new file mode 100644 index 00000000000..53811a101aa --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationListComposite.java @@ -0,0 +1,161 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.ColumnSortEvent; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +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.SelectionModel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SelectionProvider; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationListComposite extends Composite { + + protected static AdminConsoleTableResources tableResource = GWT.create(AdminConsoleTableResources.class); + + private final SelectionModel configurationSelectionModel; + private final CellTable configurationTable; + protected ListDataProvider configurationsDataProvider; + + private final SimplePanel mainPanel; + private final VerticalPanel panel; + + private final Label noConfigurationsLabel; + + private final SailingServiceAsync sailingService; + private final ErrorReporter errorReporter; + private final StringMessages stringMessages; + + public DeviceConfigurationListComposite(final SailingServiceAsync sailingService, + final SelectionProvider selectionProvider, + final ErrorReporter errorReporter, final StringMessages stringMessages) { + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + + mainPanel = new SimplePanel(); + panel = new VerticalPanel(); + mainPanel.setWidget(panel); + + noConfigurationsLabel = new Label(stringMessages.noConfigurations()); + noConfigurationsLabel.setWordWrap(false); + panel.add(noConfigurationsLabel); + + configurationsDataProvider = new ListDataProvider(); + refreshTable(); + + configurationTable = createConfigurationTable(); + configurationTable.setVisible(true); + + configurationSelectionModel = new MultiSelectionModel(); + configurationSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { + @SuppressWarnings("unchecked") + @Override + public void onSelectionChange(SelectionChangeEvent event) { + selectionProvider.setSelection(getSelectedConfigurations()); + } + }); + configurationTable.setSelectionModel(configurationSelectionModel); + panel.add(configurationTable); + + initWidget(mainPanel); + } + + public void refreshTable() { + sailingService.getDeviceConfigurationMatchers(new AsyncCallback>() { + + @Override + public void onSuccess(List result) { + if (configurationsDataProvider.getList().isEmpty()) { + configurationTable.getColumnSortList().clear(); + configurationTable.getColumnSortList().push(configurationTable.getColumn(0)); + } + configurationsDataProvider.getList().clear(); + configurationsDataProvider.getList().addAll(result); + ColumnSortEvent.fire(configurationTable, configurationTable.getColumnSortList()); + + noConfigurationsLabel.setVisible(false); + } + + @Override + public void onFailure(Throwable caught) { + noConfigurationsLabel.setText(stringMessages.errorRetrievingConfiguration()); + noConfigurationsLabel.setVisible(true); + configurationTable.setVisible(false); + errorReporter.reportError("Error retrieving configuration data from server: " + caught.getMessage()); + } + }); + } + + protected CellTable createConfigurationTable() { + CellTable table = new CellTable( + /* pageSize */10000, tableResource); + configurationsDataProvider.addDataDisplay(table); + table.setWidth("100%"); + + ListHandler columnSortHandler = + new ListHandler(configurationsDataProvider.getList()); + table.addColumnSortHandler(columnSortHandler); + + TextColumn identifierTypeColumn = + new TextColumn() { + @Override + public String getValue(DeviceConfigurationMatcherDTO identifier) { + return identifier.type.name(); + } + }; + identifierTypeColumn.setSortable(true); + columnSortHandler.setComparator(identifierTypeColumn, new Comparator() { + @Override + public int compare(DeviceConfigurationMatcherDTO r1, DeviceConfigurationMatcherDTO r2) { + return new Integer(r1.rank).compareTo(r2.rank); + } + }); + + TextColumn identifierNameColumn = + new TextColumn() { + @Override + public String getValue(DeviceConfigurationMatcherDTO identifier) { + return DeviceConfigurationPanel.renderIdentifiers(identifier.clients); + } + }; + identifierNameColumn.setSortable(true); + columnSortHandler.setComparator(identifierNameColumn, new Comparator() { + @Override + public int compare(DeviceConfigurationMatcherDTO r1, DeviceConfigurationMatcherDTO r2) { + return r1.toString().compareTo(r2.toString()); + } + }); + + table.addColumn(identifierTypeColumn, "Matcher"); + table.addColumn(identifierNameColumn, "Device"); + return table; + } + + private List getSelectedConfigurations() { + List result = new ArrayList(); + if (configurationsDataProvider != null) { + for (DeviceConfigurationMatcherDTO regatta : configurationsDataProvider.getList()) { + if (configurationSelectionModel.isSelected(regatta)) { + result.add(regatta); + } + } + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java new file mode 100644 index 00000000000..ab1d7063fc3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationPanel.java @@ -0,0 +1,219 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Arrays; +import java.util.List; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.adminconsole.DeviceConfigurationDetailComposite.DeviceConfigurationCloneListener; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SelectionChangeListener; +import com.sap.sailing.gwt.ui.client.SelectionProvider; +import com.sap.sailing.gwt.ui.client.SelectionProviderImpl; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationPanel extends SimplePanel implements SelectionChangeListener, DeviceConfigurationCloneListener { + + public static String renderIdentifiers(List clientIdentifiers) { + if (clientIdentifiers.size() == 1) { + return clientIdentifiers.get(0); + } else if (clientIdentifiers.size() == 0) { + return "[ANY]"; + } else { + return clientIdentifiers.toString(); + } + } + + private final SailingServiceAsync sailingService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + + private Button addConfigurationButton; + private Button removeConfigurationButton; + private Button refreshConfigurationsButton; + private DeviceConfigurationListComposite listComposite; + private DeviceConfigurationDetailComposite detailComposite; + + private final SelectionProvider selectionProvider; + + public DeviceConfigurationPanel(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter reporter) { + this.sailingService = sailingService; + this.stringMessages = stringMessages; + this.errorReporter = reporter; + + this.selectionProvider = new SelectionProviderImpl(true); + this.selectionProvider.addSelectionChangeListener(this); + + setupUi(); + } + + private void setupUi() { + VerticalPanel mainPanel = new VerticalPanel(); + setWidget(mainPanel); + mainPanel.setWidth("100%"); + setupControlPanel(mainPanel); + setupConfigurationPanels(mainPanel); + } + + private void setupControlPanel(VerticalPanel mainPanel) { + HorizontalPanel deviceManagementControlPanel = new HorizontalPanel(); + deviceManagementControlPanel.setSpacing(5); + addConfigurationButton = new Button(stringMessages.addConfiguration()); + addConfigurationButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + createConfiguration(); + } + }); + deviceManagementControlPanel.add(addConfigurationButton); + + removeConfigurationButton = new Button(stringMessages.remove()); + removeConfigurationButton.setEnabled(false); + removeConfigurationButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + removeConfiguration(); + } + }); + deviceManagementControlPanel.add(removeConfigurationButton); + + refreshConfigurationsButton = new Button(stringMessages.refresh()); + refreshConfigurationsButton.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + detailComposite.setConfiguration(null); + detailComposite.setVisible(false); + listComposite.refreshTable(); + } + }); + deviceManagementControlPanel.add(refreshConfigurationsButton); + + mainPanel.add(deviceManagementControlPanel); + } + + private void setupConfigurationPanels(VerticalPanel mainPanel) { + Grid grid = new Grid(1 ,2); + mainPanel.add(grid); + + listComposite = createListComposite(sailingService, selectionProvider, errorReporter, stringMessages); + grid.setWidget(0, 0, listComposite); + grid.getRowFormatter().setVerticalAlign(0, HasVerticalAlignment.ALIGN_TOP); + grid.getColumnFormatter().getElement(1).getStyle().setPaddingTop(2.0, Unit.EM); + + detailComposite = createDetailComposite(sailingService, errorReporter, stringMessages, this); + detailComposite.setVisible(false); + grid.setWidget(0, 1, detailComposite); + } + + @Override + public void onSelectionChange(List selectedConfigurations) { + if (selectedConfigurations.size() == 1 && selectedConfigurations.iterator().hasNext()) { + detailComposite.setConfiguration(selectedConfigurations.iterator().next()); + detailComposite.setVisible(true); + } else { + detailComposite.setConfiguration(null); + detailComposite.setVisible(false); + } + removeConfigurationButton.setEnabled(!selectedConfigurations.isEmpty()); + } + + private void createConfiguration() { + createConfiguration(new DeviceConfigurationDTO()); + } + + private void createConfiguration(final DeviceConfigurationDTO configuration) { + sailingService.getDeviceConfigurationMatchers(new AsyncCallback>() { + @Override + public void onSuccess(List allMatchers) { + createConfiguration(allMatchers, configuration); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + + private void createConfiguration(final List allMatchers, final DeviceConfigurationDTO configuration) { + getCreateDialog(stringMessages, + new DeviceConfigurationCreateMatcherDialog.MatcherValidator(allMatchers), + new DialogCallback() { + @Override + public void ok(DeviceConfigurationMatcherDTO createdMatcher) { + sailingService.createOrUpdateDeviceConfiguration(createdMatcher, configuration, + new AsyncCallback() { + @Override + public void onSuccess(DeviceConfigurationMatcherDTO newMatcher) { + listComposite.refreshTable(); + onSelectionChange(Arrays.asList(newMatcher)); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + + @Override + public void cancel() { } + }).show(); + } + + protected DataEntryDialog getCreateDialog(final StringMessages stringMessages, + final Validator validator, + final DialogCallback callback) { + return new DeviceConfigurationCreateMatcherDialog(stringMessages, validator, callback); + } + + protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, SelectionProvider selectionProvider, + ErrorReporter errorReporter, StringMessages stringMessages) { + return new DeviceConfigurationListComposite(sailingService, selectionProvider, + errorReporter, stringMessages); + } + + protected DeviceConfigurationDetailComposite createDetailComposite(SailingServiceAsync sailingService, + ErrorReporter errorReporter, StringMessages stringMessages, DeviceConfigurationCloneListener cloneListener) { + return new DeviceConfigurationDetailComposite(sailingService, errorReporter, stringMessages, cloneListener); + } + + private void removeConfiguration() { + detailComposite.setConfiguration(null); + detailComposite.setVisible(false); + for (DeviceConfigurationMatcherDTO identifier : selectionProvider.getSelectedItems()) { + sailingService.removeDeviceConfiguration(identifier.type, identifier.clients, new AsyncCallback() { + @Override + public void onSuccess(Boolean result) { + listComposite.refreshTable(); + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + }); + } + } + + @Override + public void onCloneRequested(DeviceConfigurationMatcherDTO matcher, DeviceConfigurationDTO configuration) { + createConfiguration(configuration); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java new file mode 100644 index 00000000000..039459b1b5e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java @@ -0,0 +1,105 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +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.sap.sailing.domain.common.impl.QRCodeUtils; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.QRCodeComposite; + +public class DeviceConfigurationQRIdentifierDialog extends DialogBox { + + public static final String rcAppApkPath = "/apps/com.sap.sailing.racecommittee.app.apk"; + + private static final int qrCodeSize = 320; + + private final TextBox identifierBox; + private final TextBox serverBox; + private final QRCodeComposite qrCodeComposite; + + protected static String getApkPath(String serverUrl) { + if (serverUrl.endsWith("/")) { + return serverUrl.substring(0, serverUrl.length() - 1) + rcAppApkPath; + } + return serverUrl + rcAppApkPath; + } + + public DeviceConfigurationQRIdentifierDialog(String identifier, StringMessages stringMessages) { + setText("Synchronize device with server"); + + identifierBox = new TextBox(); + identifierBox.setValue(identifier); + identifierBox.setReadOnly(true); + identifierBox.setVisibleLength(40); + + serverBox = new TextBox(); + serverBox.setVisibleLength(40); + serverBox.setValue(Window.Location.getProtocol() + "//" + Window.Location.getHost()); + serverBox.addKeyUpHandler(new KeyUpHandler() { + @Override + public void onKeyUp(KeyUpEvent event) { + generateQRCode(); + } + }); + serverBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + generateQRCode(); + } + }); + + Grid inputGrid = new Grid(2, 2); + inputGrid.setWidget(0, 0, new Label("Identifier:")); + inputGrid.setWidget(0, 1, identifierBox); + inputGrid.setWidget(1, 0, new Label("Server URL:")); + inputGrid.setWidget(1, 1, serverBox); + + qrCodeComposite = new QRCodeComposite(qrCodeSize); + + Button exitButton = new Button(stringMessages.close()); + exitButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + hide(); + } + }); + + HorizontalPanel actionPanel = new HorizontalPanel(); + actionPanel.add(exitButton); + + VerticalPanel panel = new VerticalPanel(); + panel.add(inputGrid); + panel.add(qrCodeComposite); + panel.setCellHorizontalAlignment(qrCodeComposite, HasHorizontalAlignment.ALIGN_CENTER); + panel.add(actionPanel); + setWidget(panel); + } + + @Override + protected void onAttach() { + super.onAttach(); + generateQRCode(); + } + + protected void generateQRCode() { + if (identifierBox.getValue().contains("#")) { + Window.alert("I'm not capable of generating a code for this identifier."); + } else if (!identifierBox.getValue().isEmpty() && !serverBox.getValue().isEmpty()) { + String encoded = QRCodeUtils.composeQRContent(identifierBox.getValue(), getApkPath(serverBox.getValue())); + qrCodeComposite.generateQRCode(encoded); + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserDetailComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserDetailComposite.java new file mode 100644 index 00000000000..6985905c447 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserDetailComposite.java @@ -0,0 +1,43 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class DeviceConfigurationUserDetailComposite extends DeviceConfigurationDetailComposite { + + public DeviceConfigurationUserDetailComposite(SailingServiceAsync sailingService, ErrorReporter errorReporter, + StringMessages stringMessages, DeviceConfigurationCloneListener listener) { + super(sailingService, errorReporter, stringMessages, listener); + } + + protected void setupIdentifier(Grid grid, int gridRow) { + super.setupIdentifier(grid, gridRow); + + HorizontalPanel panel = new HorizontalPanel(); + panel.add(identifierBox); + + Button qrCodeButton = new Button("QR-Sync"); + qrCodeButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + if (identifierBox.getValue() == null || identifierBox.getValue().isEmpty()) { + Window.alert("There is no identifier set."); + } else { + DialogBox dialog = new DeviceConfigurationQRIdentifierDialog(identifierBox.getValue(), stringMessages); + dialog.show(); + dialog.center(); + } + } + }); + panel.add(qrCodeButton); + grid.setWidget(gridRow, 1, panel); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java new file mode 100644 index 00000000000..ae4363c776b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserListComposite.java @@ -0,0 +1,52 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Comparator; + +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; +import com.google.gwt.user.cellview.client.TextColumn; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SelectionProvider; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationUserListComposite extends DeviceConfigurationListComposite { + + public DeviceConfigurationUserListComposite(SailingServiceAsync sailingService, + SelectionProvider selectionProvider, ErrorReporter errorReporter, + StringMessages stringMessages) { + super(sailingService, selectionProvider, errorReporter, stringMessages); + } + + @Override + protected CellTable createConfigurationTable() { + CellTable table = new CellTable( + /* pageSize */10000, tableResource); + configurationsDataProvider.addDataDisplay(table); + table.setWidth("100%"); + + ListHandler columnSortHandler = + new ListHandler(configurationsDataProvider.getList()); + table.addColumnSortHandler(columnSortHandler); + + TextColumn identifierNameColumn = + new TextColumn() { + @Override + public String getValue(DeviceConfigurationMatcherDTO identifier) { + return DeviceConfigurationPanel.renderIdentifiers(identifier.clients); + } + }; + identifierNameColumn.setSortable(true); + columnSortHandler.setComparator(identifierNameColumn, new Comparator() { + @Override + public int compare(DeviceConfigurationMatcherDTO r1, DeviceConfigurationMatcherDTO r2) { + return r1.toString().compareTo(r2.toString()); + } + }); + + table.addColumn(identifierNameColumn, "Device"); + return table; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java new file mode 100644 index 00000000000..3be2e7a6710 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationUserPanel.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.sap.sailing.gwt.ui.adminconsole.DeviceConfigurationDetailComposite.DeviceConfigurationCloneListener; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; +import com.sap.sailing.gwt.ui.client.DataEntryDialog.Validator; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SelectionProvider; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationMatcherDTO; + +public class DeviceConfigurationUserPanel extends DeviceConfigurationPanel { + + public DeviceConfigurationUserPanel(SailingServiceAsync sailingService, StringMessages stringMessages, + ErrorReporter reporter) { + super(sailingService, stringMessages, reporter); + } + + @Override + protected DeviceConfigurationListComposite createListComposite(SailingServiceAsync sailingService, SelectionProvider selectionProvider, + ErrorReporter errorReporter, StringMessages stringMessages) { + return new DeviceConfigurationUserListComposite(sailingService, selectionProvider, errorReporter, stringMessages); + } + + @Override + protected DeviceConfigurationDetailComposite createDetailComposite(SailingServiceAsync sailingService, + ErrorReporter errorReporter, StringMessages stringMessages, DeviceConfigurationCloneListener cloneListener) { + return new DeviceConfigurationUserDetailComposite(sailingService, errorReporter, stringMessages, cloneListener); + } + + @Override + protected DataEntryDialog getCreateDialog(StringMessages stringMessages, + Validator validator, DialogCallback callback) { + return new DeviceConfigurationCreateSingleMatcherDialog(stringMessages, validator, callback); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EditCompetitorsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EditCompetitorsDialog.java new file mode 100644 index 00000000000..3674c12e2df --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EditCompetitorsDialog.java @@ -0,0 +1,49 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.ErrorReporter; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class EditCompetitorsDialog extends DataEntryDialog> { + private final SailingServiceAsync sailingService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private final String leaderboardName; + + protected static class CompetitorsValidator implements Validator> { + public CompetitorsValidator() { + super(); + } + + @Override + public String getErrorMessage(List valueToValidate) { + return null; + } + } + + public EditCompetitorsDialog(final SailingServiceAsync sailingService, String leaderboardName, final StringMessages stringMessages, + final ErrorReporter errorReporter, DialogCallback> callback) { + super(stringMessages.actionEditCompetitors(), null, stringMessages.ok(), stringMessages.cancel(), new CompetitorsValidator(), callback); + this.sailingService = sailingService; + this.stringMessages = stringMessages; + this.errorReporter = errorReporter; + this.leaderboardName = leaderboardName; + } + + @Override + protected List getResult() { + return null; + } + + @Override + protected Widget getAdditionalWidget() { + CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, leaderboardName, stringMessages, errorReporter); + + return competitorPanel; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FleetListEditorComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FleetListEditorComposite.java new file mode 100644 index 00000000000..61b3e36aecd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FleetListEditorComposite.java @@ -0,0 +1,176 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CaptionPanel; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.IntegerBox; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.TextBox; +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.StringMessages; +import com.sap.sailing.gwt.ui.shared.ListEditorComposite; + +public class FleetListEditorComposite extends ListEditorComposite { + + public static ListEditorComposite createExpanded(List initialValues, + StringMessages stringMessages, ImageResource removeImage) { + return new FleetListEditorComposite(initialValues, new ExpandedUi(stringMessages, removeImage)); + } + + public FleetListEditorComposite(List initialValues, ListEditorUiStrategy activeUi) { + super(initialValues, activeUi); + } + + private static class ExpandedUi extends ExpandedListEditorUi { + + public ExpandedUi(StringMessages stringMessages, ImageResource removeImage) { + super(stringMessages, removeImage); + } + + @Override + protected Widget createAddWidget() { + CaptionPanel captionPanel = new CaptionPanel(stringMessages.addFleet()); + + HorizontalPanel panel = new HorizontalPanel(); + panel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + + final TextBox nameBox = createNameBox(); + final IntegerBox orderNoBox = createOrderNoBox(); + final ListBox colorListBox = createColorListBox(nameBox, orderNoBox); + final Button addButton = new Button(stringMessages.add()); + addButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + addValue(new FleetDTO(nameBox.getValue(), orderNoBox.getValue(), getSelectedColor(colorListBox))); + } + }); + + Grid fleetsGrid = new Grid(2, 3); + fleetsGrid.setCellSpacing(4); + fleetsGrid.setHTML(0, 0, stringMessages.color()); + fleetsGrid.setHTML(0, 1, stringMessages.name()); + fleetsGrid.setHTML(0, 2, stringMessages.rank()); + fleetsGrid.setWidget(1, 0, colorListBox); + fleetsGrid.setWidget(1, 1, nameBox); + fleetsGrid.setWidget(1, 2, orderNoBox); + + panel.add(fleetsGrid); + panel.add(addButton); + captionPanel.add(panel); + return captionPanel; + } + + @Override + protected Widget createValueWidget(FleetDTO value) { + HorizontalPanel panel = new HorizontalPanel(); + + final TextBox nameBox = createNameBox(); + nameBox.setValue(value.getName()); + nameBox.setEnabled(false); + nameBox.setReadOnly(true); + + final IntegerBox orderNoBox = createOrderNoBox(); + orderNoBox.setValue(value.getOrderNo()); + orderNoBox.setEnabled(false); + orderNoBox.setReadOnly(true); + + final ListBox colorListBox = createColorListBox(nameBox, orderNoBox); + colorListBox.setEnabled(false); + if (value.getColor() == null) { + colorListBox.setSelectedIndex(0); + } else { + for (int i = 1; i < colorListBox.getItemCount(); i++) { + String fleetsColorValue = colorListBox.getValue(i); + FleetColors fleetsColor = FleetColors.valueOf(fleetsColorValue); + if (fleetsColor.getColor().equals(value.getColor())) { + colorListBox.setSelectedIndex(i); + break; + } + } + } + + panel.add(colorListBox); + panel.add(nameBox); + panel.add(orderNoBox); + return panel; + } + + private IntegerBox createOrderNoBox() { + IntegerBox orderNoBox = new IntegerBox(); + orderNoBox.setVisibleLength(3); + orderNoBox.setValue(0); + return orderNoBox; + } + + private TextBox createNameBox() { + final TextBox nameBox = new TextBox(); + nameBox.setValue("Default"); + nameBox.setVisibleLength(40); + nameBox.setWidth("175px"); + return nameBox; + } + + private ListBox createColorListBox(final TextBox nameBox, final IntegerBox orderNoBox) { + final ListBox colorListBox = new ListBox(false); + colorListBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + // set default order no of the selected color + int selIndex = colorListBox.getSelectedIndex(); + if (selIndex == 0) { + orderNoBox.setValue(0); + } else { + String value = colorListBox.getValue(selIndex); + final FleetColors color = FleetColors.valueOf(value); + if (color != null) { + orderNoBox.setValue(color.getDefaultOrderNo()); + nameBox.setValue(getColorText(color)); + } + } + } + }); + colorListBox.addItem(stringMessages.noColor()); + for (FleetColors value : FleetColors.values()) { + colorListBox.addItem(value.name()); + } + colorListBox.setSelectedIndex(0); + return colorListBox; + } + + private Color getSelectedColor(ListBox colorListBox) { + Color result = null; + int selIndex = colorListBox.getSelectedIndex(); + // the zero index represents the 'no color' option + if (selIndex > 0) { + String value = colorListBox.getValue(selIndex); + for (FleetColors color : FleetColors.values()) { + if (color.name().equals(value)) { + result = color.getColor(); + break; + } + } + } + return result; + } + + private String getColorText(FleetColors color) { + if (color == null) { + return stringMessages.noColor(); + } + return color.name().charAt(0) + color.name().toLowerCase().substring(1); + } + + } + +}; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardDialog.java index cfde83c7a6e..f8e3610d6fc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardDialog.java @@ -80,7 +80,7 @@ public abstract class FlexibleLeaderboardDialog extends AbstractLeaderboardDialo if (courseArea == null) { leaderboard.setCourseAreaIdAsString(null); } else { - leaderboard.setCourseAreaIdAsString(getSelectedCourseArea().id); + leaderboard.setCourseAreaIdAsString(getSelectedCourseArea().uuidAsString); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java index f182de91b50..58c2efd007e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/FlexibleLeaderboardEditDialog.java @@ -38,7 +38,7 @@ public class FlexibleLeaderboardEditDialog extends FlexibleLeaderboardDialog { sailingEventsListBox = createSailingEventListBox(); for (EventDTO event : existingEvents) { for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) { - if (courseArea.id.equals(leaderboard.getCourseAreaIdAsString())) { + if (courseArea.uuidAsString.equals(leaderboard.getCourseAreaIdAsString())) { int index = existingEvents.indexOf(event) + 1; // + 1 because of the "Please select... item" sailingEventsListBox.setSelectedIndex(index); onEventSelectionChanged(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java index 4c138e93846..6fd4171d8b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigImagesBarCell.java @@ -10,6 +10,7 @@ import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; public class LeaderboardConfigImagesBarCell extends ImagesBarCell { public static final String ACTION_REMOVE = "ACTION_REMOVE"; public static final String ACTION_EDIT_SCORES = "ACTION_EDIT_SCORES"; + public static final String ACTION_EDIT_COMPETITORS = "ACTION_EDIT_COMPETITORS"; public static final String ACTION_EDIT = "ACTION_EDIT"; public static final String ACTION_CONFIGURE_URL = "ACTION_CONFIGURE_URL"; private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); @@ -29,6 +30,7 @@ public class LeaderboardConfigImagesBarCell extends ImagesBarCell { protected Iterable getImageSpecs() { return Arrays.asList(new ImageSpec(ACTION_EDIT, stringMessages.actionEdit(), makeImagePrototype(resources.editIcon())), new ImageSpec(ACTION_EDIT_SCORES, stringMessages.actionEditScores(), makeImagePrototype(resources.scoresIcon())), + new ImageSpec(ACTION_EDIT_COMPETITORS, stringMessages.actionEditCompetitors(), makeImagePrototype(resources.competitorsIcon())), new ImageSpec(ACTION_CONFIGURE_URL, stringMessages.actionConfigureUrl(), makeImagePrototype(resources.settingsActionIcon())), new ImageSpec(ACTION_REMOVE, stringMessages.actionRemove(), makeImagePrototype(resources.removeIcon()))); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index bfc430a8779..a35f0b9fa5c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -44,6 +44,7 @@ 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.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.NamedDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; @@ -52,7 +53,6 @@ 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; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; -import com.sap.sailing.gwt.ui.client.shared.panels.AbstractFilterablePanel; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.MarkedAsyncCallback; import com.sap.sailing.gwt.ui.client.ParallelExecutionCallback; @@ -64,10 +64,12 @@ import com.sap.sailing.gwt.ui.client.RegattaDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.URLEncoder; +import com.sap.sailing.gwt.ui.client.shared.panels.AbstractFilterablePanel; 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.EventDTO; +import com.sap.sailing.gwt.ui.shared.RaceLogSetStartTimeDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -167,7 +169,7 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb } } - public LeaderboardConfigPanel(SailingServiceAsync sailingService, AdminConsoleEntryPoint adminConsole, + public LeaderboardConfigPanel(final SailingServiceAsync sailingService, AdminConsoleEntryPoint adminConsole, final ErrorReporter errorReporter, StringMessages theStringConstants, final boolean showRaceDetails) { this.stringMessages = theStringConstants; this.sailingService = sailingService; @@ -346,6 +348,19 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb String debugParam = Window.Location.getParameter("gwt.codesvr"); Window.open("/gwt/LeaderboardEditing.html?name=" + leaderboardDTO.name + (debugParam != null && !debugParam.isEmpty() ? "&gwt.codesvr=" + debugParam : ""), "_blank", null); + } else if (LeaderboardConfigImagesBarCell.ACTION_EDIT_COMPETITORS.equals(value)) { + EditCompetitorsDialog editCompetitorsDialog = new EditCompetitorsDialog(sailingService, leaderboardDTO.name, stringMessages, + errorReporter, new DialogCallback>() { + @Override + public void cancel() { + } + + @Override + public void ok(final List result) { + } + }); + editCompetitorsDialog.show(); + } else if (LeaderboardConfigImagesBarCell.ACTION_CONFIGURE_URL.equals(value)) { openLeaderboardUrlConfigDialog(leaderboardDTO, stringMessages); } @@ -514,6 +529,8 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb unlinkRaceColumnFromTrackedRace(object.getA().getRaceColumnName(), object.getB()); } else if (LeaderboardRaceConfigImagesBarCell.ACTION_REFRESH_RACELOG.equals(value)) { refreshRaceLog(object.getA(), object.getB(), true); + } else if(LeaderboardRaceConfigImagesBarCell.ACTION_SET_STARTTIME.equals(value)) { + setStartTime(object.getA(), object.getB()); } } }); @@ -705,6 +722,31 @@ public class LeaderboardConfigPanel extends FormPanel implements SelectedLeaderb }); } + private void setStartTime(RaceColumnDTO raceColumnDTO, FleetDTO fleetDTO) { + new SetStartTimeDialog(sailingService, errorReporter, getSelectedLeaderboardName(), raceColumnDTO.getName(), + fleetDTO.getName(), stringMessages, new DialogCallback() { + @Override + public void ok(RaceLogSetStartTimeDTO editedObject) { + sailingService.setStartTime(editedObject, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(caught.getMessage()); + } + + @Override + public void onSuccess(Boolean result) { + if (!result) { + Window.alert("Failed to set new start time."); + } + } + }); + } + + @Override + public void cancel() { } + }).show(); + } + private void refreshRaceLog(final RaceColumnDTO raceColumnDTO, final FleetDTO fleet, final boolean showAlerts) { final String selectedLeaderboardName = getSelectedLeaderboardName(); sailingService.reloadRaceLog(selectedLeaderboardName, raceColumnDTO, fleet, new AsyncCallback() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java index 7b9312983e1..1d8ec4c7488 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardRaceConfigImagesBarCell.java @@ -10,10 +10,12 @@ import com.sap.sailing.gwt.ui.client.shared.components.ImagesBarCell; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { - public static final String ACTION_REMOVE = "ACTION_REMOVE"; - public static final String ACTION_UNLINK = "ACTION_UNLINK"; - public static final String ACTION_EDIT = "ACTION_EDIT"; + public final static String ACTION_REMOVE = "ACTION_REMOVE"; + public final static String ACTION_UNLINK = "ACTION_UNLINK"; + public final static String ACTION_EDIT = "ACTION_EDIT"; public final static String ACTION_REFRESH_RACELOG = "ACTION_REFRESH_RACE_LOG"; + public final static String ACTION_SET_STARTTIME = "ACTION_SET_STARTTIME"; + private final StringMessages stringMessages; private final SelectedLeaderboardProvider selectedLeaderboardProvider; private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); @@ -35,6 +37,7 @@ public class LeaderboardRaceConfigImagesBarCell extends ImagesBarCell { result.add(new ImageSpec(ACTION_REMOVE, stringMessages.actionRaceRemove(), makeImagePrototype(resources.removeIcon()))); } result.add(new ImageSpec(ACTION_REFRESH_RACELOG, stringMessages.refreshRaceLog(), makeImagePrototype(resources.reloadIcon()))); + result.add(new ImageSpec(ACTION_SET_STARTTIME, stringMessages.setStartTime(), makeImagePrototype(resources.clock()))); return result; } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java new file mode 100644 index 00000000000..dabfa8597e5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ListBoxUtils.java @@ -0,0 +1,65 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.List; + +import com.google.gwt.user.client.ui.ListBox; +import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; + +public final class ListBoxUtils { + + + public static void setupCourseDesignerModeListBox(ListBox box, CourseDesignerMode selectedCourseDesignerMode, + String noItemText) { + for (CourseDesignerMode mode : CourseDesignerMode.values()) { + if (mode == CourseDesignerMode.UNKNOWN) { + box.addItem(noItemText, mode.name()); + } else { + box.addItem(mode.toString(), mode.name()); + } + if (mode == selectedCourseDesignerMode) { + box.setSelectedIndex(box.getItemCount() - 1); + } + } + } + + public static void setupRacingProcedureTypeListBox(ListBox box, RacingProcedureType selectedRacingProcedureType, + String noItemText) { + box.clear(); + for (RacingProcedureType type : RacingProcedureType.values()) { + if (type == RacingProcedureType.UNKNOWN) { + box.addItem(noItemText, type.name()); + } else { + box.addItem(type.toString(), type.name()); + } + if (type == selectedRacingProcedureType) { + box.setSelectedIndex(box.getItemCount() - 1); + } + } + } + + public static void setupFlagsListBox(ListBox box, Flags selectedFlag, String noItemText) { + box.clear(); + for (Flags flag : Flags.values()) { + if (flag == Flags.NONE) { + box.addItem(noItemText, flag.name()); + } else { + box.addItem(flag.toString(), flag.name()); + } + if (flag == selectedFlag) { + box.setSelectedIndex(box.getItemCount() -1); + } + } + } + + public static void setupFlagsListBox(ListBox box, List selectedFlags) { + box.clear(); + for (Flags flag : Flags.validValues()) { + box.addItem(flag.toString(), flag.name()); + if (selectedFlags.contains(flag)) { + box.setItemSelected(box.getItemCount() - 1, true); + } + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java new file mode 100644 index 00000000000..8510701762a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java @@ -0,0 +1,418 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.DisclosurePanel; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Image; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sailing.gwt.ui.client.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO; +import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO.RegattaConfigurationDTO.RacingProcedureConfigurationDTO; + +public class RegattaConfigurationDialog extends DataEntryDialog { + + private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + private final StringMessages stringMessages; + private final DeviceConfigurationDTO.RegattaConfigurationDTO originalConfiguration; + + private VerticalPanel contentPanel; + + private ListBox racingProcedureListBox; + private ListBox designerModeEntryListBox; + + private DisclosurePanel rrs26DisclosurePanel; + private CheckBox rrs26EnabledBox; + private ListBox rrs26ClassFlagListBox; + private CheckBox rrs26RecallBox; + private ListBox rrs26StartModeFlagsBox; + + private DisclosurePanel gateStartDisclosurePanel; + private CheckBox gateStartEnabledBox; + private ListBox gateStartClassFlagListBox; + private CheckBox gateStartRecallBox; + private CheckBox gateStartPathfinderBox; + private CheckBox gateStartGolfDownBox; + + private DisclosurePanel essDisclosurePanel; + private CheckBox essEnabledBox; + private ListBox essClassFlagListBox; + private CheckBox essRecallBox; + + private DisclosurePanel basicDisclosurePanel; + private CheckBox basicEnabledBox; + private ListBox basicClassFlagListBox; + private CheckBox basicRecallBox; + + public RegattaConfigurationDialog(DeviceConfigurationDTO.RegattaConfigurationDTO regattaConfiguration, + StringMessages messages, DataEntryDialog.DialogCallback callback) { + super(messages.racingProcedureConfiguration(), "", messages.save(), messages.cancel(), null, callback); + this.stringMessages = messages; + this.originalConfiguration = regattaConfiguration; + } + + @Override + protected Widget getAdditionalWidget() { + contentPanel = new VerticalPanel(); + setupGeneral(); + setupRRS26(); + setupGateStart(); + setupESS(); + setupBasic(); + + if (rrs26EnabledBox.getValue()) { + rrs26DisclosurePanel.setOpen(true); + } + if (gateStartEnabledBox.getValue()) { + gateStartDisclosurePanel.setOpen(true); + } + if (essEnabledBox.getValue()) { + essDisclosurePanel.setOpen(true); + } + if (basicEnabledBox.getValue()) { + basicDisclosurePanel.setOpen(true); + } + + return contentPanel; + } + + private void setupGeneral() { + Grid grid = new Grid(2, 2); + setupCourseDesignerListBox(grid, 0); + setupRacingProcedureListBox(grid, 1); + contentPanel.add(grid); + } + + private void setupRacingProcedureListBox(Grid grid, int gridRow) { + racingProcedureListBox = new ListBox(false); + racingProcedureListBox.setWidth("100%"); + ListBoxUtils.setupRacingProcedureTypeListBox(racingProcedureListBox, + originalConfiguration.defaultRacingProcedureType, stringMessages.dontoverwrite()); + racingProcedureListBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + markAsDrity(true); + } + }); + grid.setWidget(gridRow, 0, new Label(stringMessages.racingProcedure())); + grid.setWidget(gridRow, 1, racingProcedureListBox); + } + + private void setupCourseDesignerListBox(Grid grid, int gridRow) { + designerModeEntryListBox = new ListBox(false); + designerModeEntryListBox.setWidth("100%"); + ListBoxUtils.setupCourseDesignerModeListBox(designerModeEntryListBox, + originalConfiguration.defaultCourseDesignerMode, stringMessages.dontoverwrite()); + designerModeEntryListBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + markAsDrity(true); + } + }); + grid.setWidget(gridRow, 0, new Label(stringMessages.courseDesignerMode())); + grid.setWidget(gridRow, 1, designerModeEntryListBox); + } + + private ListBox setupClassFlagListBox(RacingProcedureConfigurationDTO config) { + ListBox box = createListBox(false); + Flags selectedFlag = null; + if (config != null) { + selectedFlag = config.classFlag; + } + ListBoxUtils.setupFlagsListBox(box, selectedFlag, stringMessages.dontoverwrite()); + box.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + markAsDrity(true); + } + }); + return box; + } + + private CheckBox setupRecallBox() { + CheckBox box = new CheckBox(stringMessages.activateIndividualRecall()); + box.addValueChangeHandler(new ValueChangeHandler() { + + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + } + }); + return box; + } + + private void setupRRS26() { + rrs26DisclosurePanel = new DisclosurePanel(stringMessages.rrs26Start()); + VerticalPanel panel = new VerticalPanel(); + + Grid grid = new Grid(3, 3); + rrs26EnabledBox = new CheckBox(stringMessages.setConfiguration()); + rrs26EnabledBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + boolean isActive = event.getValue(); + rrs26ClassFlagListBox.setEnabled(isActive); + rrs26RecallBox.setEnabled(isActive); + rrs26StartModeFlagsBox.setEnabled(isActive); + } + }); + rrs26ClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null + : originalConfiguration.rrs26Configuration); + rrs26ClassFlagListBox.setWidth("100%"); + rrs26RecallBox = setupRecallBox(); + setupRRS26StartModeFlags(); + + grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); + grid.setWidget(0, 1, rrs26ClassFlagListBox); + grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("RRS26"))); + grid.setWidget(1, 0, rrs26RecallBox); + grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + grid.setWidget(2, 0, new Label(stringMessages.startmodeFlags() + ":")); + grid.setWidget(2, 1, rrs26StartModeFlagsBox); + grid.setWidget(2, 2, createHelpImage(stringMessages.startmodeFlagsHelpText())); + + rrs26EnabledBox.setValue(originalConfiguration != null && originalConfiguration.rrs26Configuration != null); + ValueChangeEvent.fire(rrs26EnabledBox, rrs26EnabledBox.getValue()); + + panel.add(rrs26EnabledBox); + panel.add(grid); + rrs26DisclosurePanel.add(panel); + contentPanel.add(rrs26DisclosurePanel); + } + + private void setupRRS26StartModeFlags() { + rrs26StartModeFlagsBox = new ListBox(true); + rrs26StartModeFlagsBox.setWidth("100%"); + List selectedFlags = new ArrayList(); + if (originalConfiguration != null && originalConfiguration.rrs26Configuration != null + && originalConfiguration.rrs26Configuration.startModeFlags != null) { + selectedFlags = originalConfiguration.rrs26Configuration.startModeFlags; + } + ListBoxUtils.setupFlagsListBox(rrs26StartModeFlagsBox, selectedFlags); + rrs26StartModeFlagsBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + markAsDrity(true); + } + }); + } + + private void setupGateStart() { + gateStartDisclosurePanel = new DisclosurePanel(stringMessages.gateStart()); + VerticalPanel panel = new VerticalPanel(); + + Grid grid = new Grid(4, 3); + gateStartEnabledBox = new CheckBox(stringMessages.setConfiguration()); + gateStartEnabledBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + gateStartClassFlagListBox.setEnabled(event.getValue()); + gateStartRecallBox.setEnabled(event.getValue()); + gateStartPathfinderBox.setEnabled(event.getValue()); + gateStartGolfDownBox.setEnabled(event.getValue()); + } + }); + gateStartClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null + : originalConfiguration.gateStartConfiguration); + gateStartClassFlagListBox.setWidth("100%"); + gateStartRecallBox = setupRecallBox(); + gateStartPathfinderBox = new CheckBox(stringMessages.activatePathfinder()); + gateStartPathfinderBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + } + }); + gateStartGolfDownBox = createCheckbox(stringMessages.hasAdditionalGolfDownTime()); + gateStartGolfDownBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + } + }); + + grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); + grid.setWidget(0, 1, gateStartClassFlagListBox); + grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("Gate Start"))); + grid.setWidget(1, 0, gateStartRecallBox); + grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + grid.setWidget(2, 0, gateStartPathfinderBox); + grid.setWidget(2, 2, createHelpImage(stringMessages.pathfinderHelpText())); + grid.setWidget(3, 0, gateStartGolfDownBox); + grid.setWidget(3, 2, createHelpImage(stringMessages.additionalGolfDownTimeHelpText())); + + gateStartEnabledBox.setValue(originalConfiguration != null + && originalConfiguration.gateStartConfiguration != null); + ValueChangeEvent.fire(gateStartEnabledBox, gateStartEnabledBox.getValue()); + + panel.add(gateStartEnabledBox); + panel.add(grid); + gateStartDisclosurePanel.add(panel); + contentPanel.add(gateStartDisclosurePanel); + } + + private void setupESS() { + essDisclosurePanel = new DisclosurePanel(stringMessages.essStart()); + VerticalPanel panel = new VerticalPanel(); + + Grid grid = new Grid(2, 3); + essEnabledBox = new CheckBox(stringMessages.setConfiguration()); + essEnabledBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + essClassFlagListBox.setEnabled(event.getValue()); + essRecallBox.setEnabled(event.getValue()); + } + }); + essClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null + : originalConfiguration.essConfiguration); + essClassFlagListBox.setWidth("100%"); + essRecallBox = setupRecallBox(); + + grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); + grid.setWidget(0, 1, essClassFlagListBox); + grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("ESS"))); + grid.setWidget(1, 0, essRecallBox); + grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + + essEnabledBox.setValue(originalConfiguration != null && originalConfiguration.essConfiguration != null); + ValueChangeEvent.fire(essEnabledBox, essEnabledBox.getValue()); + + panel.add(essEnabledBox); + panel.add(grid); + essDisclosurePanel.add(panel); + contentPanel.add(essDisclosurePanel); + } + + private void setupBasic() { + basicDisclosurePanel = new DisclosurePanel(stringMessages.basicStart()); + VerticalPanel panel = new VerticalPanel(); + + Grid grid = new Grid(2, 3); + basicEnabledBox = new CheckBox(stringMessages.setConfiguration()); + basicEnabledBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + markAsDrity(true); + basicClassFlagListBox.setEnabled(event.getValue()); + basicRecallBox.setEnabled(event.getValue()); + } + }); + basicClassFlagListBox = setupClassFlagListBox(originalConfiguration == null ? null + : originalConfiguration.basicConfiguration); + basicClassFlagListBox.setWidth("100%"); + basicRecallBox = setupRecallBox(); + + grid.setWidget(0, 0, new Label(stringMessages.classFlag() + ":")); + grid.setWidget(0, 1, basicClassFlagListBox); + grid.setWidget(0, 2, createHelpImage(stringMessages.classFlagHelpText("basic start"))); + grid.setWidget(1, 0, basicRecallBox); + grid.setWidget(1, 2, createHelpImage(stringMessages.individualRecallHelpText())); + + basicEnabledBox.setValue(originalConfiguration != null && originalConfiguration.basicConfiguration != null); + ValueChangeEvent.fire(basicEnabledBox, basicEnabledBox.getValue()); + + panel.add(basicEnabledBox); + panel.add(grid); + basicDisclosurePanel.add(panel); + contentPanel.add(basicDisclosurePanel); + } + + private void markAsDrity(boolean b) { + // hmnn do we need to this? + } + + private Widget createHelpImage(String tooltip) { + Image help = new Image(); + help.setResource(resources.help()); + help.setTitle(tooltip); + help.setAltText(tooltip); + return help; + } + + @Override + protected RegattaConfigurationDTO getResult() { + DeviceConfigurationDTO.RegattaConfigurationDTO result = new DeviceConfigurationDTO.RegattaConfigurationDTO(); + + int index = racingProcedureListBox.getSelectedIndex(); + if (index >= 0) { + RacingProcedureType type = RacingProcedureType.valueOf(racingProcedureListBox.getValue(index)); + result.defaultRacingProcedureType = type == RacingProcedureType.UNKNOWN ? null : type; + } + + result.defaultRacingProcedureType = getSelectedRacingProcedure(); + + index = designerModeEntryListBox.getSelectedIndex(); + if (index >= 0) { + CourseDesignerMode mode = CourseDesignerMode.valueOf(designerModeEntryListBox.getValue(index)); + result.defaultCourseDesignerMode = mode == CourseDesignerMode.UNKNOWN ? null : mode; + } + + if (rrs26EnabledBox.getValue()) { + result.rrs26Configuration = new DeviceConfigurationDTO.RegattaConfigurationDTO.RRS26ConfigurationDTO(); + getRacingProcedureConfigurationResults(result.rrs26Configuration, rrs26ClassFlagListBox, rrs26RecallBox); + List flags = new ArrayList(); + for (int i = 0; i < rrs26StartModeFlagsBox.getItemCount(); i++) { + if (rrs26StartModeFlagsBox.isItemSelected(i)) { + flags.add(Flags.valueOf(rrs26StartModeFlagsBox.getValue(i))); + } + } + result.rrs26Configuration.startModeFlags = flags.isEmpty() ? null : flags; + } + if (gateStartEnabledBox.getValue()) { + result.gateStartConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.GateStartConfigurationDTO(); + getRacingProcedureConfigurationResults(result.gateStartConfiguration, gateStartClassFlagListBox, + gateStartRecallBox); + result.gateStartConfiguration.hasPathfinder = gateStartPathfinderBox.getValue(); + result.gateStartConfiguration.hasAdditionalGolfDownTime = gateStartGolfDownBox.getValue(); + } + if (essEnabledBox.getValue()) { + result.essConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.ESSConfigurationDTO(); + getRacingProcedureConfigurationResults(result.essConfiguration, essClassFlagListBox, essRecallBox); + } + if (basicEnabledBox.getValue()) { + result.basicConfiguration = new DeviceConfigurationDTO.RegattaConfigurationDTO.RacingProcedureConfigurationDTO(); + getRacingProcedureConfigurationResults(result.basicConfiguration, basicClassFlagListBox, basicRecallBox); + } + return result; + } + + private RacingProcedureType getSelectedRacingProcedure() { + int index = racingProcedureListBox.getSelectedIndex(); + if (index >= 0) { + RacingProcedureType type = RacingProcedureType.valueOf(racingProcedureListBox.getValue(index)); + return type == RacingProcedureType.UNKNOWN ? null : type; + } + return null; + } + + private void getRacingProcedureConfigurationResults( + DeviceConfigurationDTO.RegattaConfigurationDTO.RacingProcedureConfigurationDTO target, + ListBox classListBox, CheckBox recallBox) { + int index = classListBox.getSelectedIndex(); + if (index >= 0) { + Flags flag = Flags.valueOf(classListBox.getValue(index)); + target.classFlag = flag == Flags.NONE ? null : flag; + } + target.hasInidividualRecall = recallBox.getValue(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 237ad79656a..fde518c072d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -49,6 +49,7 @@ public class RegattaDetailsComposite extends Composite { private final Label boatClassName; private final Label scoringSystem; private final Label defaultCourseArea; + private final Label configuration; private final SelectionModel seriesSelectionModel; private final CellTable seriesTable; @@ -66,7 +67,7 @@ public class RegattaDetailsComposite extends Composite { mainPanel = new CaptionPanel(stringMessages.regatta()); VerticalPanel vPanel = new VerticalPanel(); mainPanel.add(vPanel); - Grid grid = new Grid(4, 2); + Grid grid = new Grid(5, 2); vPanel.add(grid); regattaName = new Label(); grid.setWidget(0 , 0, new Label(stringMessages.regattaName() + ":")); @@ -77,9 +78,12 @@ public class RegattaDetailsComposite extends Composite { defaultCourseArea = new Label(); grid.setWidget(2 , 0, new Label(stringMessages.courseArea() + ":")); grid.setWidget(2 , 1, defaultCourseArea); + configuration = new Label(); + grid.setWidget(3, 0, new Label(stringMessages.racingProcedureConfiguration() + ":")); + grid.setWidget(3, 1, configuration);; scoringSystem = new Label(); - grid.setWidget(3 , 0, new Label(stringMessages.scoringSystem() + ":")); - grid.setWidget(3 , 1, scoringSystem); + grid.setWidget(4 , 0, new Label(stringMessages.scoringSystem() + ":")); + grid.setWidget(4 , 1, scoringSystem); seriesTable = createRegattaSeriesTable(); seriesSelectionModel = new SingleSelectionModel(); seriesSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { @@ -314,7 +318,12 @@ public class RegattaDetailsComposite extends Composite { if (regatta != null) { regattaName.setText(regatta.getName()); boatClassName.setText(regatta.boatClass != null ? regatta.boatClass.getName() : ""); - defaultCourseArea.setText(regatta.defaultCourseAreaIdAsString == null ? "" : regatta.defaultCourseAreaName); + defaultCourseArea.setText(regatta.defaultCourseAreaUuidAsString == null ? "" : regatta.defaultCourseAreaName); + if (regatta.configuration != null) { + configuration.setText(stringMessages.configured()); + } else { + configuration.setText(stringMessages.none()); + } ScoringSchemeType scoringScheme = regatta.scoringScheme; String scoringSystemText = scoringScheme == null ? "" : ScoringSchemeTypeFormatter.format(scoringScheme, stringMessages); scoringSystem.setText(scoringSystemText); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index 6bf18dd8dec..5a835a8a2b4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -221,7 +221,9 @@ public class RegattaListComposite extends Composite implements RegattaDisplayer private void commitEditedRegatta(final RegattaDTO editedRegatta) { final RegattaIdentifier regattaName = new RegattaName(editedRegatta.getName()); - sailingService.updateRegatta(regattaName, editedRegatta.defaultCourseAreaIdAsString, new AsyncCallback() { + sailingService.updateRegatta(regattaName, editedRegatta.defaultCourseAreaUuidAsString, + editedRegatta.configuration, + new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error trying to update regatta " + editedRegatta.getName() + ": " + caught.getMessage()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java index 508e526ee78..96449b5c68a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaStructureManagementPanel.java @@ -165,7 +165,7 @@ public class RegattaStructureManagementPanel extends SimplePanel implements Rega } sailingService.createRegatta(newRegatta.getName(), newRegatta.boatClass==null?null:newRegatta.boatClass.getName(), new RegattaCreationParametersDTO(seriesStructure), true, - newRegatta.scoringScheme, newRegatta.defaultCourseAreaIdAsString, new AsyncCallback() { + newRegatta.scoringScheme, newRegatta.defaultCourseAreaUuidAsString, new AsyncCallback() { @Override public void onFailure(Throwable t) { errorReporter.reportError("Error trying to create new regatta " + newRegatta.getName() + ": " + t.getMessage()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java index b818570bb23..fa06e1f8164 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java @@ -6,21 +6,19 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -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.user.client.ui.Button; -import com.google.gwt.user.client.ui.Grid; -import com.google.gwt.user.client.ui.Label; +import com.google.gwt.core.shared.GWT; import com.google.gwt.user.client.ui.VerticalPanel; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.ListEditorComposite; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAndFleetsDialog { - private Grid seriesGrid; + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + + private ListEditorComposite seriesEditor; protected static class RegattaParameterValidator implements Validator { @@ -99,60 +97,14 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd List existingEvents, StringMessages stringConstants, DialogCallback callback) { super(new RegattaDTO(), existingEvents, stringConstants.addRegatta(), stringConstants.ok(), stringConstants, new RegattaParameterValidator(stringConstants, existingRegattas), callback); - this.seriesGrid = new Grid(0, 0); + + this.seriesEditor = SeriesWithFleetsListEditor.createExpanded(Collections.emptyList(), stringMessages, resources.removeIcon()); } @Override protected void setupAdditionalWidgetsOnPanel(final VerticalPanel panel) { panel.add(createHeadlineLabel(stringMessages.series())); - panel.add(seriesGrid); - Button addSeriesButton = new Button(stringMessages.addSeries()); - addSeriesButton.addClickHandler(new ClickHandler() { - @Override - public void onClick(ClickEvent event) { - RegattaDTO result = getResult(); - SeriesWithFleetsCreateDialog dialog = new SeriesWithFleetsCreateDialog(Collections - .unmodifiableCollection(result.series), stringMessages, new DialogCallback() { - @Override - public void cancel() { - } - - @Override - public void ok(SeriesDTO newSeries) { - series.add(newSeries); - updateSeriesGrid(panel); - } - }); - dialog.show(); - } - }); - panel.add(addSeriesButton); - } - - private void updateSeriesGrid(VerticalPanel parentPanel) { - int widgetIndex = parentPanel.getWidgetIndex(seriesGrid); - parentPanel.remove(seriesGrid); - - int seriesCount = series.size(); - seriesGrid = new Grid(seriesCount * 2, 3); - seriesGrid.setCellSpacing(3); - - for (int i = 0; i < seriesCount; i++) { - SeriesDTO seriesDTO = series.get(i); - Label seriesLabel = new Label((i + 1) + ". " + stringMessages.series() + ":"); - seriesLabel.setWordWrap(false); - seriesLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); - seriesGrid.setWidget(i * 2, 0, seriesLabel); - seriesGrid.setHTML(i * 2, 1, seriesDTO.getName()); - if (seriesDTO.getFleets() != null && seriesDTO.getFleets().size() > 0) { - seriesGrid.setHTML(i * 2 + 1, 1, seriesDTO.getFleets().size() + " fleets: " - + seriesDTO.getFleets().toString()); - } else { - seriesGrid.setHTML(i * 2 + 1, 1, seriesDTO.getFleets().size() + " No fleets defined."); - } - } - - parentPanel.insert(seriesGrid, widgetIndex); + panel.add(seriesEditor); } @Override @@ -160,5 +112,12 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd super.show(); nameEntryField.setFocus(true); } + + @Override + protected RegattaDTO getResult() { + RegattaDTO dto = super.getResult(); + dto.series = seriesEditor.getValue(); + return dto; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java index bfe5b2aff4f..fcb050e7a4e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java @@ -1,6 +1,5 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.ArrayList; import java.util.List; import com.google.gwt.event.dom.client.ChangeEvent; @@ -19,7 +18,6 @@ import com.sap.sailing.gwt.ui.leaderboard.ScoringSchemeTypeFormatter; import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sailing.gwt.ui.shared.SeriesDTO; public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog { @@ -32,7 +30,6 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog series; protected List existingEvents; protected abstract void setupAdditionalWidgetsOnPanel(VerticalPanel panel); @@ -46,7 +43,6 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog(); nameEntryField = createTextBox(null); nameEntryField.setVisibleLength(40); @@ -102,8 +98,6 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog(); - regatta.series.addAll(series); setCourseAreaInRegatta(regatta); return regatta; } @@ -111,9 +105,9 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends DataEntryDialog existingRegattas, - List existingEvents, StringMessages stringMessages, DialogCallback callback) { + List existingEvents, final StringMessages stringMessages, DialogCallback callback) { super(regatta, existingEvents, stringMessages.editRegatta(), stringMessages.ok(), stringMessages, null, callback); + currentRegattaConfiguration = regatta.configuration; + nameEntryField.setEnabled(false); boatClassEntryField.setEnabled(false); scoringSchemeListBox.setEnabled(false); sailingEventsListBox.setEnabled(true); courseAreaListBox.setEnabled(true); + + regattaConfigurationCheckbox = createCheckbox(stringMessages.setRacingProcedureConfiguration()); + regattaConfigurationCheckbox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + regattaConfigurationButton.setEnabled(event.getValue()); + } + }); + regattaConfigurationButton = new Button(stringMessages.edit()); + regattaConfigurationButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + new RegattaConfigurationDialog(currentRegattaConfiguration == null ? new RegattaConfigurationDTO() : currentRegattaConfiguration, + stringMessages, new DialogCallback() { + @Override + public void ok(RegattaConfigurationDTO newConfiguration) { + currentRegattaConfiguration = newConfiguration; + } + + @Override + public void cancel() { + } + }).show();; + } + }); + regattaConfigurationCheckbox.setValue(regatta.configuration != null); + regattaConfigurationButton.setEnabled(regatta.configuration != null); } @Override @@ -29,7 +72,26 @@ public class RegattaWithSeriesAndFleetsEditDialog extends RegattaWithSeriesAndFl @Override protected void setupAdditionalWidgetsOnPanel(VerticalPanel panel) { - // nothing yet + VerticalPanel content = new VerticalPanel(); + + Grid proceduresGrid = new Grid(1,2); + proceduresGrid.setWidget(0, 0, regattaConfigurationCheckbox); + proceduresGrid.setWidget(0, 1, regattaConfigurationButton); + + content.add(proceduresGrid); + panel.add(content); + } + + @Override + protected RegattaDTO getResult() { + RegattaDTO regatta = super.getResult(); + + if (regattaConfigurationCheckbox.getValue()) { + regatta.configuration = currentRegattaConfiguration; + } else { + regatta.configuration = null; + } + return regatta; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java index 04c6887d212..0c1713177f3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SeriesEditDialog.java @@ -1,18 +1,20 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import com.google.gwt.core.shared.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CheckBox; -import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; @@ -24,19 +26,17 @@ import com.sap.sailing.gwt.ui.client.DataEntryDialog; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SeriesDTO; +import com.sap.sailing.gwt.ui.shared.StringListEditorComposite; public class SeriesEditDialog extends DataEntryDialog { - private final ListBox addRacesListBox; - private Button addRacesBtn; + + private static AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); + private CheckBox isMedalCheckbox; private CheckBox startWithZeroScoreCheckbox; private CheckBox firstColumnIsNonDiscardableCarryForwardCheckbox; private CheckBox useSeriesResultDiscardingThresholdsCheckbox; - private final List raceNameEntryFields; - private final List