diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 608991787f5..a8133831ce8 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -631,7 +631,7 @@ if [[ "$@" == "build" ]] || [[ "$@" == "all" ]]; then PATH=$PATH:$ANDROID_HOME/platform-tools SDK_MANAGER="$ANDROID_HOME/tools/bin/sdkmanager" if [ \! -x "$SDK_MANAGER" ]; then - SDK_MANAGER="$SDK_MANAGER/tools/bin/sdkmanager.bat" + SDK_MANAGER="$ANDROID_HOME/tools/bin/sdkmanager.bat" fi mobile_extra="-P -with-not-android-relevant -P with-mobile" diff --git a/configuration/installPluginsForEclipseOxygen.sh b/configuration/installPluginsForEclipseOxygen.sh index 3cfb7049ea5..9c263371ac1 100755 --- a/configuration/installPluginsForEclipseOxygen.sh +++ b/configuration/installPluginsForEclipseOxygen.sh @@ -1,6 +1,6 @@ #!/bin/bash -# You need an installation of the Neon SR2 release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit): +# You need an installation of the Oxygen SR2 release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit): # http://www.eclipse.org/downloads/packages/eclipse-ide-eclipse-committers/oxygen2 if [[ $1 == "" ]]; then diff --git a/configuration/installPluginsForEclipsePhoton.sh b/configuration/installPluginsForEclipsePhoton.sh new file mode 100644 index 00000000000..169c59be235 --- /dev/null +++ b/configuration/installPluginsForEclipsePhoton.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# You need an installation of the Photon release of "Eclipse IDE for Eclipse Committers" matching you OS and JDK (32 vs 64 Bit): +# http://www.eclipse.org/downloads/packages/eclipse-ide-eclipse-committers/photonr + +if [[ $1 == "" ]]; then + echo "You need to specify the Eclipse installation directory" + exit 1 +fi + +installPath="$1" + +installPlugins() { + "$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -installIU $2 + echo "" +} + +updatePlugins() { + "$installPath/eclipse" -application org.eclipse.equinox.p2.director -noSplash -roaming -repository $1 -uninstallIU $2 -installIU $2 + echo "" +} + +# Not necessary if using Eclipse for JEE developers +echo "Installing webtools (WTP/WST/JPT) and m2e that are required by the GWT plugin..." +installPlugins http://download.eclipse.org/releases/photon/ org.eclipse.wst.web_ui.feature.feature.group,org.eclipse.jst.web_ui.feature.feature.group,org.eclipse.wst.xml_ui.feature.feature.group,org.eclipse.jpt.common.feature.feature.group,org.eclipse.jpt.jpa.feature.feature.group,org.eclipse.m2e.feature.feature.group,org.eclipse.m2e.wtp.feature.feature.group + +echo "Installing GWT plugin..." +installPlugins http://storage.googleapis.com/gwt-eclipse-plugin/v3/release com.gwtplugins.eclipse.suite.v3.feature.feature.group + +echo "Installing Android Tools..." +installPlugins https://dl-ssl.google.com/android/eclipse com.android.ide.eclipse.adt.feature.feature.group + +echo "Installing GWT SDM debug bridge..." +installPlugins http://p2.sapsailing.com/p2/sdbg com.github.sdbg.feature.feature.group + +echo "Installing EasyShell..." +installPlugins http://anb0s.github.io/EasyShell de.anbos.eclipse.easyshell.feature.feature.group + +echo "Installing BIRT charts (requirement for MAT)..." +installPlugins http://download.eclipse.org/birt/update-site/4.6 org.eclipse.birt.chart.feature.group + +echo "Installing Memory Analyzer..." +installPlugins http://download.eclipse.org/releases/photon org.eclipse.mat.feature.feature.group,org.eclipse.mat.chart.feature.feature.group + +echo "Installing latest version of Code Recommenders..." +updatePlugins http://download.eclipse.org/recommenders/updates/stable/ org.eclipse.recommenders.rcp.feature.feature.group,org.eclipse.recommenders.mylyn.rcp.feature.feature.group,org.eclipse.recommenders.snipmatch.rcp.feature.feature.group,org.eclipse.recommenders.news.rcp.feature.feature.group + +echo "Installing latest version of EGit..." +updatePlugins http://download.eclipse.org/egit/updates org.eclipse.jgit.feature.group,org.eclipse.jgit.http.apache.feature.group,org.eclipse.egit.feature.group,org.eclipse.egit.mylyn.feature.group,org.eclipse.egit.gitflow.feature.feature.group + +# Currently not needed because Eclipse Photon already provides current versions +# echo "Installing latest version of Mylyn ..." +# updatePlugins http://download.eclipse.org/mylyn/releases/latest org.eclipse.mylyn_feature.feature.group,org.eclipse.mylyn.bugzilla_feature.feature.group,org.eclipse.mylyn.builds.feature.group,org.eclipse.mylyn.commons.feature.group,org.eclipse.mylyn.commons.identity.feature.group,org.eclipse.mylyn.commons.notifications.feature.group,org.eclipse.mylyn.commons.repositories.feature.group,org.eclipse.mylyn.commons.repositories.http.feature.group,org.eclipse.mylyn.context_feature.feature.group,org.eclipse.mylyn.discovery.feature.group,org.eclipse.mylyn.gerrit.feature.feature.group,org.eclipse.mylyn.git.feature.group,org.eclipse.mylyn.hudson.feature.group,org.eclipse.mylyn.java_feature.feature.group,org.eclipse.mylyn.monitor.feature.group,org.eclipse.mylyn.reviews.feature.feature.group,org.eclipse.mylyn.team_feature.feature.group,org.eclipse.mylyn.versions.feature.group,org.eclipse.mylyn.wikitext_feature.feature.group + +echo "Installing SAP JVM Tools (profiler) ..." +installPlugins https://tools.hana.ondemand.com/oxygen com.sap.jvm.profiling.feature.group + +echo "Installation completed!" diff --git a/configuration/launchhudsonslave b/configuration/launchhudsonslave new file mode 100755 index 00000000000..392b57d90d5 --- /dev/null +++ b/configuration/launchhudsonslave @@ -0,0 +1,25 @@ +#!/bin/bash +AWS=/root/.local/bin/aws +REGION=eu-west-1 +HUDSON_SLAVE_AMI_ID=ami-63e9f689 +echo Launching instance... +instanceid=`$AWS ec2 run-instances --image-id $HUDSON_SLAVE_AMI_ID --count 1 --instance-type c5d.4xlarge --key-name Axel --security-groups "Sailing Analytics App" --region $REGION --placement AvailabilityZone=eu-west-1c --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=Hudson Ubuntu Slave}]' --instance-initiated-shutdown-behavior terminate | tee /tmp/slavelaunch.out | jq .Instances[0].InstanceId | sed -e 's/"//g'` +if [ "$instanceid" = "" ]; then + echo Error launching instance + exit 1 +else + echo Instance ID is $instanceid + while [ "`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].State.Name`" != "\"running\"" ]; do + echo Instance $instanceid not running yet\; trying again... + sleep 5 + done + echo Instance $instanceid seems running now + private_ip=`$AWS ec2 describe-instances --region $REGION --instance-ids $instanceid | jq .Reservations[0].Instances[0].PrivateIpAddress | sed -e 's/"//g'` + echo Probing for SSH on private IP $private_ip + while su - hudson -c "ssh -o ConnectTimeout=10,StrictHostKeyChecking=no hudson@$private_ip ls >/dev/null"; do + echo SSH daemon not reachable yet. Trying again... + done + echo SSH daemon reached. State should be ready to connect to now. + su - hudson -c "ssh -o StrictHostKeyChecking=no hudson@$private_ip \"/opt/sapjvm_8/bin/java -jar slave.jar; sudo /sbin/shutdown -h now\"" + $AWS ec2 terminate-instances --instance-ids $instanceid +fi diff --git a/configuration/sailing.sh b/configuration/sailing.sh index 9e801102202..f096a276999 100755 --- a/configuration/sailing.sh +++ b/configuration/sailing.sh @@ -12,7 +12,7 @@ export JAVA_HOME=/opt/sapjvm_8 export JAVA_1_7_HOME=/opt/jdk1.7.0_75 export ANDROID_HOME=/opt/android-sdk-linux -export PATH=$PATH:$JAVA_HOME/bin:/opt/amazon/ec2-api-tools-1.6.8.0/bin:/opt/amazon/bin +export PATH=$PATH:$JAVA_HOME/bin:/opt/amazon/ec2-api-tools-1.6.8.0/bin:/opt/amazon/bin:/opt/apache-maven-3.2.1/bin export DISPLAY=:2.0 diff --git a/configuration/settings-agg.js b/configuration/settings-agg.js new file mode 100755 index 00000000000..5bbb25b21de --- /dev/null +++ b/configuration/settings-agg.js @@ -0,0 +1,40 @@ +// Aggregates settings by their frequency across all DBs. +// Usage: mongo --host --port settings-agg.js +allDatabases = db.adminCommand({ "listDatabases": 1 }).databases + +collection = 'PREFERENCES' + +acc = {} + +allDatabases.forEach(function(d) { + //database = connect('localhost:27017/' + d.name) + database = db.getSiblingDB(d.name) + collections = database.getCollectionNames() + if (collections.indexOf(collection) >= 0) { + usage = database.getCollection(collection).aggregate([{$unwind: "$KEYS_AND_VALUES"}, {$group: {_id:"$KEYS_AND_VALUES.VALUE", total:{$sum:1}}}]) + + usage.forEach(function(u) { + if (u._id[0] == '{') { + if (u._id in acc) { + acc[u._id] += u.total + } else { + acc[u._id] = u.total + } + } + }) + } +}) + +result = [] +for (var key in acc) { + if (acc.hasOwnProperty(key)) { + result.push({"setting": key, "count": acc[key]}) + } +} + +result.sort(function(a, b) { return b.count - a.count }) + +for (var i in result) { + print("Setting: " + result[i].setting + "\nCount: " + result[i].count + "\n") +} + diff --git a/doc/SAPSailingAnalytics_Administrator_Training.pptx b/doc/SAPSailingAnalytics_Administrator_Training.pptx index 73acb57142a..417c356472f 100644 Binary files a/doc/SAPSailingAnalytics_Administrator_Training.pptx and b/doc/SAPSailingAnalytics_Administrator_Training.pptx differ diff --git a/ios/SAPTracker/cfg/VERSION b/ios/SAPTracker/cfg/VERSION index c787b213b07..154b9fce5be 100644 --- a/ios/SAPTracker/cfg/VERSION +++ b/ios/SAPTracker/cfg/VERSION @@ -1 +1 @@ -1.0.22 +1.0.23 diff --git a/ios/SAPTracker/cfg/central-signing/NAASSigningExportOptions.plist b/ios/SAPTracker/cfg/central-signing/NAASSigningExportOptions.plist new file mode 100644 index 00000000000..dc4d563fafd --- /dev/null +++ b/ios/SAPTracker/cfg/central-signing/NAASSigningExportOptions.plist @@ -0,0 +1,13 @@ + + + + +method +app-store +provisioningProfiles + +com.sap.sailing.ios.SAPTracker.release +SAP Sail InSight + + + diff --git a/ios/SAPTracker/src/xcode/SAPTracker.xcodeproj/project.pbxproj b/ios/SAPTracker/src/xcode/SAPTracker.xcodeproj/project.pbxproj index f87656767a3..270ddda359c 100755 --- a/ios/SAPTracker/src/xcode/SAPTracker.xcodeproj/project.pbxproj +++ b/ios/SAPTracker/src/xcode/SAPTracker.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 64FDEBDC20D12DCF0086C2F4 /* AFAutoPurgingImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 64FDEBDA20D12DCE0086C2F4 /* AFAutoPurgingImageCache.m */; }; + 64FDEBDD20D12DCF0086C2F4 /* AFImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 64FDEBDB20D12DCE0086C2F4 /* AFImageDownloader.m */; }; 7F0658FD19F7D6700077FF95 /* ScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0658FC19F7D6700077FF95 /* ScanViewController.swift */; }; 7F0658FF19F7EE910077FF95 /* CheckInData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0658FE19F7EE910077FF95 /* CheckInData.swift */; }; 7F14A10A1A13B9A6001C1ECB /* _abkhazia.png in Resources */ = {isa = PBXBuildFile; fileRef = 7F14A0041A13B9A6001C1ECB /* _abkhazia.png */; }; @@ -385,12 +387,9 @@ C8E0BD0D1F39ADAD002FCCA5 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E0BD0C1F39ADAD002FCCA5 /* KeychainPasswordItem.swift */; }; C8E0D3131F59475B001EEF5C /* CheckInTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E0D3121F59475B001EEF5C /* CheckInTableViewController.swift */; }; C8F1B1E11D488B6D004C3379 /* Translation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F1B1E01D488B6D004C3379 /* Translation.swift */; }; - C8F71EDA1D8691F700826DE4 /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EA61D8691F700826DE4 /* AFHTTPRequestOperation.m */; }; - C8F71EDB1D8691F700826DE4 /* AFHTTPRequestOperationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EA81D8691F700826DE4 /* AFHTTPRequestOperationManager.m */; }; C8F71EDC1D8691F700826DE4 /* AFHTTPSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EAA1D8691F700826DE4 /* AFHTTPSessionManager.m */; }; C8F71EDD1D8691F700826DE4 /* AFNetworkReachabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EAD1D8691F700826DE4 /* AFNetworkReachabilityManager.m */; }; C8F71EDE1D8691F700826DE4 /* AFSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EAF1D8691F700826DE4 /* AFSecurityPolicy.m */; }; - C8F71EDF1D8691F700826DE4 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EB11D8691F700826DE4 /* AFURLConnectionOperation.m */; }; C8F71EE01D8691F700826DE4 /* AFURLRequestSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EB31D8691F700826DE4 /* AFURLRequestSerialization.m */; }; C8F71EE11D8691F700826DE4 /* AFURLResponseSerialization.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EB51D8691F700826DE4 /* AFURLResponseSerialization.m */; }; C8F71EE21D8691F700826DE4 /* AFURLSessionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EB71D8691F700826DE4 /* AFURLSessionManager.m */; }; @@ -402,7 +401,6 @@ C8F71EE81D8691F700826DE4 /* TSMarkdownParser.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EC61D8691F700826DE4 /* TSMarkdownParser.m */; }; C8F71EE91D8691F700826DE4 /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71EC91D8691F700826DE4 /* AFNetworkActivityIndicatorManager.m */; }; C8F71EEA1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71ECB1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.m */; }; - C8F71EEB1D8691F700826DE4 /* UIAlertView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71ECD1D8691F700826DE4 /* UIAlertView+AFNetworking.m */; }; C8F71EEC1D8691F700826DE4 /* UIButton+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71ECF1D8691F700826DE4 /* UIButton+AFNetworking.m */; }; C8F71EED1D8691F700826DE4 /* UIImageView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71ED21D8691F700826DE4 /* UIImageView+AFNetworking.m */; }; C8F71EEE1D8691F700826DE4 /* UIProgressView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F71ED51D8691F700826DE4 /* UIProgressView+AFNetworking.m */; }; @@ -423,6 +421,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 64FDEBD220D12C390086C2F4 /* AFImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFImageDownloader.h; sourceTree = ""; }; + 64FDEBD320D12C390086C2F4 /* AFAutoPurgingImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFAutoPurgingImageCache.h; sourceTree = ""; }; + 64FDEBDA20D12DCE0086C2F4 /* AFAutoPurgingImageCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AFAutoPurgingImageCache.m; sourceTree = ""; }; + 64FDEBDB20D12DCE0086C2F4 /* AFImageDownloader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AFImageDownloader.m; sourceTree = ""; }; 7F03FD3F19FA4D7400FA1327 /* bridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bridge.h; sourceTree = ""; }; 7F0658FC19F7D6700077FF95 /* ScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ScanViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 7F0658FE19F7EE910077FF95 /* CheckInData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CheckInData.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; @@ -809,10 +811,6 @@ C8E0BD0C1F39ADAD002FCCA5 /* KeychainPasswordItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainPasswordItem.swift; sourceTree = ""; }; C8E0D3121F59475B001EEF5C /* CheckInTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckInTableViewController.swift; sourceTree = ""; }; C8F1B1E01D488B6D004C3379 /* Translation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Translation.swift; sourceTree = ""; }; - C8F71EA51D8691F700826DE4 /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperation.h; sourceTree = ""; }; - C8F71EA61D8691F700826DE4 /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperation.m; sourceTree = ""; }; - C8F71EA71D8691F700826DE4 /* AFHTTPRequestOperationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPRequestOperationManager.h; sourceTree = ""; }; - C8F71EA81D8691F700826DE4 /* AFHTTPRequestOperationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPRequestOperationManager.m; sourceTree = ""; }; C8F71EA91D8691F700826DE4 /* AFHTTPSessionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFHTTPSessionManager.h; sourceTree = ""; }; C8F71EAA1D8691F700826DE4 /* AFHTTPSessionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFHTTPSessionManager.m; sourceTree = ""; }; C8F71EAB1D8691F700826DE4 /* AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFNetworking.h; sourceTree = ""; }; @@ -820,8 +818,6 @@ C8F71EAD1D8691F700826DE4 /* AFNetworkReachabilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFNetworkReachabilityManager.m; sourceTree = ""; }; C8F71EAE1D8691F700826DE4 /* AFSecurityPolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFSecurityPolicy.h; sourceTree = ""; }; C8F71EAF1D8691F700826DE4 /* AFSecurityPolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFSecurityPolicy.m; sourceTree = ""; }; - C8F71EB01D8691F700826DE4 /* AFURLConnectionOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLConnectionOperation.h; sourceTree = ""; }; - C8F71EB11D8691F700826DE4 /* AFURLConnectionOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperation.m; sourceTree = ""; }; C8F71EB21D8691F700826DE4 /* AFURLRequestSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLRequestSerialization.h; sourceTree = ""; }; C8F71EB31D8691F700826DE4 /* AFURLRequestSerialization.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLRequestSerialization.m; sourceTree = ""; }; C8F71EB41D8691F700826DE4 /* AFURLResponseSerialization.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFURLResponseSerialization.h; sourceTree = ""; }; @@ -845,8 +841,6 @@ C8F71EC91D8691F700826DE4 /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFNetworkActivityIndicatorManager.m; sourceTree = ""; }; C8F71ECA1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIActivityIndicatorView+AFNetworking.h"; sourceTree = ""; }; C8F71ECB1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIActivityIndicatorView+AFNetworking.m"; sourceTree = ""; }; - C8F71ECC1D8691F700826DE4 /* UIAlertView+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIAlertView+AFNetworking.h"; sourceTree = ""; }; - C8F71ECD1D8691F700826DE4 /* UIAlertView+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIAlertView+AFNetworking.m"; sourceTree = ""; }; C8F71ECE1D8691F700826DE4 /* UIButton+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+AFNetworking.h"; sourceTree = ""; }; C8F71ECF1D8691F700826DE4 /* UIButton+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+AFNetworking.m"; sourceTree = ""; }; C8F71ED01D8691F700826DE4 /* UIImage+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+AFNetworking.h"; sourceTree = ""; }; @@ -1565,10 +1559,6 @@ C8F71EA41D8691F700826DE4 /* AFNetworking */ = { isa = PBXGroup; children = ( - C8F71EA51D8691F700826DE4 /* AFHTTPRequestOperation.h */, - C8F71EA61D8691F700826DE4 /* AFHTTPRequestOperation.m */, - C8F71EA71D8691F700826DE4 /* AFHTTPRequestOperationManager.h */, - C8F71EA81D8691F700826DE4 /* AFHTTPRequestOperationManager.m */, C8F71EA91D8691F700826DE4 /* AFHTTPSessionManager.h */, C8F71EAA1D8691F700826DE4 /* AFHTTPSessionManager.m */, C8F71EAB1D8691F700826DE4 /* AFNetworking.h */, @@ -1576,8 +1566,6 @@ C8F71EAD1D8691F700826DE4 /* AFNetworkReachabilityManager.m */, C8F71EAE1D8691F700826DE4 /* AFSecurityPolicy.h */, C8F71EAF1D8691F700826DE4 /* AFSecurityPolicy.m */, - C8F71EB01D8691F700826DE4 /* AFURLConnectionOperation.h */, - C8F71EB11D8691F700826DE4 /* AFURLConnectionOperation.m */, C8F71EB21D8691F700826DE4 /* AFURLRequestSerialization.h */, C8F71EB31D8691F700826DE4 /* AFURLRequestSerialization.m */, C8F71EB41D8691F700826DE4 /* AFURLResponseSerialization.h */, @@ -1618,12 +1606,14 @@ C8F71EC71D8691F700826DE4 /* UIKit+AFNetworking */ = { isa = PBXGroup; children = ( + 64FDEBDA20D12DCE0086C2F4 /* AFAutoPurgingImageCache.m */, + 64FDEBD320D12C390086C2F4 /* AFAutoPurgingImageCache.h */, + 64FDEBDB20D12DCE0086C2F4 /* AFImageDownloader.m */, + 64FDEBD220D12C390086C2F4 /* AFImageDownloader.h */, C8F71EC81D8691F700826DE4 /* AFNetworkActivityIndicatorManager.h */, C8F71EC91D8691F700826DE4 /* AFNetworkActivityIndicatorManager.m */, C8F71ECA1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.h */, C8F71ECB1D8691F700826DE4 /* UIActivityIndicatorView+AFNetworking.m */, - C8F71ECC1D8691F700826DE4 /* UIAlertView+AFNetworking.h */, - C8F71ECD1D8691F700826DE4 /* UIAlertView+AFNetworking.m */, C8F71ECE1D8691F700826DE4 /* UIButton+AFNetworking.h */, C8F71ECF1D8691F700826DE4 /* UIButton+AFNetworking.m */, C8F71ED01D8691F700826DE4 /* UIImage+AFNetworking.h */, @@ -2034,14 +2024,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 64FDEBDC20D12DCF0086C2F4 /* AFAutoPurgingImageCache.m in Sources */, + 64FDEBDD20D12DCF0086C2F4 /* AFImageDownloader.m in Sources */, C867BD091F4B0274007E78E0 /* TrainingCompetitorViewController.swift in Sources */, C85DB82A1F31AAFD009559CE /* LoginViewController.swift in Sources */, - C8F71EDA1D8691F700826DE4 /* AFHTTPRequestOperation.m in Sources */, 7F1875B31A10F1B800C25BE0 /* TrackingViewController.swift in Sources */, C8739E081D2AA2A500A89F3C /* Leaderboard+CoreDataProperties.swift in Sources */, C8739E121D2BF14F00A89F3C /* EventData.swift in Sources */, C843D4881EBCADDC00250F01 /* CompetitorSessionController.swift in Sources */, - C8F71EDF1D8691F700826DE4 /* AFURLConnectionOperation.m in Sources */, C880646E2087DA1F005FBE57 /* BoatClassData.swift in Sources */, C891887A2057F55100E37CC3 /* BoatSessionController.swift in Sources */, C8F71EE31D8691F700826DE4 /* SVIndefiniteAnimatedView.m in Sources */, @@ -2090,7 +2080,6 @@ C89C5FCA1D06CECD002AAAD2 /* TrackingViewStatusCell.swift in Sources */, C843D4861EBC927800250F01 /* RegattaMarkViewController.swift in Sources */, C8F71EDE1D8691F700826DE4 /* AFSecurityPolicy.m in Sources */, - C8F71EDB1D8691F700826DE4 /* AFHTTPRequestOperationManager.m in Sources */, 7F8B8D3F1A37356C00C34B88 /* TrackingPageViewController.swift in Sources */, C89188712057E7D700E37CC3 /* BoatCheckIn.swift in Sources */, C843D48F1EC099EB00250F01 /* CoreData2-3.xcmappingmodel in Sources */, @@ -2143,7 +2132,6 @@ C8B3B9C21F3C6B3900B3B6BA /* SignUpTranslation.swift in Sources */, 7F950A911A35E967003C8C5C /* LeaderboardViewController.swift in Sources */, C8F71EED1D8691F700826DE4 /* UIImageView+AFNetworking.m in Sources */, - C8F71EEB1D8691F700826DE4 /* UIAlertView+AFNetworking.m in Sources */, C88F6D2D1FEACC5700A616BE /* LeaderboardGroupData.swift in Sources */, C87A74EA1F5053AE003DE396 /* RegattaCompetitorCreateAndAddData.swift in Sources */, C8E0D3131F59475B001EEF5C /* CheckInTableViewController.swift in Sources */, diff --git a/ios/SAPTracker/src/xcode/SAPTracker/CheckInRequestManager.swift b/ios/SAPTracker/src/xcode/SAPTracker/CheckInRequestManager.swift index b5005c7c731..7bbb816a88a 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/CheckInRequestManager.swift +++ b/ios/SAPTracker/src/xcode/SAPTracker/CheckInRequestManager.swift @@ -85,12 +85,12 @@ class CheckInRequestManager: NSObject { } let baseURLString: String - let manager: AFHTTPRequestOperationManager + let manager: AFHTTPSessionManager let sessionManager: AFURLSessionManager init(baseURLString: String = "") { self.baseURLString = baseURLString - manager = AFHTTPRequestOperationManager(baseURL: URL(string: baseURLString)) + manager = AFHTTPSessionManager(baseURL: URL(string: baseURLString)) manager.requestSerializer = AFJSONRequestSerializer() as AFHTTPRequestSerializer manager.requestSerializer.timeoutInterval = Application.RequestTimeout manager.responseSerializer = AFJSONResponseSerializer() as AFHTTPResponseSerializer @@ -111,6 +111,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getEventSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getEventFailure(error: error, failure: failure) } ) @@ -138,6 +139,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getLeaderboardSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getLeaderboardFailure(error: error, failure: failure) } ) @@ -165,6 +167,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getBoatSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getBoatFailure(error: error, failure: failure) } ) @@ -192,6 +195,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getCompetitorSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getCompetitorFailure(error: error, failure: failure) } ) @@ -221,6 +225,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getMarkSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getMarkFailure(error: error, failure: failure) } ) @@ -248,6 +253,7 @@ class CheckInRequestManager: NSObject { manager.get( urlString, parameters: nil, + progress: nil, success: { (requestOperation, responseObject) in self.getTeamSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.getTeamFailure(error: error, failure: failure) } ) @@ -301,6 +307,7 @@ class CheckInRequestManager: NSObject { manager.post( urlString, parameters: body, + progress: nil, success: { (requestOperation, responseObject) in self.postCheckInSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.postCheckInFailure(error: error, failure: failure) } ) @@ -340,6 +347,7 @@ class CheckInRequestManager: NSObject { manager.post( urlString, parameters: body, + progress: nil, success: { (requestOperation, responseObject) in self.postCheckOutSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.postCheckOutFailure(error: error, failure: failure) } ) @@ -381,6 +389,7 @@ class CheckInRequestManager: NSObject { manager.post( urlString, parameters: body, + progress: nil, success: { (requestOperation, responseObject) in self.postGPSFixesSuccess(responseObject: responseObject, success: success) }, failure: { (requestOperation, error) in self.postGPSFixesFailure(error: error, failure: failure) } ) diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.h deleted file mode 100644 index cf6def4e3e4..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.h +++ /dev/null @@ -1,70 +0,0 @@ -// AFHTTPRequestOperation.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import -#import "AFURLConnectionOperation.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. - */ -@interface AFHTTPRequestOperation : AFURLConnectionOperation - -///------------------------------------------------ -/// @name Getting HTTP URL Connection Information -///------------------------------------------------ - -/** - The last HTTP response received by the operation's connection. - */ -@property (readonly, nonatomic, strong, nullable) NSHTTPURLResponse *response; - -/** - Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an AFHTTPResponse serializer, which uses the raw data as its response object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed. - - @warning `responseSerializer` must not be `nil`. Setting a response serializer will clear out any cached value - */ -@property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; - -/** - An object constructed by the `responseSerializer` from the response and response data. Returns `nil` unless the operation `isFinished`, has a `response`, and has `responseData` with non-zero content length. If an error occurs during serialization, `nil` will be returned, and the `error` property will be populated with the serialization error. - */ -@property (readonly, nonatomic, strong, nullable) id responseObject; - -///----------------------------------------------------------- -/// @name Setting Completion Block Success / Failure Callbacks -///----------------------------------------------------------- - -/** - Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed. - - This method should be overridden in subclasses in order to specify the response object passed into the success block. - - @param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request. - @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request. - */ -- (void)setCompletionBlockWithSuccess:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.m deleted file mode 100644 index b8deda839a4..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperation.m +++ /dev/null @@ -1,206 +0,0 @@ -// AFHTTPRequestOperation.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import "AFHTTPRequestOperation.h" - -static dispatch_queue_t http_request_operation_processing_queue() { - static dispatch_queue_t af_http_request_operation_processing_queue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - af_http_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.http-request.processing", DISPATCH_QUEUE_CONCURRENT); - }); - - return af_http_request_operation_processing_queue; -} - -static dispatch_group_t http_request_operation_completion_group() { - static dispatch_group_t af_http_request_operation_completion_group; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - af_http_request_operation_completion_group = dispatch_group_create(); - }); - - return af_http_request_operation_completion_group; -} - -#pragma mark - - -@interface AFURLConnectionOperation () -@property (readwrite, nonatomic, strong) NSURLRequest *request; -@property (readwrite, nonatomic, strong) NSURLResponse *response; -@end - -@interface AFHTTPRequestOperation () -@property (readwrite, nonatomic, strong) NSHTTPURLResponse *response; -@property (readwrite, nonatomic, strong) id responseObject; -@property (readwrite, nonatomic, strong) NSError *responseSerializationError; -@property (readwrite, nonatomic, strong) NSRecursiveLock *lock; -@end - -@implementation AFHTTPRequestOperation -@dynamic response; -@dynamic lock; - -- (instancetype)initWithRequest:(NSURLRequest *)urlRequest { - self = [super initWithRequest:urlRequest]; - if (!self) { - return nil; - } - - self.responseSerializer = [AFHTTPResponseSerializer serializer]; - - return self; -} - -- (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { - NSParameterAssert(responseSerializer); - - [self.lock lock]; - _responseSerializer = responseSerializer; - self.responseObject = nil; - self.responseSerializationError = nil; - [self.lock unlock]; -} - -- (id)responseObject { - [self.lock lock]; - if (!_responseObject && [self isFinished] && !self.error) { - NSError *error = nil; - self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error]; - if (error) { - self.responseSerializationError = error; - } - } - [self.lock unlock]; - - return _responseObject; -} - -- (NSError *)error { - if (_responseSerializationError) { - return _responseSerializationError; - } else { - return [super error]; - } -} - -#pragma mark - AFHTTPRequestOperation - -- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-retain-cycles" -#pragma clang diagnostic ignored "-Wgnu" - self.completionBlock = ^{ - if (self.completionGroup) { - dispatch_group_enter(self.completionGroup); - } - - dispatch_async(http_request_operation_processing_queue(), ^{ - if (self.error) { - if (failure) { - dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ - failure(self, self.error); - }); - } - } else { - id responseObject = self.responseObject; - if (self.error) { - if (failure) { - dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ - failure(self, self.error); - }); - } - } else { - if (success) { - dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ - success(self, responseObject); - }); - } - } - } - - if (self.completionGroup) { - dispatch_group_leave(self.completionGroup); - } - }); - }; -#pragma clang diagnostic pop -} - -#pragma mark - AFURLRequestOperation - -- (void)pause { - [super pause]; - - u_int64_t offset = 0; - if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { - offset = [(NSNumber *)[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue]; - } else { - offset = [(NSData *)[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; - } - - NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy]; - if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) { - [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; - } - [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"]; - self.request = mutableURLRequest; -} - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (id)initWithCoder:(NSCoder *)decoder { - self = [super initWithCoder:decoder]; - if (!self) { - return nil; - } - - self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - - [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone { - AFHTTPRequestOperation *operation = [super copyWithZone:zone]; - - operation.responseSerializer = [self.responseSerializer copyWithZone:zone]; - operation.completionQueue = self.completionQueue; - operation.completionGroup = self.completionGroup; - - return operation; -} - -@end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.h deleted file mode 100644 index 81708116541..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.h +++ /dev/null @@ -1,326 +0,0 @@ -// AFHTTPRequestOperationManager.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import -#import -#import - -#if __IPHONE_OS_VERSION_MIN_REQUIRED -#import -#else -#import -#endif - -#import "AFHTTPRequestOperation.h" -#import "AFURLResponseSerialization.h" -#import "AFURLRequestSerialization.h" -#import "AFSecurityPolicy.h" -#import "AFNetworkReachabilityManager.h" - -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - -NS_ASSUME_NONNULL_BEGIN - -/** - `AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management. - - ## Subclassing Notes - - Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application. - - For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect. - - ## Methods to Override - - To change the behavior of all request operation construction for an `AFHTTPRequestOperationManager` subclass, override `HTTPRequestOperationWithRequest:success:failure`. - - ## Serialization - - Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to ``. - - Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `` - - ## URL Construction Using Relative Paths - - For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`. - - Below are a few examples of how `baseURL` and relative paths interact: - - NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; - [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo - [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz - [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo - [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo - [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ - [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ - - Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash. - - ## Network Reachability Monitoring - - Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details. - - ## NSSecureCoding & NSCopying Caveats - - `AFHTTPRequestOperationManager` conforms to the `NSSecureCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. There are a few minor caveats to keep in mind, however: - - - Archives and copies of HTTP clients will be initialized with an empty operation queue. - - NSSecureCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set. - */ -@interface AFHTTPRequestOperationManager : NSObject - -/** - The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods. - */ -@property (readonly, nonatomic, strong, nullable) NSURL *baseURL; - -/** - Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies. - - @warning `requestSerializer` must not be `nil`. - */ -@property (nonatomic, strong) AFHTTPRequestSerializer * requestSerializer; - -/** - Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to a JSON serializer, which serializes data from responses with a `application/json` MIME type, and falls back to the raw data object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed. - - @warning `responseSerializer` must not be `nil`. - */ -@property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; - -/** - The operation queue on which request operations are scheduled and run. - */ -@property (nonatomic, strong) NSOperationQueue *operationQueue; - -///------------------------------- -/// @name Managing URL Credentials -///------------------------------- - -/** - Whether request operations should consult the credential storage for authenticating the connection. `YES` by default. - - @see AFURLConnectionOperation -shouldUseCredentialStorage - */ -@property (nonatomic, assign) BOOL shouldUseCredentialStorage; - -/** - The credential used by request operations for authentication challenges. - - @see AFURLConnectionOperation -credential - */ -@property (nonatomic, strong, nullable) NSURLCredential *credential; - -///------------------------------- -/// @name Managing Security Policy -///------------------------------- - -/** - The security policy used by created request operations to evaluate server trust for secure connections. `AFHTTPRequestOperationManager` uses the `defaultPolicy` unless otherwise specified. - */ -@property (nonatomic, strong) AFSecurityPolicy *securityPolicy; - -///------------------------------------ -/// @name Managing Network Reachability -///------------------------------------ - -/** - The network reachability manager. `AFHTTPRequestOperationManager` uses the `sharedManager` by default. - */ -@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager; - -///------------------------------- -/// @name Managing Callback Queues -///------------------------------- - -/** - The dispatch queue for the `completionBlock` of request operations. If `NULL` (default), the main queue is used. - */ -#if OS_OBJECT_USE_OBJC -@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; -#else -@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue; -#endif - -/** - The dispatch group for the `completionBlock` of request operations. If `NULL` (default), a private dispatch group is used. - */ -#if OS_OBJECT_USE_OBJC -@property (nonatomic, strong, nullable) dispatch_group_t completionGroup; -#else -@property (nonatomic, assign, nullable) dispatch_group_t completionGroup; -#endif - -///--------------------------------------------- -/// @name Creating and Initializing HTTP Clients -///--------------------------------------------- - -/** - Creates and returns an `AFHTTPRequestOperationManager` object. - */ -+ (instancetype)manager; - -/** - Initializes an `AFHTTPRequestOperationManager` object with the specified base URL. - - This is the designated initializer. - - @param url The base URL for the HTTP client. - - @return The newly-initialized HTTP client - */ -- (instancetype)initWithBaseURL:(nullable NSURL *)url NS_DESIGNATED_INITIALIZER; - -///--------------------------------------- -/// @name Managing HTTP Request Operations -///--------------------------------------- - -/** - Creates an `AFHTTPRequestOperation`, and sets the response serializers to that of the HTTP client. - - @param request The request object to be loaded asynchronously during execution of the operation. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the created request operation and the object created from the response data of request. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - */ -- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; - -///--------------------------- -/// @name Making HTTP Requests -///--------------------------- - -/** - Creates and runs an `AFHTTPRequestOperation` with a `GET` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)GET:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a `HEAD` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single arguments: the request operation. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)HEAD:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a `POST` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a multipart `POST` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString - parameters:(nullable id)parameters - constructingBodyWithBlock:(nullable void (^)(id formData))block - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a `PUT` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)PUT:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a `PATCH` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)PATCH:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -/** - Creates and runs an `AFHTTPRequestOperation` with a `DELETE` request. - - @param URLString The URL string used to create the request URL. - @param parameters The parameters to be encoded according to the client request serializer. - @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes two arguments: the request operation, and the response object created by the client response serializer. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the request operation and the error describing the network or parsing error that occurred. - - @see -HTTPRequestOperationWithRequest:success:failure: - */ -- (nullable AFHTTPRequestOperation *)DELETE:(NSString *)URLString - parameters:(nullable id)parameters - success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.m deleted file mode 100644 index ee682b794ed..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPRequestOperationManager.m +++ /dev/null @@ -1,290 +0,0 @@ -// AFHTTPRequestOperationManager.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import - -#import "AFHTTPRequestOperationManager.h" -#import "AFHTTPRequestOperation.h" - -#import -#import - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#import -#endif - -@interface AFHTTPRequestOperationManager () -@property (readwrite, nonatomic, strong) NSURL *baseURL; -@end - -@implementation AFHTTPRequestOperationManager - -+ (instancetype)manager { - return [[self alloc] initWithBaseURL:nil]; -} - -- (instancetype)init { - return [self initWithBaseURL:nil]; -} - -- (instancetype)initWithBaseURL:(NSURL *)url { - self = [super init]; - if (!self) { - return nil; - } - - // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected - if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) { - url = [url URLByAppendingPathComponent:@""]; - } - - self.baseURL = url; - - self.requestSerializer = [AFHTTPRequestSerializer serializer]; - self.responseSerializer = [AFJSONResponseSerializer serializer]; - - self.securityPolicy = [AFSecurityPolicy defaultPolicy]; - - self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; - - self.operationQueue = [[NSOperationQueue alloc] init]; - - self.shouldUseCredentialStorage = YES; - - return self; -} - -#pragma mark - - -#ifdef _SYSTEMCONFIGURATION_H -#endif - -- (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { - NSParameterAssert(requestSerializer); - - _requestSerializer = requestSerializer; -} - -- (void)setResponseSerializer:(AFHTTPResponseSerializer *)responseSerializer { - NSParameterAssert(responseSerializer); - - _responseSerializer = responseSerializer; -} - -#pragma mark - - -- (AFHTTPRequestOperation *)HTTPRequestOperationWithHTTPMethod:(NSString *)method - URLString:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - NSError *serializationError = nil; - NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; - if (serializationError) { - if (failure) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ - failure(nil, serializationError); - }); -#pragma clang diagnostic pop - } - - return nil; - } - - return [self HTTPRequestOperationWithRequest:request success:success failure:failure]; -} - -- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; - operation.responseSerializer = self.responseSerializer; - operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage; - operation.credential = self.credential; - operation.securityPolicy = self.securityPolicy; - - [operation setCompletionBlockWithSuccess:success failure:failure]; - operation.completionQueue = self.completionQueue; - operation.completionGroup = self.completionGroup; - - return operation; -} - -#pragma mark - - -- (AFHTTPRequestOperation *)GET:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)HEAD:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(AFHTTPRequestOperation *requestOperation, __unused id responseObject) { - if (success) { - success(requestOperation); - } - } failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)POST:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)POST:(NSString *)URLString - parameters:(id)parameters - constructingBodyWithBlock:(void (^)(id formData))block - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - NSError *serializationError = nil; - NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError]; - if (serializationError) { - if (failure) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ - failure(nil, serializationError); - }); -#pragma clang diagnostic pop - } - - return nil; - } - - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)PUT:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)PATCH:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -- (AFHTTPRequestOperation *)DELETE:(NSString *)URLString - parameters:(id)parameters - success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure]; - - [self.operationQueue addOperation:operation]; - - return operation; -} - -#pragma mark - NSObject - -- (NSString *)description { - return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.operationQueue]; -} - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (id)initWithCoder:(NSCoder *)decoder { - NSURL *baseURL = [decoder decodeObjectForKey:NSStringFromSelector(@selector(baseURL))]; - - self = [self initWithBaseURL:baseURL]; - if (!self) { - return nil; - } - - self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))]; - self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; - AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))]; - if (decodedPolicy) { - self.securityPolicy = decodedPolicy; - } - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; - [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; - [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; - [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone { - AFHTTPRequestOperationManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL]; - - HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; - HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; - HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone]; - - return HTTPClient; -} - -@end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.h old mode 100644 new mode 100755 index 04b087626fa..5ce279a6577 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.h @@ -1,5 +1,5 @@ // AFHTTPSessionManager.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,9 @@ #if !TARGET_OS_WATCH #import #endif -#import +#import -#if __IPHONE_OS_VERSION_MIN_REQUIRED +#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV #import #else #import @@ -33,14 +33,6 @@ #import "AFURLSessionManager.h" -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - /** `AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths. @@ -79,8 +71,6 @@ @warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance. */ -#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_OS_WATCH - NS_ASSUME_NONNULL_BEGIN @interface AFHTTPSessionManager : AFURLSessionManager @@ -151,8 +141,26 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + + +/** + Creates and runs an `NSURLSessionDataTask` with a `GET` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param downloadProgress A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString + parameters:(nullable id)parameters + progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `HEAD` request. @@ -167,7 +175,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `POST` request. @@ -181,8 +189,25 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + +/** + Creates and runs an `NSURLSessionDataTask` with a `POST` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. @@ -198,8 +223,27 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id formData))block - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; + +/** + Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request. + + @param URLString The URL string used to create the request URL. + @param parameters The parameters to be encoded according to the client request serializer. + @param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol. + @param uploadProgress A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer. + @param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred. + + @see -dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: + */ +- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + constructingBodyWithBlock:(nullable void (^)(id formData))block + progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PUT` request. @@ -213,8 +257,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id)parameters - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `PATCH` request. @@ -228,8 +272,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; /** Creates and runs an `NSURLSessionDataTask` with a `DELETE` request. @@ -243,11 +287,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id)parameters - success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success - failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure; + success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; @end NS_ASSUME_NONNULL_END - -#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.m old mode 100644 new mode 100755 index ed46b26d3cf..259107060f0 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFHTTPSessionManager.m @@ -1,5 +1,5 @@ // AFHTTPSessionManager.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,23 +21,20 @@ #import "AFHTTPSessionManager.h" -#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_WATCH_OS - #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import +#import #import -#ifdef _SYSTEMCONFIGURATION_H #import #import #import #import #import -#endif -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV #import #elif TARGET_OS_WATCH #import @@ -89,9 +86,6 @@ #pragma mark - -#ifdef _SYSTEMCONFIGURATION_H -#endif - - (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { NSParameterAssert(requestSerializer); @@ -111,7 +105,24 @@ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure]; + + return [self GET:URLString parameters:parameters progress:nil success:success failure:failure]; +} + +- (NSURLSessionDataTask *)GET:(NSString *)URLString + parameters:(id)parameters + progress:(void (^)(NSProgress * _Nonnull))downloadProgress + success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" + URLString:URLString + parameters:parameters + uploadProgress:nil + downloadProgress:downloadProgress + success:success + failure:failure]; [dataTask resume]; @@ -123,7 +134,7 @@ success:(void (^)(NSURLSessionDataTask *task))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(NSURLSessionDataTask *task, __unused id responseObject) { + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, __unused id responseObject) { if (success) { success(task); } @@ -139,16 +150,35 @@ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; + return [self POST:URLString parameters:parameters progress:nil success:success failure:failure]; +} + +- (NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(id)parameters + progress:(void (^)(NSProgress * _Nonnull))uploadProgress + success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure]; [dataTask resume]; return dataTask; } +- (NSURLSessionDataTask *)POST:(NSString *)URLString + parameters:(nullable id)parameters + constructingBodyWithBlock:(nullable void (^)(id _Nonnull))block + success:(nullable void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success + failure:(nullable void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure +{ + return [self POST:URLString parameters:parameters constructingBodyWithBlock:block progress:nil success:success failure:failure]; +} + - (NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(id)parameters constructingBodyWithBlock:(void (^)(id formData))block + progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { @@ -167,7 +197,7 @@ return nil; } - __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { + __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(task, error); @@ -189,7 +219,7 @@ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -201,7 +231,7 @@ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -213,7 +243,7 @@ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { - NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure]; + NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:nil success:success failure:failure]; [dataTask resume]; @@ -223,6 +253,8 @@ - (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(void (^)(NSURLSessionDataTask *, id))success failure:(void (^)(NSURLSessionDataTask *, NSError *))failure { @@ -242,7 +274,10 @@ } __block NSURLSessionDataTask *dataTask = nil; - dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { + dataTask = [self dataTaskWithRequest:request + uploadProgress:uploadProgress + downloadProgress:downloadProgress + completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error); @@ -269,7 +304,7 @@ return YES; } -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))]; NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; if (!configuration) { @@ -314,7 +349,7 @@ #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration]; HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; @@ -324,5 +359,3 @@ } @end - -#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.h old mode 100644 new mode 100755 index e2eb945df04..0feb18d3950 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.h @@ -1,5 +1,5 @@ // AFNetworkReachabilityManager.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,14 +24,6 @@ #if !TARGET_OS_WATCH #import -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, @@ -46,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN Reachability can be used to determine background information about why a network operation failed, or to trigger a network operation retrying when a connection is established. It should not be used to prevent a user from initiating a network request, as it's possible that an initial request may be required to establish reachability. - See Apple's Reachability Sample Code (https://developer.apple.com/library/ios/samplecode/reachability/) + See Apple's Reachability Sample Code ( https://developer.apple.com/library/ios/samplecode/reachability/ ) @warning Instances of `AFNetworkReachabilityManager` must be started with `-startMonitoring` before reachability status can be determined. */ @@ -81,6 +73,13 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)sharedManager; +/** + Creates and returns a network reachability manager with the default socket address. + + @return An initialized network reachability manager, actively monitoring the default socket address. + */ ++ (instancetype)manager; + /** Creates and returns a network reachability manager for the specified domain. @@ -93,7 +92,7 @@ NS_ASSUME_NONNULL_BEGIN /** Creates and returns a network reachability manager for the socket address. - @param address The socket address (`sockaddr_in`) used to evaluate network reachability. + @param address The socket address (`sockaddr_in6`) used to evaluate network reachability. @return An initialized network reachability manager, actively monitoring the specified socket address. */ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.m old mode 100644 new mode 100755 index fe67781e3c0..d45836485da --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworkReachabilityManager.m @@ -1,5 +1,5 @@ // AFNetworkReachabilityManager.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -94,6 +94,7 @@ static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused targ AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); } + static const void * AFNetworkReachabilityRetainCallback(const void *info) { return Block_copy(info); } @@ -105,7 +106,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { } @interface AFNetworkReachabilityManager () -@property (readwrite, nonatomic, strong) id networkReachability; +@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability; @property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; @end @@ -116,35 +117,46 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { static AFNetworkReachabilityManager *_sharedManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - struct sockaddr_in address; - bzero(&address, sizeof(address)); - address.sin_len = sizeof(address); - address.sin_family = AF_INET; - - _sharedManager = [self managerForAddress:&address]; + _sharedManager = [self manager]; }); return _sharedManager; } -#ifndef __clang_analyzer__ + (instancetype)managerForDomain:(NSString *)domain { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; + + CFRelease(reachability); return manager; } -#endif -#ifndef __clang_analyzer__ + (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; + CFRelease(reachability); + return manager; } + ++ (instancetype)manager +{ +#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) + struct sockaddr_in6 address; + bzero(&address, sizeof(address)); + address.sin6_len = sizeof(address); + address.sin6_family = AF_INET6; +#else + struct sockaddr_in address; + bzero(&address, sizeof(address)); + address.sin_len = sizeof(address); + address.sin_family = AF_INET; #endif + return [self managerForAddress:&address]; +} - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability { self = [super init]; @@ -152,7 +164,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { return nil; } - self.networkReachability = CFBridgingRelease(reachability); + _networkReachability = CFRetain(reachability); self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; return self; @@ -165,6 +177,10 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { - (void)dealloc { [self stopMonitoring]; + + if (_networkReachability != NULL) { + CFRelease(_networkReachability); + } } #pragma mark - @@ -201,14 +217,13 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { }; - id networkReachability = self.networkReachability; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; - SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context); - SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); + SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; - if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) { + if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); @@ -219,7 +234,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { return; } - SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); + SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); } #pragma mark - diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworking.h old mode 100644 new mode 100755 index 6d442bb148e..e2fb2f44e61 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFNetworking.h @@ -22,6 +22,7 @@ #import #import +#import #ifndef _AFNETWORKING_ #define _AFNETWORKING_ @@ -29,18 +30,12 @@ #import "AFURLRequestSerialization.h" #import "AFURLResponseSerialization.h" #import "AFSecurityPolicy.h" + #if !TARGET_OS_WATCH #import "AFNetworkReachabilityManager.h" - #import "AFURLConnectionOperation.h" - #import "AFHTTPRequestOperation.h" - #import "AFHTTPRequestOperationManager.h" #endif -#if ( ( defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || \ - ( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 ) || \ - TARGET_OS_WATCH ) #import "AFURLSessionManager.h" #import "AFHTTPSessionManager.h" -#endif #endif /* _AFNETWORKING_ */ diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.h old mode 100644 new mode 100755 index d968f49d090..c005efa81bb --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.h @@ -1,5 +1,5 @@ // AFSecurityPolicy.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -44,9 +44,13 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; /** - The certificates used to evaluate server trust according to the SSL pinning mode. By default, this property is set to any (`.cer`) certificates included in the app bundle. Note that if you create an array with duplicate certificates, the duplicate certificates will be removed. Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches. + The certificates used to evaluate server trust according to the SSL pinning mode. + + By default, this property is set to any (`.cer`) certificates included in the target compiling AFNetworking. Note that if you are using AFNetworking as embedded framework, no certificates will be pinned by default. Use `certificatesInBundle` to load certificates from your target, and then create a new policy by calling `policyWithPinningMode:withPinnedCertificates`. + + Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches. */ -@property (nonatomic, strong, nullable) NSArray *pinnedCertificates; +@property (nonatomic, strong, nullable) NSSet *pinnedCertificates; /** Whether or not to trust servers with an invalid or expired SSL certificates. Defaults to `NO`. @@ -58,6 +62,17 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL validatesDomainName; +///----------------------------------------- +/// @name Getting Certificates from the Bundle +///----------------------------------------- + +/** + Returns any certificates included in the bundle. If you are using AFNetworking as an embedded framework, you must use this method to find the certificates you have included in your app bundle, and use them when creating your security policy by calling `policyWithPinningMode:withPinnedCertificates`. + + @return The certificates included in the given bundle. + */ ++ (NSSet *)certificatesInBundle:(NSBundle *)bundle; + ///----------------------------------------- /// @name Getting Specific Security Policies ///----------------------------------------- @@ -82,23 +97,20 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode; +/** + Creates and returns a security policy with the specified pinning mode. + + @param pinningMode The SSL pinning mode. + @param pinnedCertificates The certificates to pin against. + + @return A new security policy. + */ ++ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates; + ///------------------------------ /// @name Evaluating Server Trust ///------------------------------ -/** - Whether or not the specified server trust should be accepted, based on the security policy. - - This method should be used when responding to an authentication challenge from a server. - - @param serverTrust The X.509 certificate trust of the server. - - @return Whether or not to trust the server. - - @warning This method has been deprecated in favor of `-evaluateServerTrust:forDomain:`. - */ -- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust DEPRECATED_ATTRIBUTE; - /** Whether or not the specified server trust should be accepted, based on the security policy. diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.m old mode 100644 new mode 100755 index 2624775605d..ec81d377c6a --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFSecurityPolicy.m @@ -1,5 +1,5 @@ // AFSecurityPolicy.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,7 +23,7 @@ #import -#if !TARGET_OS_IOS && !TARGET_OS_WATCH +#if !TARGET_OS_IOS && !TARGET_OS_WATCH && !TARGET_OS_TV static NSData * AFSecKeyGetData(SecKeyRef key) { CFDataRef data = NULL; @@ -41,7 +41,7 @@ _out: #endif static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) { -#if TARGET_OS_IOS || TARGET_OS_WATCH +#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV return [(__bridge id)key1 isEqual:(__bridge id)key2]; #else return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)]; @@ -150,25 +150,29 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { @interface AFSecurityPolicy() @property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode; -@property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys; +@property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys; @end @implementation AFSecurityPolicy -+ (NSArray *)defaultPinnedCertificates { - static NSArray *_defaultPinnedCertificates = nil; ++ (NSSet *)certificatesInBundle:(NSBundle *)bundle { + NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; + + NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]]; + for (NSString *path in paths) { + NSData *certificateData = [NSData dataWithContentsOfFile:path]; + [certificates addObject:certificateData]; + } + + return [NSSet setWithSet:certificates]; +} + ++ (NSSet *)defaultPinnedCertificates { + static NSSet *_defaultPinnedCertificates = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle bundleForClass:[self class]]; - NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; - - NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]]; - for (NSString *path in paths) { - NSData *certificateData = [NSData dataWithContentsOfFile:path]; - [certificates addObject:certificateData]; - } - - _defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates]; + _defaultPinnedCertificates = [self certificatesInBundle:bundle]; }); return _defaultPinnedCertificates; @@ -182,15 +186,19 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { } + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode { + return [self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]]; +} + ++ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates { AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = pinningMode; - [securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]]; + [securityPolicy setPinnedCertificates:pinnedCertificates]; return securityPolicy; } -- (id)init { +- (instancetype)init { self = [super init]; if (!self) { return nil; @@ -201,11 +209,11 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { return self; } -- (void)setPinnedCertificates:(NSArray *)pinnedCertificates { - _pinnedCertificates = [[NSOrderedSet orderedSetWithArray:pinnedCertificates] array]; +- (void)setPinnedCertificates:(NSSet *)pinnedCertificates { + _pinnedCertificates = pinnedCertificates; if (self.pinnedCertificates) { - NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]]; + NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]]; for (NSData *certificate in self.pinnedCertificates) { id publicKey = AFPublicKeyForCertificate(certificate); if (!publicKey) { @@ -213,7 +221,7 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { } [mutablePinnedPublicKeys addObject:publicKey]; } - self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys]; + self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys]; } else { self.pinnedPublicKeys = nil; } @@ -221,10 +229,6 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { #pragma mark - -- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust { - return [self evaluateServerTrust:serverTrust forDomain:nil]; -} - - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { @@ -256,7 +260,6 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { return NO; } - NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); switch (self.SSLPinningMode) { case AFSSLPinningModeNone: default: @@ -272,13 +275,16 @@ static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) { return NO; } - NSUInteger trustedCertificateCount = 0; - for (NSData *trustChainCertificate in serverCertificates) { + // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA) + NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust); + + for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) { if ([self.pinnedCertificates containsObject:trustChainCertificate]) { - trustedCertificateCount++; + return YES; } } - return trustedCertificateCount > 0; + + return NO; } case AFSSLPinningModePublicKey: { NSUInteger trustedPublicKeyCount = 0; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.h deleted file mode 100644 index c21119e2ba3..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.h +++ /dev/null @@ -1,344 +0,0 @@ -// AFURLConnectionOperation.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import - -#import -#import "AFURLRequestSerialization.h" -#import "AFURLResponseSerialization.h" -#import "AFSecurityPolicy.h" - -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - -/** - `AFURLConnectionOperation` is a subclass of `NSOperation` that implements `NSURLConnection` delegate methods. - - ## Subclassing Notes - - This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors. - - If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes. - - ## NSURLConnection Delegate Methods - - `AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods: - - - `connection:didReceiveResponse:` - - `connection:didReceiveData:` - - `connectionDidFinishLoading:` - - `connection:didFailWithError:` - - `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:` - - `connection:willCacheResponse:` - - `connectionShouldUseCredentialStorage:` - - `connection:needNewBodyStream:` - - `connection:willSendRequestForAuthenticationChallenge:` - - If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first. - - ## Callbacks and Completion Blocks - - The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this. - - Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/). - - ## SSL Pinning - - Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle. - - SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice. - - Connections will be validated on all matching certificates with a `.cer` extension in the bundle root. - - ## NSCoding & NSCopying Conformance - - `AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind: - - ### NSCoding Caveats - - - Encoded operations do not include any block or stream properties. Be sure to set `completionBlock`, `outputStream`, and any callback blocks as necessary when using `-initWithCoder:` or `NSKeyedUnarchiver`. - - Operations are paused on `encodeWithCoder:`. If the operation was encoded while paused or still executing, its archived state will return `YES` for `isReady`. Otherwise, the state of an operation when encoding will remain unchanged. - - ### NSCopying Caveats - - - `-copy` and `-copyWithZone:` return a new operation with the `NSURLRequest` of the original. So rather than an exact copy of the operation at that particular instant, the copying mechanism returns a completely new instance, which can be useful for retrying operations. - - A copy of an operation will not include the `outputStream` of the original. - - Operation copies do not include `completionBlock`, as it often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied. - */ - -NS_ASSUME_NONNULL_BEGIN - -@interface AFURLConnectionOperation : NSOperation - -///------------------------------- -/// @name Accessing Run Loop Modes -///------------------------------- - -/** - The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`. - */ -@property (nonatomic, strong) NSSet *runLoopModes; - -///----------------------------------------- -/// @name Getting URL Connection Information -///----------------------------------------- - -/** - The request used by the operation's connection. - */ -@property (readonly, nonatomic, strong) NSURLRequest *request; - -/** - The last response received by the operation's connection. - */ -@property (readonly, nonatomic, strong, nullable) NSURLResponse *response; - -/** - The error, if any, that occurred in the lifecycle of the request. - */ -@property (readonly, nonatomic, strong, nullable) NSError *error; - -///---------------------------- -/// @name Getting Response Data -///---------------------------- - -/** - The data received during the request. - */ -@property (readonly, nonatomic, strong, nullable) NSData *responseData; - -/** - The string representation of the response data. - */ -@property (readonly, nonatomic, copy, nullable) NSString *responseString; - -/** - The string encoding of the response. - - If the response does not specify a valid string encoding, `responseStringEncoding` will return `NSUTF8StringEncoding`. - */ -@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding; - -///------------------------------- -/// @name Managing URL Credentials -///------------------------------- - -/** - Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. - - This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. - */ -@property (nonatomic, assign) BOOL shouldUseCredentialStorage; - -/** - The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. - - This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. - */ -@property (nonatomic, strong, nullable) NSURLCredential *credential; - -///------------------------------- -/// @name Managing Security Policy -///------------------------------- - -/** - The security policy used to evaluate server trust for secure connections. - */ -@property (nonatomic, strong) AFSecurityPolicy *securityPolicy; - -///------------------------ -/// @name Accessing Streams -///------------------------ - -/** - The input stream used to read data to be sent during the request. - - This property acts as a proxy to the `HTTPBodyStream` property of `request`. - */ -@property (nonatomic, strong) NSInputStream *inputStream; - -/** - The output stream that is used to write data received until the request is finished. - - By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request, with the intermediary `outputStream` property set to `nil`. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. - */ -@property (nonatomic, strong, nullable) NSOutputStream *outputStream; - -///--------------------------------- -/// @name Managing Callback Queues -///--------------------------------- - -/** - The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used. - */ -#if OS_OBJECT_USE_OBJC -@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; -#else -@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue; -#endif - -/** - The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used. - */ -#if OS_OBJECT_USE_OBJC -@property (nonatomic, strong, nullable) dispatch_group_t completionGroup; -#else -@property (nonatomic, assign, nullable) dispatch_group_t completionGroup; -#endif - -///--------------------------------------------- -/// @name Managing Request Operation Information -///--------------------------------------------- - -/** - The user info dictionary for the receiver. - */ -@property (nonatomic, strong) NSDictionary *userInfo; -// FIXME: It doesn't seem that this userInfo is used anywhere in the implementation. - -///------------------------------------------------------ -/// @name Initializing an AFURLConnectionOperation Object -///------------------------------------------------------ - -/** - Initializes and returns a newly allocated operation object with a url connection configured with the specified url request. - - This is the designated initializer. - - @param urlRequest The request object to be used by the operation connection. - */ -- (instancetype)initWithRequest:(NSURLRequest *)urlRequest NS_DESIGNATED_INITIALIZER; - -///---------------------------------- -/// @name Pausing / Resuming Requests -///---------------------------------- - -/** - Pauses the execution of the request operation. - - A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect. - */ -- (void)pause; - -/** - Whether the request operation is currently paused. - - @return `YES` if the operation is currently paused, otherwise `NO`. - */ -- (BOOL)isPaused; - -/** - Resumes the execution of the paused request operation. - - Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. - */ -- (void)resume; - -///---------------------------------------------- -/// @name Configuring Backgrounding Task Behavior -///---------------------------------------------- - -/** - Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task. - - @param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified. - */ -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(nullable void (^)(void))handler NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions."); -#endif - -///--------------------------------- -/// @name Setting Progress Callbacks -///--------------------------------- - -/** - Sets a callback to be called when an undetermined number of bytes have been uploaded to the server. - - @param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes three arguments: the number of bytes written since the last time the upload progress block was called, the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times, and will execute on the main thread. - */ -- (void)setUploadProgressBlock:(nullable void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; - -/** - Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. - - @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. - */ -- (void)setDownloadProgressBlock:(nullable void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; - -///------------------------------------------------- -/// @name Setting NSURLConnection Delegate Callbacks -///------------------------------------------------- - -/** - Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`. - - @param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol). - - If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates. - */ -- (void)setWillSendRequestForAuthenticationChallengeBlock:(nullable void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; - -/** - Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDataDelegate` method `connection:willSendRequest:redirectResponse:`. - - @param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect. - */ -- (void)setRedirectResponseBlock:(nullable NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block; - - -/** - Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`. - - @param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request. - */ -- (void)setCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block; - -/// - -/** - - */ -+ (NSArray *)batchOfRequestOperations:(nullable NSArray *)operations - progressBlock:(nullable void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock - completionBlock:(nullable void (^)(NSArray *operations))completionBlock; - -@end - -///-------------------- -/// @name Notifications -///-------------------- - -/** - Posted when an operation begins executing. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingOperationDidStartNotification; - -/** - Posted when an operation finishes. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingOperationDidFinishNotification; - -NS_ASSUME_NONNULL_END diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.m deleted file mode 100644 index 2126ca6f067..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLConnectionOperation.m +++ /dev/null @@ -1,792 +0,0 @@ -// AFURLConnectionOperation.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import "AFURLConnectionOperation.h" - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#import -#endif - -#if !__has_feature(objc_arc) -#error AFNetworking must be built with ARC. -// You can turn on ARC for only AFNetworking files by adding -fobjc-arc to the build phase for each of its files. -#endif - -typedef NS_ENUM(NSInteger, AFOperationState) { - AFOperationPausedState = -1, - AFOperationReadyState = 1, - AFOperationExecutingState = 2, - AFOperationFinishedState = 3, -}; - -static dispatch_group_t url_request_operation_completion_group() { - static dispatch_group_t af_url_request_operation_completion_group; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - af_url_request_operation_completion_group = dispatch_group_create(); - }); - - return af_url_request_operation_completion_group; -} - -static dispatch_queue_t url_request_operation_completion_queue() { - static dispatch_queue_t af_url_request_operation_completion_queue; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT ); - }); - - return af_url_request_operation_completion_queue; -} - -static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; - -NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; -NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; - -typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); -typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); -typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); -typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse); -typedef void (^AFURLConnectionOperationBackgroundTaskCleanupBlock)(); - -static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { - switch (state) { - case AFOperationReadyState: - return @"isReady"; - case AFOperationExecutingState: - return @"isExecuting"; - case AFOperationFinishedState: - return @"isFinished"; - case AFOperationPausedState: - return @"isPaused"; - default: { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" - return @"state"; -#pragma clang diagnostic pop - } - } -} - -static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { - switch (fromState) { - case AFOperationReadyState: - switch (toState) { - case AFOperationPausedState: - case AFOperationExecutingState: - return YES; - case AFOperationFinishedState: - return isCancelled; - default: - return NO; - } - case AFOperationExecutingState: - switch (toState) { - case AFOperationPausedState: - case AFOperationFinishedState: - return YES; - default: - return NO; - } - case AFOperationFinishedState: - return NO; - case AFOperationPausedState: - return toState == AFOperationReadyState; - default: { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" - switch (toState) { - case AFOperationPausedState: - case AFOperationReadyState: - case AFOperationExecutingState: - case AFOperationFinishedState: - return YES; - default: - return NO; - } - } -#pragma clang diagnostic pop - } -} - -@interface AFURLConnectionOperation () -@property (readwrite, nonatomic, assign) AFOperationState state; -@property (readwrite, nonatomic, strong) NSRecursiveLock *lock; -@property (readwrite, nonatomic, strong) NSURLConnection *connection; -@property (readwrite, nonatomic, strong) NSURLRequest *request; -@property (readwrite, nonatomic, strong) NSURLResponse *response; -@property (readwrite, nonatomic, strong) NSError *error; -@property (readwrite, nonatomic, strong) NSData *responseData; -@property (readwrite, nonatomic, copy) NSString *responseString; -@property (readwrite, nonatomic, assign) NSStringEncoding responseStringEncoding; -@property (readwrite, nonatomic, assign) long long totalBytesRead; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationBackgroundTaskCleanupBlock backgroundTaskCleanup; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; -@property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse; - -- (void)operationDidStart; -- (void)finish; -- (void)cancelConnection; -@end - -@implementation AFURLConnectionOperation -@synthesize outputStream = _outputStream; - -+ (void)networkRequestThreadEntryPoint:(id)__unused object { - @autoreleasepool { - [[NSThread currentThread] setName:@"AFNetworking"]; - - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; - [runLoop run]; - } -} - -+ (NSThread *)networkRequestThread { - static NSThread *_networkRequestThread = nil; - static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ - _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; - [_networkRequestThread start]; - }); - - return _networkRequestThread; -} - -- (instancetype)initWithRequest:(NSURLRequest *)urlRequest { - NSParameterAssert(urlRequest); - - self = [super init]; - if (!self) { - return nil; - } - - _state = AFOperationReadyState; - - self.lock = [[NSRecursiveLock alloc] init]; - self.lock.name = kAFNetworkingLockName; - - self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; - - self.request = urlRequest; - - self.shouldUseCredentialStorage = YES; - - self.securityPolicy = [AFSecurityPolicy defaultPolicy]; - - return self; -} - -- (instancetype)init NS_UNAVAILABLE -{ - return nil; -} - -- (void)dealloc { - if (_outputStream) { - [_outputStream close]; - _outputStream = nil; - } - - if (_backgroundTaskCleanup) { - _backgroundTaskCleanup(); - } -} - -#pragma mark - - -- (void)setResponseData:(NSData *)responseData { - [self.lock lock]; - if (!responseData) { - _responseData = nil; - } else { - _responseData = [NSData dataWithBytes:responseData.bytes length:responseData.length]; - } - [self.lock unlock]; -} - -- (NSString *)responseString { - [self.lock lock]; - if (!_responseString && self.response && self.responseData) { - self.responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseStringEncoding]; - } - [self.lock unlock]; - - return _responseString; -} - -- (NSStringEncoding)responseStringEncoding { - [self.lock lock]; - if (!_responseStringEncoding && self.response) { - NSStringEncoding stringEncoding = NSUTF8StringEncoding; - if (self.response.textEncodingName) { - CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName); - if (IANAEncoding != kCFStringEncodingInvalidId) { - stringEncoding = CFStringConvertEncodingToNSStringEncoding(IANAEncoding); - } - } - - self.responseStringEncoding = stringEncoding; - } - [self.lock unlock]; - - return _responseStringEncoding; -} - -- (NSInputStream *)inputStream { - return self.request.HTTPBodyStream; -} - -- (void)setInputStream:(NSInputStream *)inputStream { - NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; - mutableRequest.HTTPBodyStream = inputStream; - self.request = mutableRequest; -} - -- (NSOutputStream *)outputStream { - if (!_outputStream) { - self.outputStream = [NSOutputStream outputStreamToMemory]; - } - - return _outputStream; -} - -- (void)setOutputStream:(NSOutputStream *)outputStream { - [self.lock lock]; - if (outputStream != _outputStream) { - if (_outputStream) { - [_outputStream close]; - } - - _outputStream = outputStream; - } - [self.lock unlock]; -} - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { - [self.lock lock]; - if (!self.backgroundTaskCleanup) { - UIApplication *application = [UIApplication sharedApplication]; - UIBackgroundTaskIdentifier __block backgroundTaskIdentifier = UIBackgroundTaskInvalid; - __weak __typeof(self)weakSelf = self; - - self.backgroundTaskCleanup = ^(){ - if (backgroundTaskIdentifier != UIBackgroundTaskInvalid) { - [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier]; - backgroundTaskIdentifier = UIBackgroundTaskInvalid; - } - }; - - backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ - __strong __typeof(weakSelf)strongSelf = weakSelf; - - if (handler) { - handler(); - } - - if (strongSelf) { - [strongSelf cancel]; - strongSelf.backgroundTaskCleanup(); - } - }]; - } - [self.lock unlock]; -} -#endif - -#pragma mark - - -- (void)setState:(AFOperationState)state { - if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) { - return; - } - - [self.lock lock]; - NSString *oldStateKey = AFKeyPathFromOperationState(self.state); - NSString *newStateKey = AFKeyPathFromOperationState(state); - - [self willChangeValueForKey:newStateKey]; - [self willChangeValueForKey:oldStateKey]; - _state = state; - [self didChangeValueForKey:oldStateKey]; - [self didChangeValueForKey:newStateKey]; - [self.lock unlock]; -} - -- (void)pause { - if ([self isPaused] || [self isFinished] || [self isCancelled]) { - return; - } - - [self.lock lock]; - if ([self isExecuting]) { - [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - - dispatch_async(dispatch_get_main_queue(), ^{ - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - [notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; - }); - } - - self.state = AFOperationPausedState; - [self.lock unlock]; -} - -- (void)operationDidPause { - [self.lock lock]; - [self.connection cancel]; - [self.lock unlock]; -} - -- (BOOL)isPaused { - return self.state == AFOperationPausedState; -} - -- (void)resume { - if (![self isPaused]) { - return; - } - - [self.lock lock]; - self.state = AFOperationReadyState; - - [self start]; - [self.lock unlock]; -} - -#pragma mark - - -- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { - self.uploadProgress = block; -} - -- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { - self.downloadProgress = block; -} - -- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { - self.authenticationChallenge = block; -} - -- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { - self.cacheResponse = block; -} - -- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block { - self.redirectResponse = block; -} - -#pragma mark - NSOperation - -- (void)setCompletionBlock:(void (^)(void))block { - [self.lock lock]; - if (!block) { - [super setCompletionBlock:nil]; - } else { - __weak __typeof(self)weakSelf = self; - [super setCompletionBlock:^ { - __strong __typeof(weakSelf)strongSelf = weakSelf; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group(); - dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue(); -#pragma clang diagnostic pop - - dispatch_group_async(group, queue, ^{ - block(); - }); - - dispatch_group_notify(group, url_request_operation_completion_queue(), ^{ - [strongSelf setCompletionBlock:nil]; - }); - }]; - } - [self.lock unlock]; -} - -- (BOOL)isReady { - return self.state == AFOperationReadyState && [super isReady]; -} - -- (BOOL)isExecuting { - return self.state == AFOperationExecutingState; -} - -- (BOOL)isFinished { - return self.state == AFOperationFinishedState; -} - -- (BOOL)isConcurrent { - return YES; -} - -- (void)start { - [self.lock lock]; - if ([self isCancelled]) { - [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - } else if ([self isReady]) { - self.state = AFOperationExecutingState; - - [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - } - [self.lock unlock]; -} - -- (void)operationDidStart { - [self.lock lock]; - if (![self isCancelled]) { - self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; - - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - for (NSString *runLoopMode in self.runLoopModes) { - [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; - [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; - } - - [self.outputStream open]; - [self.connection start]; - } - [self.lock unlock]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; - }); -} - -- (void)finish { - [self.lock lock]; - self.state = AFOperationFinishedState; - [self.lock unlock]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; - }); -} - -- (void)cancel { - [self.lock lock]; - if (![self isFinished] && ![self isCancelled]) { - [super cancel]; - - if ([self isExecuting]) { - [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - } - } - [self.lock unlock]; -} - -- (void)cancelConnection { - NSDictionary *userInfo = nil; - if ([self.request URL]) { - userInfo = @{NSURLErrorFailingURLErrorKey : [self.request URL]}; - } - NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; - - if (![self isFinished]) { - if (self.connection) { - [self.connection cancel]; - [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error]; - } else { - // Accommodate race condition where `self.connection` has not yet been set before cancellation - self.error = error; - [self finish]; - } - } -} - -#pragma mark - - -+ (NSArray *)batchOfRequestOperations:(NSArray *)operations - progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock - completionBlock:(void (^)(NSArray *operations))completionBlock -{ - if (!operations || [operations count] == 0) { - return @[[NSBlockOperation blockOperationWithBlock:^{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (completionBlock) { - completionBlock(@[]); - } - }); - }]]; - } - - __block dispatch_group_t group = dispatch_group_create(); - NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{ - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ - if (completionBlock) { - completionBlock(operations); - } - }); - }]; - - for (AFURLConnectionOperation *operation in operations) { - operation.completionGroup = group; - void (^originalCompletionBlock)(void) = [operation.completionBlock copy]; - __weak __typeof(operation)weakOperation = operation; - operation.completionBlock = ^{ - __strong __typeof(weakOperation)strongOperation = weakOperation; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue(); -#pragma clang diagnostic pop - dispatch_group_async(group, queue, ^{ - if (originalCompletionBlock) { - originalCompletionBlock(); - } - - NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL __unused *stop) { - return [op isFinished]; - }] count]; - - if (progressBlock) { - progressBlock(numberOfFinishedOperations, [operations count]); - } - - dispatch_group_leave(group); - }); - }; - - dispatch_group_enter(group); - [batchedOperation addDependency:operation]; - } - - return [operations arrayByAddingObject:batchedOperation]; -} - -#pragma mark - NSObject - -- (NSString *)description { - [self.lock lock]; - NSString *description = [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response]; - [self.lock unlock]; - return description; -} - -#pragma mark - NSURLConnectionDelegate - -- (void)connection:(NSURLConnection *)connection -willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge -{ - if (self.authenticationChallenge) { - self.authenticationChallenge(connection, challenge); - return; - } - - if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { - if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { - NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; - [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; - } else { - [[challenge sender] cancelAuthenticationChallenge:challenge]; - } - } else { - if ([challenge previousFailureCount] == 0) { - if (self.credential) { - [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; - } else { - [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; - } - } else { - [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; - } - } -} - -- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { - return self.shouldUseCredentialStorage; -} - -- (NSURLRequest *)connection:(NSURLConnection *)connection - willSendRequest:(NSURLRequest *)request - redirectResponse:(NSURLResponse *)redirectResponse -{ - if (self.redirectResponse) { - return self.redirectResponse(connection, request, redirectResponse); - } else { - return request; - } -} - -- (void)connection:(NSURLConnection __unused *)connection - didSendBodyData:(NSInteger)bytesWritten - totalBytesWritten:(NSInteger)totalBytesWritten -totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite -{ - dispatch_async(dispatch_get_main_queue(), ^{ - if (self.uploadProgress) { - self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); - } - }); -} - -- (void)connection:(NSURLConnection __unused *)connection -didReceiveResponse:(NSURLResponse *)response -{ - self.response = response; -} - -- (void)connection:(NSURLConnection __unused *)connection - didReceiveData:(NSData *)data -{ - NSUInteger length = [data length]; - while (YES) { - NSInteger totalNumberOfBytesWritten = 0; - if ([self.outputStream hasSpaceAvailable]) { - const uint8_t *dataBuffer = (uint8_t *)[data bytes]; - - NSInteger numberOfBytesWritten = 0; - while (totalNumberOfBytesWritten < (NSInteger)length) { - numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; - if (numberOfBytesWritten == -1) { - break; - } - - totalNumberOfBytesWritten += numberOfBytesWritten; - } - - break; - } else { - [self.connection cancel]; - if (self.outputStream.streamError) { - [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; - } - return; - } - } - - dispatch_async(dispatch_get_main_queue(), ^{ - self.totalBytesRead += (long long)length; - - if (self.downloadProgress) { - self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); - } - }); -} - -- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { - self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; - - [self.outputStream close]; - if (self.responseData) { - self.outputStream = nil; - } - - self.connection = nil; - - [self finish]; -} - -- (void)connection:(NSURLConnection __unused *)connection - didFailWithError:(NSError *)error -{ - self.error = error; - - [self.outputStream close]; - if (self.responseData) { - self.outputStream = nil; - } - - self.connection = nil; - - [self finish]; -} - -- (NSCachedURLResponse *)connection:(NSURLConnection *)connection - willCacheResponse:(NSCachedURLResponse *)cachedResponse -{ - if (self.cacheResponse) { - return self.cacheResponse(connection, cachedResponse); - } else { - if ([self isCancelled]) { - return nil; - } - - return cachedResponse; - } -} - -#pragma mark - NSSecureCoding - -+ (BOOL)supportsSecureCoding { - return YES; -} - -- (id)initWithCoder:(NSCoder *)decoder { - NSURLRequest *request = [decoder decodeObjectOfClass:[NSURLRequest class] forKey:NSStringFromSelector(@selector(request))]; - - self = [self initWithRequest:request]; - if (!self) { - return nil; - } - - self.state = (AFOperationState)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] integerValue]; - self.response = [decoder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:NSStringFromSelector(@selector(response))]; - self.error = [decoder decodeObjectOfClass:[NSError class] forKey:NSStringFromSelector(@selector(error))]; - self.responseData = [decoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(responseData))]; - self.totalBytesRead = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesRead))] longLongValue]; - - return self; -} - -- (void)encodeWithCoder:(NSCoder *)coder { - [self pause]; - - [coder encodeObject:self.request forKey:NSStringFromSelector(@selector(request))]; - - switch (self.state) { - case AFOperationExecutingState: - case AFOperationPausedState: - [coder encodeInteger:AFOperationReadyState forKey:NSStringFromSelector(@selector(state))]; - break; - default: - [coder encodeInteger:self.state forKey:NSStringFromSelector(@selector(state))]; - break; - } - - [coder encodeObject:self.response forKey:NSStringFromSelector(@selector(response))]; - [coder encodeObject:self.error forKey:NSStringFromSelector(@selector(error))]; - [coder encodeObject:self.responseData forKey:NSStringFromSelector(@selector(responseData))]; - [coder encodeInt64:self.totalBytesRead forKey:NSStringFromSelector(@selector(totalBytesRead))]; -} - -#pragma mark - NSCopying - -- (id)copyWithZone:(NSZone *)zone { - AFURLConnectionOperation *operation = [(AFURLConnectionOperation *)[[self class] allocWithZone:zone] initWithRequest:self.request]; - - operation.uploadProgress = self.uploadProgress; - operation.downloadProgress = self.downloadProgress; - operation.authenticationChallenge = self.authenticationChallenge; - operation.cacheResponse = self.cacheResponse; - operation.redirectResponse = self.redirectResponse; - operation.completionQueue = self.completionQueue; - operation.completionGroup = self.completionGroup; - - return operation; -} - -@end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.h old mode 100644 new mode 100755 index 15ced1dd0cd..694696b9aa4 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.h @@ -1,5 +1,5 @@ // AFURLRequestSerialization.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,7 +20,9 @@ // THE SOFTWARE. #import -#if TARGET_OS_IOS +#import + +#if TARGET_OS_IOS || TARGET_OS_TV #import #elif TARGET_OS_WATCH #import @@ -28,6 +30,31 @@ NS_ASSUME_NONNULL_BEGIN +/** + Returns a percent-escaped string following RFC 3986 for a query string key or value. + RFC 3986 states that the following characters are "reserved" characters. + - General Delimiters: ":", "#", "[", "]", "@", "?", "/" + - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" + + In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow + query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" + should be percent-escaped in the query string. + + @param string The string to be percent-escaped. + + @return The percent-escaped string. + */ +FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string); + +/** + A helper method to generate encoded url query parameters for appending to the end of a URL. + + @param parameters A dictionary of key/values to be encoded. + + @return A url encoded query string + */ +FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters); + /** The `AFURLRequestSerialization` protocol is adopted by an object that encodes parameters for a specified HTTP requests. Request serializers may encode parameters as query strings, HTTP bodies, setting the appropriate HTTP header fields as necessary. @@ -46,11 +73,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters - error:(NSError * __nullable __autoreleasing *)error -#ifdef NS_SWIFT_NOTHROW -NS_SWIFT_NOTHROW -#endif -; + error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; + @end #pragma mark - @@ -130,7 +154,7 @@ typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) { @discussion To add or remove default request headers, use `setValue:forHTTPHeaderField:`. */ -@property (readonly, nonatomic, strong) NSDictionary *HTTPRequestHeaders; +@property (readonly, nonatomic, strong) NSDictionary *HTTPRequestHeaders; /** Creates and returns a serializer with default configuration. @@ -164,12 +188,6 @@ forHTTPHeaderField:(NSString *)field; - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password; -/** - @deprecated This method has been deprecated. Use -setValue:forHTTPHeaderField: instead. - */ -- (void)setAuthorizationHeaderFieldWithToken:(NSString *)token DEPRECATED_ATTRIBUTE; - - /** Clears any existing value for the "Authorization" HTTP header. */ @@ -182,7 +200,7 @@ forHTTPHeaderField:(NSString *)field; /** HTTP methods for which serialized requests will encode parameters as a query string. `GET`, `HEAD`, and `DELETE` by default. */ -@property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI; +@property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI; /** Set the method of query string serialization according to one of the pre-defined styles. @@ -204,13 +222,6 @@ forHTTPHeaderField:(NSString *)field; /// @name Creating Request Objects ///------------------------------- -/** - @deprecated This method has been deprecated. Use -requestWithMethod:URLString:parameters:error: instead. - */ -- (NSMutableURLRequest *)requestWithMethod:(NSString *)method - URLString:(NSString *)URLString - parameters:(id)parameters DEPRECATED_ATTRIBUTE; - /** Creates an `NSMutableURLRequest` object with the specified HTTP method and URL string. @@ -226,15 +237,7 @@ forHTTPHeaderField:(NSString *)field; - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id)parameters - error:(NSError * __nullable __autoreleasing *)error; - -/** - @deprecated This method has been deprecated. Use -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error: instead. - */ -- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method - URLString:(NSString *)URLString - parameters:(NSDictionary *)parameters - constructingBodyWithBlock:(void (^)(id formData))block DEPRECATED_ATTRIBUTE; + error:(NSError * _Nullable __autoreleasing *)error; /** Creates an `NSMutableURLRequest` object with the specified HTTP method and URLString, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2 @@ -251,9 +254,9 @@ forHTTPHeaderField:(NSString *)field; */ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString - parameters:(nullable NSDictionary *)parameters + parameters:(nullable NSDictionary *)parameters constructingBodyWithBlock:(nullable void (^)(id formData))block - error:(NSError * __nullable __autoreleasing *)error; + error:(NSError * _Nullable __autoreleasing *)error; /** Creates an `NSMutableURLRequest` by removing the `HTTPBodyStream` from a request, and asynchronously writing its contents into the specified file, invoking the completion handler when finished. @@ -268,7 +271,7 @@ forHTTPHeaderField:(NSString *)field; */ - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL - completionHandler:(nullable void (^)(NSError * __nullable error))handler; + completionHandler:(nullable void (^)(NSError * _Nullable error))handler; @end @@ -292,7 +295,7 @@ forHTTPHeaderField:(NSString *)field; */ - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name - error:(NSError * __nullable __autoreleasing *)error; + error:(NSError * _Nullable __autoreleasing *)error; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. @@ -309,7 +312,7 @@ forHTTPHeaderField:(NSString *)field; name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType - error:(NSError * __nullable __autoreleasing *)error; + error:(NSError * _Nullable __autoreleasing *)error; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary. @@ -356,7 +359,7 @@ forHTTPHeaderField:(NSString *)field; @param headers The HTTP headers to be appended to the form data. @param body The data to be encoded and appended to the form data. This parameter must not be `nil`. */ -- (void)appendPartWithHeaders:(nullable NSDictionary *)headers +- (void)appendPartWithHeaders:(nullable NSDictionary *)headers body:(NSData *)body; /** diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.m old mode 100644 new mode 100755 index 7e224541082..9a2ac98092b --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLRequestSerialization.m @@ -1,5 +1,5 @@ // AFURLRequestSerialization.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,7 +21,7 @@ #import "AFURLRequestSerialization.h" -#if __IPHONE_OS_VERSION_MIN_REQUIRED +#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV #import #else #import @@ -32,35 +32,6 @@ NSString * const AFNetworkingOperationFailingURLRequestErrorKey = @"com.alamofir typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error); -static NSString * AFBase64EncodedStringFromString(NSString *string) { - NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; - NSUInteger length = [data length]; - NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; - - uint8_t *input = (uint8_t *)[data bytes]; - uint8_t *output = (uint8_t *)[mutableData mutableBytes]; - - for (NSUInteger i = 0; i < length; i += 3) { - NSUInteger value = 0; - for (NSUInteger j = i; j < (i + 3); j++) { - value <<= 8; - if (j < length) { - value |= (0xFF & input[j]); - } - } - - static uint8_t const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - NSUInteger idx = (i / 3) * 4; - output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F]; - output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F]; - output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '='; - output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '='; - } - - return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; -} - /** Returns a percent-escaped string following RFC 3986 for a query string key or value. RFC 3986 states that the following characters are "reserved" characters. @@ -73,7 +44,7 @@ static NSString * AFBase64EncodedStringFromString(NSString *string) { - parameter string: The string to be percent-escaped. - returns: The percent-escaped string. */ -static NSString * AFPercentEscapedStringFromString(NSString *string) { +NSString * AFPercentEscapedStringFromString(NSString *string) { static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4 static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; @@ -114,14 +85,14 @@ static NSString * AFPercentEscapedStringFromString(NSString *string) { @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; -- (id)initWithField:(id)field value:(id)value; +- (instancetype)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValue; @end @implementation AFQueryStringPair -- (id)initWithField:(id)field value:(id)value { +- (instancetype)initWithField:(id)field value:(id)value { self = [super init]; if (!self) { return nil; @@ -148,7 +119,7 @@ static NSString * AFPercentEscapedStringFromString(NSString *string) { FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary); FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value); -static NSString * AFQueryStringFromParameters(NSDictionary *parameters) { +NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; @@ -351,12 +322,9 @@ forHTTPHeaderField:(NSString *)field - (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password { - NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password]; - [self setValue:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)] forHTTPHeaderField:@"Authorization"]; -} - -- (void)setAuthorizationHeaderFieldWithToken:(NSString *)token { - [self setValue:[NSString stringWithFormat:@"Token token=\"%@\"", token] forHTTPHeaderField:@"Authorization"]; + NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding]; + NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]; + [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"]; } - (void)clearAuthorizationHeader { @@ -376,13 +344,6 @@ forHTTPHeaderField:(NSString *)field #pragma mark - -- (NSMutableURLRequest *)requestWithMethod:(NSString *)method - URLString:(NSString *)URLString - parameters:(id)parameters -{ - return [self requestWithMethod:method URLString:URLString parameters:parameters error:nil]; -} - - (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters @@ -409,14 +370,6 @@ forHTTPHeaderField:(NSString *)field return mutableRequest; } -- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method - URLString:(NSString *)URLString - parameters:(NSDictionary *)parameters - constructingBodyWithBlock:(void (^)(id formData))block -{ - return [self multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:nil]; -} - - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters @@ -547,7 +500,7 @@ forHTTPHeaderField:(NSString *)field } if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { - if (query) { + if (query && query.length > 0) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { @@ -594,7 +547,7 @@ forHTTPHeaderField:(NSString *)field return YES; } -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [self init]; if (!self) { return nil; @@ -613,7 +566,7 @@ forHTTPHeaderField:(NSString *)field #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone]; serializer.queryStringSerializationStyle = self.queryStringSerializationStyle; @@ -645,7 +598,6 @@ static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { } static inline NSString * AFContentTypeForPathExtension(NSString *extension) { -#ifdef __UTTYPE__ NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); if (!contentType) { @@ -653,10 +605,6 @@ static inline NSString * AFContentTypeForPathExtension(NSString *extension) { } else { return contentType; } -#else -#pragma unused (extension) - return @"application/octet-stream"; -#endif } NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16; @@ -687,7 +635,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @property (readonly, nonatomic, assign) unsigned long long contentLength; @property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty; -- (id)initWithStringEncoding:(NSStringEncoding)encoding; +- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding; - (void)setInitialAndFinalBoundaries; - (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart; @end @@ -703,8 +651,8 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @implementation AFStreamingMultipartFormData -- (id)initWithURLRequest:(NSMutableURLRequest *)urlRequest - stringEncoding:(NSStringEncoding)encoding +- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest + stringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { @@ -896,7 +844,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @synthesize streamError; #pragma clang diagnostic pop -- (id)initWithStringEncoding:(NSStringEncoding)encoding { +- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { return nil; @@ -948,7 +896,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; break; } } else { - NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead; + NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; if (numberOfBytesRead == -1) { self.streamError = self.currentHTTPBodyPart.inputStream.streamError; @@ -1039,7 +987,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; #pragma mark - NSCopying --(id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { @@ -1076,7 +1024,7 @@ typedef enum { @implementation AFHTTPBodyPart -- (id)init { +- (instancetype)init { self = [super init]; if (!self) { return nil; @@ -1254,7 +1202,7 @@ typedef enum { #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init]; bodyPart.stringEncoding = self.stringEncoding; @@ -1317,7 +1265,7 @@ typedef enum { #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -1336,7 +1284,7 @@ typedef enum { #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFJSONRequestSerializer *serializer = [super copyWithZone:zone]; serializer.writingOptions = self.writingOptions; @@ -1396,7 +1344,7 @@ typedef enum { #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -1417,7 +1365,7 @@ typedef enum { #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFPropertyListRequestSerializer *serializer = [super copyWithZone:zone]; serializer.format = self.format; serializer.writeOptions = self.writeOptions; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.h old mode 100644 new mode 100755 index 1578b781711..a9430addbf8 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.h @@ -1,5 +1,5 @@ // AFURLResponseSerialization.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -42,11 +42,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data - error:(NSError * __nullable __autoreleasing *)error -#ifdef NS_SWIFT_NOTHROW -NS_SWIFT_NOTHROW -#endif -; + error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; @end @@ -85,7 +81,7 @@ NS_SWIFT_NOTHROW /** The acceptable MIME types for responses. When non-`nil`, responses with a `Content-Type` with MIME types that do not intersect with the set will result in an error during validation. */ -@property (nonatomic, copy, nullable) NSSet *acceptableContentTypes; +@property (nonatomic, copy, nullable) NSSet *acceptableContentTypes; /** Validates the specified response and data. @@ -100,7 +96,7 @@ NS_SWIFT_NOTHROW */ - (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response data:(nullable NSData *)data - error:(NSError * __nullable __autoreleasing *)error; + error:(NSError * _Nullable __autoreleasing *)error; @end @@ -239,7 +235,7 @@ NS_SWIFT_NOTHROW */ @interface AFImageResponseSerializer : AFHTTPResponseSerializer -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH /** The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance. */ @@ -263,14 +259,14 @@ NS_SWIFT_NOTHROW /** The component response serializers. */ -@property (readonly, nonatomic, copy) NSArray *responseSerializers; +@property (readonly, nonatomic, copy) NSArray > *responseSerializers; /** Creates and returns a compound serializer comprised of the specified response serializers. @warning Each response serializer specified must be a subclass of `AFHTTPResponseSerializer`, and response to `-validateResponse:data:error:`. */ -+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers; ++ (instancetype)compoundSerializerWithResponseSerializers:(NSArray > *)responseSerializers; @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.m old mode 100644 new mode 100755 index cfa8287a26c..5e4679928d8 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLResponseSerialization.m @@ -1,5 +1,5 @@ // AFURLResponseSerialization.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,8 @@ #import "AFURLResponseSerialization.h" +#import + #if TARGET_OS_IOS #import #elif TARGET_OS_WATCH @@ -113,7 +115,9 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO NSError *validationError = nil; if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { - if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) { + if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] && + !([response MIMEType] == nil && [data length] == 0)) { + if ([data length] > 0 && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]], @@ -171,7 +175,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO return YES; } -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [self init]; if (!self) { return nil; @@ -190,7 +194,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone]; serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone]; @@ -238,40 +242,15 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO } } - // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. - // See https://github.com/rails/rails/issues/1742 - NSStringEncoding stringEncoding = self.stringEncoding; - if (response.textEncodingName) { - CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName); - if (encoding != kCFStringEncodingInvalidId) { - stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding); - } - } - id responseObject = nil; NSError *serializationError = nil; - @autoreleasepool { - NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding]; - if (responseString && ![responseString isEqualToString:@" "]) { - // Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character - // See http://stackoverflow.com/a/12843465/157142 - data = [responseString dataUsingEncoding:NSUTF8StringEncoding]; - - if (data) { - if ([data length] > 0) { - responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; - } else { - return nil; - } - } else { - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Data failed decoding as a UTF-8 string", @"AFNetworking", nil), - NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Could not decode string: %@", @"AFNetworking", nil), responseString] - }; - - serializationError = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; - } - } + // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization. + // See https://github.com/rails/rails/issues/1742 + BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]]; + if (data.length > 0 && !isSpace) { + responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; + } else { + return nil; } if (self.removesKeysWithNullValues && responseObject) { @@ -287,7 +266,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -308,7 +287,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFJSONResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.readingOptions = self.readingOptions; serializer.removesKeysWithNullValues = self.removesKeysWithNullValues; @@ -408,7 +387,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -427,7 +406,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFXMLDocumentResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.options = self.options; @@ -495,7 +474,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -516,7 +495,7 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFPropertyListResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.format = self.format; serializer.readOptions = self.readOptions; @@ -528,8 +507,9 @@ static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingO #pragma mark - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH #import +#import @interface UIImage (AFNetworkingSafeImageLoading) + (UIImage *)af_safeImageWithData:(NSData *)data; @@ -667,10 +647,10 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV self.imageScale = [[UIScreen mainScreen] scale]; self.automaticallyInflatesResponseImage = YES; -#elif TARGET_OS_WATCH +#elif TARGET_OS_WATCH self.imageScale = [[WKInterfaceDevice currentDevice] screenScale]; self.automaticallyInflatesResponseImage = YES; #endif @@ -690,13 +670,13 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r } } -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH if (self.automaticallyInflatesResponseImage) { return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale); } else { return AFImageWithDataAtScale(data, self.imageScale); } -#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#else // Ensure that the image is set to it's correct pixel width and height NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data]; NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])]; @@ -710,13 +690,13 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; } -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH NSNumber *imageScale = [decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(imageScale))]; #if CGFLOAT_IS_DOUBLE self.imageScale = [imageScale doubleValue]; @@ -733,7 +713,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH [coder encodeObject:@(self.imageScale) forKey:NSStringFromSelector(@selector(imageScale))]; [coder encodeBool:self.automaticallyInflatesResponseImage forKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))]; #endif @@ -741,10 +721,10 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFImageResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH serializer.imageScale = self.imageScale; serializer.automaticallyInflatesResponseImage = self.automaticallyInflatesResponseImage; #endif @@ -796,7 +776,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r #pragma mark - NSSecureCoding -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { self = [super initWithCoder:decoder]; if (!self) { return nil; @@ -815,7 +795,7 @@ static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *r #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { AFCompoundResponseSerializer *serializer = [[[self class] allocWithZone:zone] init]; serializer.responseSerializers = self.responseSerializers; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.h old mode 100644 new mode 100755 index 4498c892943..89909fe49b0 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.h @@ -1,5 +1,5 @@ // AFURLSessionManager.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -19,6 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + #import #import "AFURLResponseSerialization.h" @@ -28,14 +29,6 @@ #import "AFNetworkReachabilityManager.h" #endif -#ifndef NS_DESIGNATED_INITIALIZER -#if __has_attribute(objc_designated_initializer) -#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) -#else -#define NS_DESIGNATED_INITIALIZER -#endif -#endif - /** `AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to ``, ``, ``, and ``. @@ -58,6 +51,7 @@ - `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:` - `URLSession:task:didReceiveChallenge:completionHandler:` - `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:` + - `URLSession:task:needNewBodyStream:` - `URLSession:task:didCompleteWithError:` ### `NSURLSessionDataDelegate` @@ -93,8 +87,6 @@ NS_ASSUME_NONNULL_BEGIN -#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_OS_WATCH - @interface AFURLSessionManager : NSObject /** @@ -119,7 +111,7 @@ NS_ASSUME_NONNULL_BEGIN ///------------------------------- /** - The security policy used by created request operations to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. + The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. */ @property (nonatomic, strong) AFSecurityPolicy *securityPolicy; @@ -141,22 +133,22 @@ NS_ASSUME_NONNULL_BEGIN /** The data, upload, and download tasks currently run by the managed session. */ -@property (readonly, nonatomic, strong) NSArray *tasks; +@property (readonly, nonatomic, strong) NSArray *tasks; /** The data tasks currently run by the managed session. */ -@property (readonly, nonatomic, strong) NSArray *dataTasks; +@property (readonly, nonatomic, strong) NSArray *dataTasks; /** The upload tasks currently run by the managed session. */ -@property (readonly, nonatomic, strong) NSArray *uploadTasks; +@property (readonly, nonatomic, strong) NSArray *uploadTasks; /** The download tasks currently run by the managed session. */ -@property (readonly, nonatomic, strong) NSArray *downloadTasks; +@property (readonly, nonatomic, strong) NSArray *downloadTasks; ///------------------------------- /// @name Managing Callback Queues @@ -165,20 +157,12 @@ NS_ASSUME_NONNULL_BEGIN /** The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used. */ -#if OS_OBJECT_USE_OBJC @property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; -#else -@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue; -#endif /** The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used. */ -#if OS_OBJECT_USE_OBJC @property (nonatomic, strong, nullable) dispatch_group_t completionGroup; -#else -@property (nonatomic, assign, nullable) dispatch_group_t completionGroup; -#endif ///--------------------------------- /// @name Working Around System Bugs @@ -224,7 +208,20 @@ NS_ASSUME_NONNULL_BEGIN @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request - completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler; + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; + +/** + Creates an `NSURLSessionDataTask` with the specified request. + + @param request The HTTP request for the request. + @param uploadProgressBlock A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. + @param downloadProgressBlock A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. + @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. + */ +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; ///--------------------------- /// @name Running Upload Tasks @@ -235,39 +232,39 @@ NS_ASSUME_NONNULL_BEGIN @param request The HTTP request for the request. @param fileURL A URL to the local file to be uploaded. - @param progress A progress object monitoring the current upload progress. + @param uploadProgressBlock A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. @see `attemptsToRecreateUploadTasksForBackgroundSessions` */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL - progress:(NSProgress * __nullable __autoreleasing * __nullable)progress - completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler; + progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; /** Creates an `NSURLSessionUploadTask` with the specified request for an HTTP body. @param request The HTTP request for the request. @param bodyData A data object containing the HTTP body to be uploaded. - @param progress A progress object monitoring the current upload progress. + @param uploadProgressBlock A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData - progress:(NSProgress * __nullable __autoreleasing * __nullable)progress - completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler; + progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; /** Creates an `NSURLSessionUploadTask` with the specified streaming request. @param request The HTTP request for the request. - @param progress A progress object monitoring the current upload progress. + @param uploadProgressBlock A block object to be executed when the upload progress is updated. Note this block is called on the session queue, not the main queue. @param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any. */ - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request - progress:(NSProgress * __nullable __autoreleasing * __nullable)progress - completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler; + progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; ///----------------------------- /// @name Running Download Tasks @@ -277,29 +274,29 @@ NS_ASSUME_NONNULL_BEGIN Creates an `NSURLSessionDownloadTask` with the specified request. @param request The HTTP request for the request. - @param progress A progress object monitoring the current download progress. + @param downloadProgressBlock A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. @warning If using a background `NSURLSessionConfiguration` on iOS, these blocks will be lost when the app is terminated. Background sessions may prefer to use `-setDownloadTaskDidFinishDownloadingBlock:` to specify the URL for saving the downloaded file, rather than the destination block of this method. */ - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request - progress:(NSProgress * __nullable __autoreleasing * __nullable)progress + progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination - completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * __nullable filePath, NSError * __nullable error))completionHandler; + completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; /** Creates an `NSURLSessionDownloadTask` with the specified resume data. @param resumeData The data used to resume downloading. - @param progress A progress object monitoring the current download progress. + @param downloadProgressBlock A block object to be executed when the download progress is updated. Note this block is called on the session queue, not the main queue. @param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL. @param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any. */ - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData - progress:(NSProgress * __nullable __autoreleasing * __nullable)progress + progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination - completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * __nullable filePath, NSError * __nullable error))completionHandler; + completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; ///--------------------------------- /// @name Getting Progress for Tasks @@ -308,20 +305,20 @@ NS_ASSUME_NONNULL_BEGIN /** Returns the upload progress of the specified task. - @param uploadTask The session upload task. Must not be `nil`. + @param task The session task. Must not be `nil`. @return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable. */ -- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask; +- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task; /** Returns the download progress of the specified task. - @param downloadTask The session download task. Must not be `nil`. + @param task The session task. Must not be `nil`. @return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable. */ -- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask; +- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task; ///----------------------------------------- /// @name Setting Session Delegate Callbacks @@ -339,7 +336,7 @@ NS_ASSUME_NONNULL_BEGIN @param block A block object to be executed when a connection level authentication challenge has occurred. The block returns the disposition of the authentication challenge, and takes three arguments: the session, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. */ -- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block; +- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block; ///-------------------------------------- /// @name Setting Task Delegate Callbacks @@ -364,7 +361,7 @@ NS_ASSUME_NONNULL_BEGIN @param block A block object to be executed when a session task has received a request specific authentication challenge. The block returns the disposition of the authentication challenge, and takes four arguments: the session, the task, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge. */ -- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block; +- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block; /** Sets a block to be executed periodically to track upload progress, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`. @@ -378,7 +375,7 @@ NS_ASSUME_NONNULL_BEGIN @param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task. */ -- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * __nullable error))block; +- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block; ///------------------------------------------- /// @name Setting Data Task Delegate Callbacks @@ -428,7 +425,7 @@ NS_ASSUME_NONNULL_BEGIN @param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error. */ -- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * __nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block; +- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block; /** Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`. @@ -446,31 +443,15 @@ NS_ASSUME_NONNULL_BEGIN @end -#endif - ///-------------------- /// @name Notifications ///-------------------- -/** - Posted when a task begins executing. - - @deprecated Use `AFNetworkingTaskDidResumeNotification` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidStartNotification DEPRECATED_ATTRIBUTE; - /** Posted when a task resumes. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification; -/** - Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task. - - @deprecated Use `AFNetworkingTaskDidCompleteNotification` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishNotification DEPRECATED_ATTRIBUTE; - /** Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task. */ @@ -492,62 +473,27 @@ FOUNDATION_EXPORT NSString * const AFURLSessionDidInvalidateNotification; FOUNDATION_EXPORT NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification; /** - The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task. - - @deprecated Use `AFNetworkingTaskDidCompleteResponseDataKey` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishResponseDataKey DEPRECATED_ATTRIBUTE; - -/** - The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task. + The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if response data exists for the task. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseDataKey; /** - The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized. - - @deprecated Use `AFNetworkingTaskDidCompleteSerializedResponseKey` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishSerializedResponseKey DEPRECATED_ATTRIBUTE; - -/** - The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized. + The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if the response was serialized. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey; /** - The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer. - - @deprecated Use `AFNetworkingTaskDidCompleteResponseSerializerKey` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishResponseSerializerKey DEPRECATED_ATTRIBUTE; - -/** - The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer. + The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if the task has an associated response serializer. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey; /** - The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk. - - @deprecated Use `AFNetworkingTaskDidCompleteAssetPathKey` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishAssetPathKey DEPRECATED_ATTRIBUTE; - -/** - The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk. + The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if an the response data has been stored directly to disk. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteAssetPathKey; /** - Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists. - - @deprecated Use `AFNetworkingTaskDidCompleteErrorKey` instead. - */ -FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishErrorKey DEPRECATED_ATTRIBUTE; - -/** - Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists. + Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidCompleteNotification` if an error exists. */ FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteErrorKey; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.m old mode 100644 new mode 100755 index 8ec0f0843b2..ef2108cd778 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/AFNetworking/AFURLSessionManager.m @@ -1,5 +1,5 @@ // AFURLSessionManager.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,7 +22,11 @@ #import "AFURLSessionManager.h" #import -#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) +#ifndef NSFoundationVersionNumber_iOS_8_0 +#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11 +#else +#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0 +#endif static dispatch_queue_t url_session_manager_creation_queue() { static dispatch_queue_t af_url_session_manager_creation_queue; @@ -34,6 +38,17 @@ static dispatch_queue_t url_session_manager_creation_queue() { return af_url_session_manager_creation_queue; } +static void url_session_manager_create_task_safely(dispatch_block_t block) { + if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { + // Fix of bug + // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) + // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 + dispatch_sync(url_session_manager_creation_queue(), block); + } else { + block(); + } +} + static dispatch_queue_t url_session_manager_processing_queue() { static dispatch_queue_t af_url_session_manager_processing_queue; static dispatch_once_t onceToken; @@ -60,21 +75,12 @@ NSString * const AFNetworkingTaskDidSuspendNotification = @"com.alamofire.networ NSString * const AFURLSessionDidInvalidateNotification = @"com.alamofire.networking.session.invalidate"; NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification = @"com.alamofire.networking.session.download.file-manager-error"; -NSString * const AFNetworkingTaskDidStartNotification = @"com.alamofire.networking.task.resume"; // Deprecated -NSString * const AFNetworkingTaskDidFinishNotification = @"com.alamofire.networking.task.complete"; // Deprecated - NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse"; NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer"; NSString * const AFNetworkingTaskDidCompleteResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; NSString * const AFNetworkingTaskDidCompleteErrorKey = @"com.alamofire.networking.task.complete.error"; NSString * const AFNetworkingTaskDidCompleteAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; -NSString * const AFNetworkingTaskDidFinishSerializedResponseKey = @"com.alamofire.networking.task.complete.serializedresponse"; // Deprecated -NSString * const AFNetworkingTaskDidFinishResponseSerializerKey = @"com.alamofire.networking.task.complete.responseserializer"; // Deprecated -NSString * const AFNetworkingTaskDidFinishResponseDataKey = @"com.alamofire.networking.complete.finish.responsedata"; // Deprecated -NSString * const AFNetworkingTaskDidFinishErrorKey = @"com.alamofire.networking.task.complete.error"; // Deprecated -NSString * const AFNetworkingTaskDidFinishAssetPathKey = @"com.alamofire.networking.task.complete.assetpath"; // Deprecated - static NSString * const AFURLSessionManagerLockName = @"com.alamofire.networking.session.manager.lock"; static NSUInteger const AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask = 3; @@ -100,17 +106,22 @@ typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSUR typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location); typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite); typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes); +typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *); typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error); + #pragma mark - @interface AFURLSessionManagerTaskDelegate : NSObject @property (nonatomic, weak) AFURLSessionManager *manager; @property (nonatomic, strong) NSMutableData *mutableData; -@property (nonatomic, strong) NSProgress *progress; +@property (nonatomic, strong) NSProgress *uploadProgress; +@property (nonatomic, strong) NSProgress *downloadProgress; @property (nonatomic, copy) NSURL *downloadFileURL; @property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading; +@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock; +@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock; @property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler; @end @@ -123,24 +134,119 @@ typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id re } self.mutableData = [NSMutableData data]; + self.uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; + self.uploadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown; - self.progress = [NSProgress progressWithTotalUnitCount:0]; - + self.downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; + self.downloadProgress.totalUnitCount = NSURLSessionTransferSizeUnknown; return self; } -#pragma mark - NSURLSessionTaskDelegate +#pragma mark - NSProgress Tracking -- (void)URLSession:(__unused NSURLSession *)session - task:(__unused NSURLSessionTask *)task - didSendBodyData:(__unused int64_t)bytesSent - totalBytesSent:(int64_t)totalBytesSent -totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend -{ - self.progress.totalUnitCount = totalBytesExpectedToSend; - self.progress.completedUnitCount = totalBytesSent; +- (void)setupProgressForTask:(NSURLSessionTask *)task { + __weak __typeof__(task) weakTask = task; + + self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend; + self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive; + [self.uploadProgress setCancellable:YES]; + [self.uploadProgress setCancellationHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask cancel]; + }]; + [self.uploadProgress setPausable:YES]; + [self.uploadProgress setPausingHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask suspend]; + }]; + if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) { + [self.uploadProgress setResumingHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask resume]; + }]; + } + + [self.downloadProgress setCancellable:YES]; + [self.downloadProgress setCancellationHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask cancel]; + }]; + [self.downloadProgress setPausable:YES]; + [self.downloadProgress setPausingHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask suspend]; + }]; + + if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) { + [self.downloadProgress setResumingHandler:^{ + __typeof__(weakTask) strongTask = weakTask; + [strongTask resume]; + }]; + } + + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive)) + options:NSKeyValueObservingOptionNew + context:NULL]; + + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesSent)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [task addObserver:self + forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend)) + options:NSKeyValueObservingOptionNew + context:NULL]; + + [self.downloadProgress addObserver:self + forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) + options:NSKeyValueObservingOptionNew + context:NULL]; + [self.uploadProgress addObserver:self + forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) + options:NSKeyValueObservingOptionNew + context:NULL]; } +- (void)cleanUpProgressForTask:(NSURLSessionTask *)task { + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))]; + [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]; + [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; + [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) { + if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) { + self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) { + self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { + self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; + } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) { + self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue]; + } + } + else if ([object isEqual:self.downloadProgress]) { + if (self.downloadProgressBlock) { + self.downloadProgressBlock(object); + } + } + else if ([object isEqual:self.uploadProgress]) { + if (self.uploadProgressBlock) { + self.uploadProgressBlock(object); + } + } +} + +#pragma mark - NSURLSessionTaskDelegate + - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error @@ -241,24 +347,6 @@ didFinishDownloadingToURL:(NSURL *)location } } -- (void)URLSession:(__unused NSURLSession *)session - downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask - didWriteData:(__unused int64_t)bytesWritten - totalBytesWritten:(int64_t)totalBytesWritten -totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite -{ - self.progress.totalUnitCount = totalBytesExpectedToWrite; - self.progress.completedUnitCount = totalBytesWritten; -} - -- (void)URLSession:(__unused NSURLSession *)session - downloadTask:(__unused NSURLSessionDownloadTask *)downloadTask - didResumeAtOffset:(int64_t)fileOffset -expectedTotalBytes:(int64_t)expectedTotalBytes { - self.progress.totalUnitCount = expectedTotalBytes; - self.progress.completedUnitCount = fileOffset; -} - @end #pragma mark - @@ -452,7 +540,7 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { for (NSURLSessionDataTask *task in dataTasks) { - [self addDelegateForDataTask:task completionHandler:nil]; + [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { @@ -464,9 +552,6 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi } }]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:nil]; - return self; } @@ -523,10 +608,14 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi [self.lock lock]; self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; + [delegate setupProgressForTask:task]; + [self addNotificationObserverForTask:task]; [self.lock unlock]; } - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; @@ -535,48 +624,28 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; + + delegate.uploadProgressBlock = uploadProgressBlock; + delegate.downloadProgressBlock = downloadProgressBlock; } - (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; - int64_t totalUnitCount = uploadTask.countOfBytesExpectedToSend; - if(totalUnitCount == NSURLSessionTransferSizeUnknown) { - NSString *contentLength = [uploadTask.originalRequest valueForHTTPHeaderField:@"Content-Length"]; - if(contentLength) { - totalUnitCount = (int64_t)[contentLength longLongValue]; - } - } - - if (delegate.progress) { - delegate.progress.totalUnitCount = totalUnitCount; - } else { - delegate.progress = [NSProgress progressWithTotalUnitCount:totalUnitCount]; - } - - delegate.progress.pausingHandler = ^{ - [uploadTask suspend]; - }; - delegate.progress.cancellationHandler = ^{ - [uploadTask cancel]; - }; - - if (progress) { - *progress = delegate.progress; - } - uploadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:uploadTask]; + + delegate.uploadProgressBlock = uploadProgressBlock; } - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { @@ -590,29 +659,24 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi }; } - if (progress) { - *progress = delegate.progress; - } - downloadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:downloadTask]; + + delegate.downloadProgressBlock = downloadProgressBlock; } - (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); + AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [self.lock lock]; + [delegate cleanUpProgressForTask:task]; + [self removeNotificationObserverForTask:task]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; } -- (void)removeAllDelegates { - [self.lock lock]; - [self.mutableTaskDelegatesKeyedByTaskIdentifier removeAllObjects]; - [self.lock unlock]; -} - #pragma mark - - (NSArray *)tasksForKeyPath:(NSString *)keyPath { @@ -673,17 +737,36 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi _responseSerializer = responseSerializer; } +#pragma mark - +- (void)addNotificationObserverForTask:(NSURLSessionTask *)task { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task]; +} + +- (void)removeNotificationObserverForTask:(NSURLSessionTask *)task { + [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task]; +} + #pragma mark - - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { + return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler]; +} + +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock + downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock + completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler { + __block NSURLSessionDataTask *dataTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); - [self addDelegateForDataTask:dataTask completionHandler:completionHandler]; + [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; } @@ -692,11 +775,11 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; }); @@ -706,36 +789,36 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi } } - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(NSData *)bodyData - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData]; }); - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *uploadTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithStreamedRequest:request]; }); - [self addDelegateForUploadTask:uploadTask progress:progress completionHandler:completionHandler]; + [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; } @@ -743,43 +826,42 @@ static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofi #pragma mark - - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { __block NSURLSessionDownloadTask *downloadTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithRequest:request]; }); - [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; + [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask; } - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData - progress:(NSProgress * __autoreleasing *)progress + progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { __block NSURLSessionDownloadTask *downloadTask = nil; - dispatch_sync(url_session_manager_creation_queue(), ^{ + url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithResumeData:resumeData]; }); - [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler]; + [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask; } #pragma mark - - -- (NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask { - return [[self delegateForTask:uploadTask] progress]; +- (NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] uploadProgress]; } -- (NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask { - return [[self delegateForTask:downloadTask] progress]; +- (NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task { + return [[self delegateForTask:task] downloadProgress]; } #pragma mark - @@ -879,7 +961,6 @@ didBecomeInvalidWithError:(NSError *)error self.sessionDidBecomeInvalid(session, error); } - [self removeAllDelegates]; [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session]; } @@ -993,9 +1074,6 @@ totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend } } - AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; - [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalUnitCount]; - if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); } @@ -1017,7 +1095,6 @@ didCompleteWithError:(NSError *)error if (self.taskDidComplete) { self.taskDidComplete(session, task, error); } - } #pragma mark - NSURLSessionDataDelegate @@ -1057,6 +1134,7 @@ didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { + AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; [delegate URLSession:session dataTask:dataTask didReceiveData:data]; @@ -1121,9 +1199,6 @@ didFinishDownloadingToURL:(NSURL *)location totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { - AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; - [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; - if (self.downloadTaskDidWriteData) { self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } @@ -1134,9 +1209,6 @@ totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { - AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; - [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes]; - if (self.downloadTaskDidResume) { self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes); } @@ -1148,7 +1220,7 @@ expectedTotalBytes:(int64_t)expectedTotalBytes return YES; } -- (id)initWithCoder:(NSCoder *)decoder { +- (instancetype)initWithCoder:(NSCoder *)decoder { NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; self = [self initWithSessionConfiguration:configuration]; @@ -1165,10 +1237,8 @@ expectedTotalBytes:(int64_t)expectedTotalBytes #pragma mark - NSCopying -- (id)copyWithZone:(NSZone *)zone { +- (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration]; } @end - -#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.h new file mode 100755 index 00000000000..9bdc15cc736 --- /dev/null +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.h @@ -0,0 +1,149 @@ +// AFAutoPurgingImageCache.h +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) +// +// 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. + +#import +#import + +#if TARGET_OS_IOS || TARGET_OS_TV +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The `AFImageCache` protocol defines a set of APIs for adding, removing and fetching images from a cache synchronously. + */ +@protocol AFImageCache + +/** + Adds the image to the cache with the given identifier. + + @param image The image to cache. + @param identifier The unique identifier for the image in the cache. + */ +- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier; + +/** + Removes the image from the cache matching the given identifier. + + @param identifier The unique identifier for the image in the cache. + + @return A BOOL indicating whether or not the image was removed from the cache. + */ +- (BOOL)removeImageWithIdentifier:(NSString *)identifier; + +/** + Removes all images from the cache. + + @return A BOOL indicating whether or not all images were removed from the cache. + */ +- (BOOL)removeAllImages; + +/** + Returns the image in the cache associated with the given identifier. + + @param identifier The unique identifier for the image in the cache. + + @return An image for the matching identifier, or nil. + */ +- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier; +@end + + +/** + The `ImageRequestCache` protocol extends the `ImageCache` protocol by adding methods for adding, removing and fetching images from a cache given an `NSURLRequest` and additional identifier. + */ +@protocol AFImageRequestCache + +/** + Adds the image to the cache using an identifier created from the request and additional identifier. + + @param image The image to cache. + @param request The unique URL request identifing the image asset. + @param identifier The additional identifier to apply to the URL request to identify the image. + */ +- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; + +/** + Removes the image from the cache using an identifier created from the request and additional identifier. + + @param request The unique URL request identifing the image asset. + @param identifier The additional identifier to apply to the URL request to identify the image. + + @return A BOOL indicating whether or not all images were removed from the cache. + */ +- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; + +/** + Returns the image from the cache associated with an identifier created from the request and additional identifier. + + @param request The unique URL request identifing the image asset. + @param identifier The additional identifier to apply to the URL request to identify the image. + + @return An image for the matching request and identifier, or nil. + */ +- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; + +@end + +/** + The `AutoPurgingImageCache` in an in-memory image cache used to store images up to a given memory capacity. When the memory capacity is reached, the image cache is sorted by last access date, then the oldest image is continuously purged until the preferred memory usage after purge is met. Each time an image is accessed through the cache, the internal access date of the image is updated. + */ +@interface AFAutoPurgingImageCache : NSObject + +/** + The total memory capacity of the cache in bytes. + */ +@property (nonatomic, assign) UInt64 memoryCapacity; + +/** + The preferred memory usage after purge in bytes. During a purge, images will be purged until the memory capacity drops below this limit. + */ +@property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge; + +/** + The current total memory usage in bytes of all images stored within the cache. + */ +@property (nonatomic, assign, readonly) UInt64 memoryUsage; + +/** + Initialies the `AutoPurgingImageCache` instance with default values for memory capacity and preferred memory usage after purge limit. `memoryCapcity` defaults to `100 MB`. `preferredMemoryUsageAfterPurge` defaults to `60 MB`. + + @return The new `AutoPurgingImageCache` instance. + */ +- (instancetype)init; + +/** + Initialies the `AutoPurgingImageCache` instance with the given memory capacity and preferred memory usage + after purge limit. + + @param memoryCapacity The total memory capacity of the cache in bytes. + @param preferredMemoryCapacity The preferred memory usage after purge in bytes. + + @return The new `AutoPurgingImageCache` instance. + */ +- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity; + +@end + +NS_ASSUME_NONNULL_END + +#endif + diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.m new file mode 100755 index 00000000000..1f40715c376 --- /dev/null +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFAutoPurgingImageCache.m @@ -0,0 +1,201 @@ +// AFAutoPurgingImageCache.m +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) +// +// 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. + +#import + +#if TARGET_OS_IOS || TARGET_OS_TV + +#import "AFAutoPurgingImageCache.h" + +@interface AFCachedImage : NSObject + +@property (nonatomic, strong) UIImage *image; +@property (nonatomic, strong) NSString *identifier; +@property (nonatomic, assign) UInt64 totalBytes; +@property (nonatomic, strong) NSDate *lastAccessDate; +@property (nonatomic, assign) UInt64 currentMemoryUsage; + +@end + +@implementation AFCachedImage + +-(instancetype)initWithImage:(UIImage *)image identifier:(NSString *)identifier { + if (self = [self init]) { + self.image = image; + self.identifier = identifier; + + CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale); + CGFloat bytesPerPixel = 4.0; + CGFloat bytesPerSize = imageSize.width * imageSize.height; + self.totalBytes = (UInt64)bytesPerPixel * (UInt64)bytesPerSize; + self.lastAccessDate = [NSDate date]; + } + return self; +} + +- (UIImage*)accessImage { + self.lastAccessDate = [NSDate date]; + return self.image; +} + +- (NSString *)description { + NSString *descriptionString = [NSString stringWithFormat:@"Idenfitier: %@ lastAccessDate: %@ ", self.identifier, self.lastAccessDate]; + return descriptionString; + +} + +@end + +@interface AFAutoPurgingImageCache () +@property (nonatomic, strong) NSMutableDictionary *cachedImages; +@property (nonatomic, assign) UInt64 currentMemoryUsage; +@property (nonatomic, strong) dispatch_queue_t synchronizationQueue; +@end + +@implementation AFAutoPurgingImageCache + +- (instancetype)init { + return [self initWithMemoryCapacity:100 * 1024 * 1024 preferredMemoryCapacity:60 * 1024 * 1024]; +} + +- (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity { + if (self = [super init]) { + self.memoryCapacity = memoryCapacity; + self.preferredMemoryUsageAfterPurge = preferredMemoryCapacity; + self.cachedImages = [[NSMutableDictionary alloc] init]; + + NSString *queueName = [NSString stringWithFormat:@"com.alamofire.autopurgingimagecache-%@", [[NSUUID UUID] UUIDString]]; + self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT); + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(removeAllImages) + name:UIApplicationDidReceiveMemoryWarningNotification + object:nil]; + + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (UInt64)memoryUsage { + __block UInt64 result = 0; + dispatch_sync(self.synchronizationQueue, ^{ + result = self.currentMemoryUsage; + }); + return result; +} + +- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier { + dispatch_barrier_async(self.synchronizationQueue, ^{ + AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier]; + + AFCachedImage *previousCachedImage = self.cachedImages[identifier]; + if (previousCachedImage != nil) { + self.currentMemoryUsage -= previousCachedImage.totalBytes; + } + + self.cachedImages[identifier] = cacheImage; + self.currentMemoryUsage += cacheImage.totalBytes; + }); + + dispatch_barrier_async(self.synchronizationQueue, ^{ + if (self.currentMemoryUsage > self.memoryCapacity) { + UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge; + NSMutableArray *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues]; + NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate" + ascending:YES]; + [sortedImages sortUsingDescriptors:@[sortDescriptor]]; + + UInt64 bytesPurged = 0; + + for (AFCachedImage *cachedImage in sortedImages) { + [self.cachedImages removeObjectForKey:cachedImage.identifier]; + bytesPurged += cachedImage.totalBytes; + if (bytesPurged >= bytesToPurge) { + break ; + } + } + self.currentMemoryUsage -= bytesPurged; + } + }); +} + +- (BOOL)removeImageWithIdentifier:(NSString *)identifier { + __block BOOL removed = NO; + dispatch_barrier_sync(self.synchronizationQueue, ^{ + AFCachedImage *cachedImage = self.cachedImages[identifier]; + if (cachedImage != nil) { + [self.cachedImages removeObjectForKey:identifier]; + self.currentMemoryUsage -= cachedImage.totalBytes; + removed = YES; + } + }); + return removed; +} + +- (BOOL)removeAllImages { + __block BOOL removed = NO; + dispatch_barrier_sync(self.synchronizationQueue, ^{ + if (self.cachedImages.count > 0) { + [self.cachedImages removeAllObjects]; + self.currentMemoryUsage = 0; + removed = YES; + } + }); + return removed; +} + +- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier { + __block UIImage *image = nil; + dispatch_sync(self.synchronizationQueue, ^{ + AFCachedImage *cachedImage = self.cachedImages[identifier]; + image = [cachedImage accessImage]; + }); + return image; +} + +- (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { + [self addImage:image withIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; +} + +- (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { + return [self removeImageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; +} + +- (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)identifier { + return [self imageWithIdentifier:[self imageCacheKeyFromURLRequest:request withAdditionalIdentifier:identifier]]; +} + +- (NSString *)imageCacheKeyFromURLRequest:(NSURLRequest *)request withAdditionalIdentifier:(NSString *)additionalIdentifier { + NSString *key = request.URL.absoluteString; + if (additionalIdentifier != nil) { + key = [key stringByAppendingString:additionalIdentifier]; + } + return key; +} + +@end + +#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.h new file mode 100755 index 00000000000..3903eec2396 --- /dev/null +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.h @@ -0,0 +1,157 @@ +// AFImageDownloader.h +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) +// +// 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. + +#import + +#if TARGET_OS_IOS || TARGET_OS_TV + +#import +#import "AFAutoPurgingImageCache.h" +#import "AFHTTPSessionManager.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) { + AFImageDownloadPrioritizationFIFO, + AFImageDownloadPrioritizationLIFO +}; + +/** + The `AFImageDownloadReceipt` is an object vended by the `AFImageDownloader` when starting a data task. It can be used to cancel active tasks running on the `AFImageDownloader` session. As a general rule, image data tasks should be cancelled using the `AFImageDownloadReceipt` instead of calling `cancel` directly on the `task` itself. The `AFImageDownloader` is optimized to handle duplicate task scenarios as well as pending versus active downloads. + */ +@interface AFImageDownloadReceipt : NSObject + +/** + The data task created by the `AFImageDownloader`. +*/ +@property (nonatomic, strong) NSURLSessionDataTask *task; + +/** + The unique identifier for the success and failure blocks when duplicate requests are made. + */ +@property (nonatomic, strong) NSUUID *receiptID; +@end + +/** The `AFImageDownloader` class is responsible for downloading images in parallel on a prioritized queue. Incoming downloads are added to the front or back of the queue depending on the download prioritization. Each downloaded image is cached in the underlying `NSURLCache` as well as the in-memory image cache. By default, any download request with a cached image equivalent in the image cache will automatically be served the cached image representation. + */ +@interface AFImageDownloader : NSObject + +/** + The image cache used to store all downloaded images in. `AFAutoPurgingImageCache` by default. + */ +@property (nonatomic, strong, nullable) id imageCache; + +/** + The `AFHTTPSessionManager` used to download images. By default, this is configured with an `AFImageResponseSerializer`, and a shared `NSURLCache` for all image downloads. + */ +@property (nonatomic, strong) AFHTTPSessionManager *sessionManager; + +/** + Defines the order prioritization of incoming download requests being inserted into the queue. `AFImageDownloadPrioritizationFIFO` by default. + */ +@property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton; + +/** + The shared default instance of `AFImageDownloader` initialized with default values. + */ ++ (instancetype)defaultInstance; + +/** + Creates a default `NSURLCache` with common usage parameter values. + + @returns The default `NSURLCache` instance. + */ ++ (NSURLCache *)defaultURLCache; + +/** + Default initializer + + @return An instance of `AFImageDownloader` initialized with default values. + */ +- (instancetype)init; + +/** + Initializes the `AFImageDownloader` instance with the given session manager, download prioritization, maximum active download count and image cache. + + @param sessionManager The session manager to use to download images. + @param downloadPrioritization The download prioritization of the download queue. + @param maximumActiveDownloads The maximum number of active downloads allowed at any given time. Recommend `4`. + @param imageCache The image cache used to store all downloaded images in. + + @return The new `AFImageDownloader` instance. + */ +- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager + downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization + maximumActiveDownloads:(NSInteger)maximumActiveDownloads + imageCache:(nullable id )imageCache; + +/** + Creates a data task using the `sessionManager` instance for the specified URL request. + + If the same data task is already in the queue or currently being downloaded, the success and failure blocks are + appended to the already existing task. Once the task completes, all success or failure blocks attached to the + task are executed in the order they were added. + + @param request The URL request. + @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. + @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. + + @return The image download receipt for the data task if available. `nil` if the image is stored in the cache. + cache and the URL request cache policy allows the cache to be used. + */ +- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; + +/** + Creates a data task using the `sessionManager` instance for the specified URL request. + + If the same data task is already in the queue or currently being downloaded, the success and failure blocks are + appended to the already existing task. Once the task completes, all success or failure blocks attached to the + task are executed in the order they were added. + + @param request The URL request. + @param receiptID The identifier to use for the download receipt that will be created for this request. This must be a unique identifier that does not represent any other request. + @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. + @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. + + @return The image download receipt for the data task if available. `nil` if the image is stored in the cache. + cache and the URL request cache policy allows the cache to be used. + */ +- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request + withReceiptID:(NSUUID *)receiptID + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; + +/** + Cancels the data task in the receipt by removing the corresponding success and failure blocks and cancelling the data task if necessary. + + If the data task is pending in the queue, it will be cancelled if no other success and failure blocks are registered with the data task. If the data task is currently executing or is already completed, the success and failure blocks are removed and will not be called when the task finishes. + + @param imageDownloadReceipt The image download receipt to cancel. + */ +- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt; + +@end + +#endif + +NS_ASSUME_NONNULL_END diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.m new file mode 100755 index 00000000000..78477bf6c65 --- /dev/null +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFImageDownloader.m @@ -0,0 +1,391 @@ +// AFImageDownloader.m +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) +// +// 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. + +#import + +#if TARGET_OS_IOS || TARGET_OS_TV + +#import "AFImageDownloader.h" +#import "AFHTTPSessionManager.h" + +@interface AFImageDownloaderResponseHandler : NSObject +@property (nonatomic, strong) NSUUID *uuid; +@property (nonatomic, copy) void (^successBlock)(NSURLRequest*, NSHTTPURLResponse*, UIImage*); +@property (nonatomic, copy) void (^failureBlock)(NSURLRequest*, NSHTTPURLResponse*, NSError*); +@end + +@implementation AFImageDownloaderResponseHandler + +- (instancetype)initWithUUID:(NSUUID *)uuid + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { + if (self = [self init]) { + self.uuid = uuid; + self.successBlock = success; + self.failureBlock = failure; + } + return self; +} + +- (NSString *)description { + return [NSString stringWithFormat: @"UUID: %@", [self.uuid UUIDString]]; +} + +@end + +@interface AFImageDownloaderMergedTask : NSObject +@property (nonatomic, strong) NSString *URLIdentifier; +@property (nonatomic, strong) NSUUID *identifier; +@property (nonatomic, strong) NSURLSessionDataTask *task; +@property (nonatomic, strong) NSMutableArray *responseHandlers; + +@end + +@implementation AFImageDownloaderMergedTask + +- (instancetype)initWithURLIdentifier:(NSString *)URLIdentifier identifier:(NSUUID *)identifier task:(NSURLSessionDataTask *)task { + if (self = [self init]) { + self.URLIdentifier = URLIdentifier; + self.task = task; + self.identifier = identifier; + self.responseHandlers = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)addResponseHandler:(AFImageDownloaderResponseHandler*)handler { + [self.responseHandlers addObject:handler]; +} + +- (void)removeResponseHandler:(AFImageDownloaderResponseHandler*)handler { + [self.responseHandlers removeObject:handler]; +} + +@end + +@implementation AFImageDownloadReceipt + +- (instancetype)initWithReceiptID:(NSUUID *)receiptID task:(NSURLSessionDataTask *)task { + if (self = [self init]) { + self.receiptID = receiptID; + self.task = task; + } + return self; +} + +@end + +@interface AFImageDownloader () + +@property (nonatomic, strong) dispatch_queue_t synchronizationQueue; +@property (nonatomic, strong) dispatch_queue_t responseQueue; + +@property (nonatomic, assign) NSInteger maximumActiveDownloads; +@property (nonatomic, assign) NSInteger activeRequestCount; + +@property (nonatomic, strong) NSMutableArray *queuedMergedTasks; +@property (nonatomic, strong) NSMutableDictionary *mergedTasks; + +@end + + +@implementation AFImageDownloader + ++ (NSURLCache *)defaultURLCache { + return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024 + diskCapacity:150 * 1024 * 1024 + diskPath:@"com.alamofire.imagedownloader"]; +} + ++ (NSURLSessionConfiguration *)defaultURLSessionConfiguration { + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + + //TODO set the default HTTP headers + + configuration.HTTPShouldSetCookies = YES; + configuration.HTTPShouldUsePipelining = NO; + + configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy; + configuration.allowsCellularAccess = YES; + configuration.timeoutIntervalForRequest = 60.0; + configuration.URLCache = [AFImageDownloader defaultURLCache]; + + return configuration; +} + +- (instancetype)init { + NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration]; + AFHTTPSessionManager *sessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:defaultConfiguration]; + sessionManager.responseSerializer = [AFImageResponseSerializer serializer]; + + return [self initWithSessionManager:sessionManager + downloadPrioritization:AFImageDownloadPrioritizationFIFO + maximumActiveDownloads:4 + imageCache:[[AFAutoPurgingImageCache alloc] init]]; +} + +- (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager + downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization + maximumActiveDownloads:(NSInteger)maximumActiveDownloads + imageCache:(id )imageCache { + if (self = [super init]) { + self.sessionManager = sessionManager; + + self.downloadPrioritizaton = downloadPrioritization; + self.maximumActiveDownloads = maximumActiveDownloads; + self.imageCache = imageCache; + + self.queuedMergedTasks = [[NSMutableArray alloc] init]; + self.mergedTasks = [[NSMutableDictionary alloc] init]; + self.activeRequestCount = 0; + + NSString *name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.synchronizationqueue-%@", [[NSUUID UUID] UUIDString]]; + self.synchronizationQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL); + + name = [NSString stringWithFormat:@"com.alamofire.imagedownloader.responsequeue-%@", [[NSUUID UUID] UUIDString]]; + self.responseQueue = dispatch_queue_create([name cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT); + } + + return self; +} + ++ (instancetype)defaultInstance { + static AFImageDownloader *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request + success:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, UIImage * _Nonnull))success + failure:(void (^)(NSURLRequest * _Nonnull, NSHTTPURLResponse * _Nullable, NSError * _Nonnull))failure { + return [self downloadImageForURLRequest:request withReceiptID:[NSUUID UUID] success:success failure:failure]; +} + +- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request + withReceiptID:(nonnull NSUUID *)receiptID + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { + __block NSURLSessionDataTask *task = nil; + dispatch_sync(self.synchronizationQueue, ^{ + NSString *URLIdentifier = request.URL.absoluteString; + if (URLIdentifier == nil) { + if (failure) { + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + failure(request, nil, error); + }); + } + return; + } + + // 1) Append the success and failure blocks to a pre-existing request if it already exists + AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier]; + if (existingMergedTask != nil) { + AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure]; + [existingMergedTask addResponseHandler:handler]; + task = existingMergedTask.task; + return; + } + + // 2) Attempt to load the image from the image cache if the cache policy allows it + switch (request.cachePolicy) { + case NSURLRequestUseProtocolCachePolicy: + case NSURLRequestReturnCacheDataElseLoad: + case NSURLRequestReturnCacheDataDontLoad: { + UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil]; + if (cachedImage != nil) { + if (success) { + dispatch_async(dispatch_get_main_queue(), ^{ + success(request, nil, cachedImage); + }); + } + return; + } + break; + } + default: + break; + } + + // 3) Create the request and set up authentication, validation and response serialization + NSUUID *mergedTaskIdentifier = [NSUUID UUID]; + NSURLSessionDataTask *createdTask; + __weak __typeof__(self) weakSelf = self; + + createdTask = [self.sessionManager + dataTaskWithRequest:request + completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { + dispatch_async(self.responseQueue, ^{ + __strong __typeof__(weakSelf) strongSelf = weakSelf; + AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; + if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) { + mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier]; + if (error) { + for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { + if (handler.failureBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler.failureBlock(request, (NSHTTPURLResponse*)response, error); + }); + } + } + } else { + [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil]; + + for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { + if (handler.successBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject); + }); + } + } + + } + } + [strongSelf safelyDecrementActiveTaskCount]; + [strongSelf safelyStartNextTaskIfNecessary]; + }); + }]; + + // 4) Store the response handler for use when the request completes + AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID + success:success + failure:failure]; + AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc] + initWithURLIdentifier:URLIdentifier + identifier:mergedTaskIdentifier + task:createdTask]; + [mergedTask addResponseHandler:handler]; + self.mergedTasks[URLIdentifier] = mergedTask; + + // 5) Either start the request or enqueue it depending on the current active request count + if ([self isActiveRequestCountBelowMaximumLimit]) { + [self startMergedTask:mergedTask]; + } else { + [self enqueueMergedTask:mergedTask]; + } + + task = mergedTask.task; + }); + if (task) { + return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task]; + } else { + return nil; + } +} + +- (void)cancelTaskForImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt { + dispatch_sync(self.synchronizationQueue, ^{ + NSString *URLIdentifier = imageDownloadReceipt.task.originalRequest.URL.absoluteString; + AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; + NSUInteger index = [mergedTask.responseHandlers indexOfObjectPassingTest:^BOOL(AFImageDownloaderResponseHandler * _Nonnull handler, __unused NSUInteger idx, __unused BOOL * _Nonnull stop) { + return handler.uuid == imageDownloadReceipt.receiptID; + }]; + + if (index != NSNotFound) { + AFImageDownloaderResponseHandler *handler = mergedTask.responseHandlers[index]; + [mergedTask removeResponseHandler:handler]; + NSString *failureReason = [NSString stringWithFormat:@"ImageDownloader cancelled URL request: %@",imageDownloadReceipt.task.originalRequest.URL.absoluteString]; + NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey:failureReason}; + NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; + if (handler.failureBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler.failureBlock(imageDownloadReceipt.task.originalRequest, nil, error); + }); + } + } + + if (mergedTask.responseHandlers.count == 0 && mergedTask.task.state == NSURLSessionTaskStateSuspended) { + [mergedTask.task cancel]; + [self removeMergedTaskWithURLIdentifier:URLIdentifier]; + } + }); +} + +- (AFImageDownloaderMergedTask*)safelyRemoveMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { + __block AFImageDownloaderMergedTask *mergedTask = nil; + dispatch_sync(self.synchronizationQueue, ^{ + mergedTask = [self removeMergedTaskWithURLIdentifier:URLIdentifier]; + }); + return mergedTask; +} + +//This method should only be called from safely within the synchronizationQueue +- (AFImageDownloaderMergedTask *)removeMergedTaskWithURLIdentifier:(NSString *)URLIdentifier { + AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; + [self.mergedTasks removeObjectForKey:URLIdentifier]; + return mergedTask; +} + +- (void)safelyDecrementActiveTaskCount { + dispatch_sync(self.synchronizationQueue, ^{ + if (self.activeRequestCount > 0) { + self.activeRequestCount -= 1; + } + }); +} + +- (void)safelyStartNextTaskIfNecessary { + dispatch_sync(self.synchronizationQueue, ^{ + if ([self isActiveRequestCountBelowMaximumLimit]) { + while (self.queuedMergedTasks.count > 0) { + AFImageDownloaderMergedTask *mergedTask = [self dequeueMergedTask]; + if (mergedTask.task.state == NSURLSessionTaskStateSuspended) { + [self startMergedTask:mergedTask]; + break; + } + } + } + }); +} + +- (void)startMergedTask:(AFImageDownloaderMergedTask *)mergedTask { + [mergedTask.task resume]; + ++self.activeRequestCount; +} + +- (void)enqueueMergedTask:(AFImageDownloaderMergedTask *)mergedTask { + switch (self.downloadPrioritizaton) { + case AFImageDownloadPrioritizationFIFO: + [self.queuedMergedTasks addObject:mergedTask]; + break; + case AFImageDownloadPrioritizationLIFO: + [self.queuedMergedTasks insertObject:mergedTask atIndex:0]; + break; + } +} + +- (AFImageDownloaderMergedTask *)dequeueMergedTask { + AFImageDownloaderMergedTask *mergedTask = nil; + mergedTask = [self.queuedMergedTasks firstObject]; + [self.queuedMergedTasks removeObject:mergedTask]; + return mergedTask; +} + +- (BOOL)isActiveRequestCountBelowMaximumLimit { + return self.activeRequestCount < self.maximumActiveDownloads; +} + +@end + +#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.h old mode 100644 new mode 100755 index 3c7649bd696..3bcf2895622 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.h @@ -1,5 +1,5 @@ // AFNetworkActivityIndicatorManager.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,16 +21,16 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS #import NS_ASSUME_NONNULL_BEGIN /** - `AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero. + `AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a session task has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero. You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code: @@ -52,9 +52,25 @@ NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropri @property (nonatomic, assign, getter = isEnabled) BOOL enabled; /** - A Boolean value indicating whether the network activity indicator is currently displayed in the status bar. + A Boolean value indicating whether the network activity indicator manager is currently active. +*/ +@property (readonly, nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; + +/** + A time interval indicating the minimum duration of networking activity that should occur before the activity indicator is displayed. The default value 1 second. If the network activity indicator should be displayed immediately when network activity occurs, this value should be set to 0 seconds. + + Apple's HIG describes the following: + + > Display the network activity indicator to provide feedback when your app accesses the network for more than a couple of seconds. If the operation finishes sooner than that, you don’t have to show the network activity indicator, because the indicator is likely to disappear before users notice its presence. + */ -@property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible; +@property (nonatomic, assign) NSTimeInterval activationDelay; + +/** + A time interval indicating the duration of time of no networking activity required before the activity indicator is disabled. This allows for continuous display of the network activity indicator across multiple requests. The default value is 0.17 seconds. + */ + +@property (nonatomic, assign) NSTimeInterval completionDelay; /** Returns the shared network activity indicator manager object for the system. @@ -73,6 +89,13 @@ NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropri */ - (void)decrementActivityCount; +/** + Set the a custom method to be executed when the network activity indicator manager should be hidden/shown. By default, this is null, and the UIApplication Network Activity Indicator will be managed automatically. If this block is set, it is the responsiblity of the caller to manager the network activity indicator going forward. + + @param block A block to be executed when the network activity indicator status changes. + */ +- (void)setNetworkingActivityActionWithBlock:(nullable void (^)(BOOL networkActivityIndicatorVisible))block; + @end NS_ASSUME_NONNULL_END diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.m old mode 100644 new mode 100755 index cf131804893..e77508e06a9 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/AFNetworkActivityIndicatorManager.m @@ -1,5 +1,5 @@ // AFNetworkActivityIndicatorManager.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,41 +21,42 @@ #import "AFNetworkActivityIndicatorManager.h" -#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) - -#import "AFHTTPRequestOperation.h" - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 +#if TARGET_OS_IOS #import "AFURLSessionManager.h" -#endif -static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17; +typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) { + AFNetworkActivityManagerStateNotActive, + AFNetworkActivityManagerStateDelayingStart, + AFNetworkActivityManagerStateActive, + AFNetworkActivityManagerStateDelayingEnd +}; + +static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0; +static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17; static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) { - if ([[notification object] isKindOfClass:[AFURLConnectionOperation class]]) { - return [(AFURLConnectionOperation *)[notification object] request]; - } - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([[notification object] respondsToSelector:@selector(originalRequest)]) { return [(NSURLSessionTask *)[notification object] originalRequest]; + } else { + return nil; } -#endif - - return nil; } +typedef void (^AFNetworkActivityActionBlock)(BOOL networkActivityIndicatorVisible); + @interface AFNetworkActivityIndicatorManager () @property (readwrite, nonatomic, assign) NSInteger activityCount; -@property (readwrite, nonatomic, strong) NSTimer *activityIndicatorVisibilityTimer; -@property (readonly, nonatomic, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; +@property (readwrite, nonatomic, strong) NSTimer *activationDelayTimer; +@property (readwrite, nonatomic, strong) NSTimer *completionDelayTimer; +@property (readonly, nonatomic, getter = isNetworkActivityOccurring) BOOL networkActivityOccurring; +@property (nonatomic, copy) AFNetworkActivityActionBlock networkActivityActionBlock; +@property (nonatomic, assign) AFNetworkActivityManagerState currentState; +@property (nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; -- (void)updateNetworkActivityIndicatorVisibility; -- (void)updateNetworkActivityIndicatorVisibilityDelayed; +- (void)updateCurrentStateForNetworkActivityChange; @end @implementation AFNetworkActivityIndicatorManager -@dynamic networkActivityIndicatorVisible; + (instancetype)sharedManager { static AFNetworkActivityIndicatorManager *_sharedManager = nil; @@ -67,24 +68,17 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat return _sharedManager; } -+ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { - return [NSSet setWithObject:@"activityCount"]; -} - -- (id)init { +- (instancetype)init { self = [super init]; if (!self) { return nil; } - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingOperationDidStartNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil]; - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 + self.currentState = AFNetworkActivityManagerStateNotActive; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil]; -#endif + self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay; + self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay; return self; } @@ -92,28 +86,40 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [_activityIndicatorVisibilityTimer invalidate]; + [_activationDelayTimer invalidate]; + [_completionDelayTimer invalidate]; } -- (void)updateNetworkActivityIndicatorVisibilityDelayed { - if (self.enabled) { - // Delay hiding of activity indicator for a short interval, to avoid flickering - if (![self isNetworkActivityIndicatorVisible]) { - [self.activityIndicatorVisibilityTimer invalidate]; - self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO]; - [[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes]; - } else { - [self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]]; - } +- (void)setEnabled:(BOOL)enabled { + _enabled = enabled; + if (enabled == NO) { + [self setCurrentState:AFNetworkActivityManagerStateNotActive]; } } -- (BOOL)isNetworkActivityIndicatorVisible { - return self.activityCount > 0; +- (void)setNetworkingActivityActionWithBlock:(void (^)(BOOL networkActivityIndicatorVisible))block { + self.networkActivityActionBlock = block; } -- (void)updateNetworkActivityIndicatorVisibility { - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; +- (BOOL)isNetworkActivityOccurring { + @synchronized(self) { + return self.activityCount > 0; + } +} + +- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible { + if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) { + [self willChangeValueForKey:@"networkActivityIndicatorVisible"]; + @synchronized(self) { + _networkActivityIndicatorVisible = networkActivityIndicatorVisible; + } + [self didChangeValueForKey:@"networkActivityIndicatorVisible"]; + if (self.networkActivityActionBlock) { + self.networkActivityActionBlock(networkActivityIndicatorVisible); + } else { + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible]; + } + } } - (void)setActivityCount:(NSInteger)activityCount { @@ -122,7 +128,7 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat } dispatch_async(dispatch_get_main_queue(), ^{ - [self updateNetworkActivityIndicatorVisibilityDelayed]; + [self updateCurrentStateForNetworkActivityChange]; }); } @@ -134,7 +140,7 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ - [self updateNetworkActivityIndicatorVisibilityDelayed]; + [self updateCurrentStateForNetworkActivityChange]; }); } @@ -149,7 +155,7 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ - [self updateNetworkActivityIndicatorVisibilityDelayed]; + [self updateCurrentStateForNetworkActivityChange]; }); } @@ -165,6 +171,91 @@ static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notificat } } +#pragma mark - Internal State Management +- (void)setCurrentState:(AFNetworkActivityManagerState)currentState { + @synchronized(self) { + if (_currentState != currentState) { + [self willChangeValueForKey:@"currentState"]; + _currentState = currentState; + switch (currentState) { + case AFNetworkActivityManagerStateNotActive: + [self cancelActivationDelayTimer]; + [self cancelCompletionDelayTimer]; + [self setNetworkActivityIndicatorVisible:NO]; + break; + case AFNetworkActivityManagerStateDelayingStart: + [self startActivationDelayTimer]; + break; + case AFNetworkActivityManagerStateActive: + [self cancelCompletionDelayTimer]; + [self setNetworkActivityIndicatorVisible:YES]; + break; + case AFNetworkActivityManagerStateDelayingEnd: + [self startCompletionDelayTimer]; + break; + } + } + [self didChangeValueForKey:@"currentState"]; + } +} + +- (void)updateCurrentStateForNetworkActivityChange { + if (self.enabled) { + switch (self.currentState) { + case AFNetworkActivityManagerStateNotActive: + if (self.isNetworkActivityOccurring) { + [self setCurrentState:AFNetworkActivityManagerStateDelayingStart]; + } + break; + case AFNetworkActivityManagerStateDelayingStart: + //No op. Let the delay timer finish out. + break; + case AFNetworkActivityManagerStateActive: + if (!self.isNetworkActivityOccurring) { + [self setCurrentState:AFNetworkActivityManagerStateDelayingEnd]; + } + break; + case AFNetworkActivityManagerStateDelayingEnd: + if (self.isNetworkActivityOccurring) { + [self setCurrentState:AFNetworkActivityManagerStateActive]; + } + break; + } + } +} + +- (void)startActivationDelayTimer { + self.activationDelayTimer = [NSTimer + timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; +} + +- (void)activationDelayTimerFired { + if (self.networkActivityOccurring) { + [self setCurrentState:AFNetworkActivityManagerStateActive]; + } else { + [self setCurrentState:AFNetworkActivityManagerStateNotActive]; + } +} + +- (void)startCompletionDelayTimer { + [self.completionDelayTimer invalidate]; + self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO]; + [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes]; +} + +- (void)completionDelayTimerFired { + [self setCurrentState:AFNetworkActivityManagerStateNotActive]; +} + +- (void)cancelActivationDelayTimer { + [self.activationDelayTimer invalidate]; +} + +- (void)cancelCompletionDelayTimer { + [self.completionDelayTimer invalidate]; +} + @end #endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.h old mode 100644 new mode 100755 index 0c8f9b52d73..d424c9b86c0 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.h @@ -1,5 +1,5 @@ // UIActivityIndicatorView+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,16 +21,14 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV #import -@class AFURLConnectionOperation; - /** - This category adds methods to the UIKit framework's `UIActivityIndicatorView` class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a request operation or session task. + This category adds methods to the UIKit framework's `UIActivityIndicatorView` class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a session task. */ @interface UIActivityIndicatorView (AFNetworking) @@ -43,20 +41,7 @@ @param task The task. If `nil`, automatic updating from any previously specified operation will be disabled. */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task; -#endif - -///--------------------------------------- -/// @name Animating for Request Operations -///--------------------------------------- - -/** - Binds the animating state to the execution state of the specified operation. - - @param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled. - */ -- (void)setAnimatingWithStateOfOperation:(nullable AFURLConnectionOperation *)operation; @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.m old mode 100644 new mode 100755 index dd362b0171a..ed704eda39a --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIActivityIndicatorView+AFNetworking.m @@ -1,5 +1,5 @@ // UIActivityIndicatorView+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,22 +22,15 @@ #import "UIActivityIndicatorView+AFNetworking.h" #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV -#import "AFHTTPRequestOperation.h" - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" -#endif @interface AFActivityIndicatorViewNotificationObserver : NSObject @property (readonly, nonatomic, weak) UIActivityIndicatorView *activityIndicatorView; - (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView; -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task; -#endif -- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation; @end @@ -52,15 +45,9 @@ return notificationObserver; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task { [[self af_notificationObserver] setAnimatingWithStateOfTask:task]; } -#endif - -- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation { - [[self af_notificationObserver] setAnimatingWithStateOfOperation:operation]; -} @end @@ -75,7 +62,6 @@ return self; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; @@ -102,34 +88,6 @@ } } } -#endif - -#pragma mark - - -- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation { - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - - [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; - [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; - - if (operation) { - if (![operation isFinished]) { - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wreceiver-is-weak" -#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" - if ([operation isExecuting]) { - [self.activityIndicatorView startAnimating]; - } else { - [self.activityIndicatorView stopAnimating]; - } -#pragma clang diagnostic pop - - [notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingOperationDidStartNotification object:operation]; - [notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingOperationDidFinishNotification object:operation]; - } - } -} #pragma mark - @@ -156,14 +114,9 @@ - (void)dealloc { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil]; -#endif - - [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; - [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; } @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.h deleted file mode 100644 index 97f5622bbf4..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.h +++ /dev/null @@ -1,99 +0,0 @@ -// UIAlertView+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import - -#import - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) - -#import - -NS_ASSUME_NONNULL_BEGIN - -@class AFURLConnectionOperation; - -/** - This category adds methods to the UIKit framework's `UIAlertView` class. The methods in this category provide support for automatically showing an alert if a session task or request operation finishes with an error. Alert title and message are filled from the corresponding `localizedDescription` & `localizedRecoverySuggestion` or `localizedFailureReason` of the error. - */ -@interface UIAlertView (AFNetworking) - -///------------------------------------- -/// @name Showing Alert for Session Task -///------------------------------------- - -/** - Shows an alert view with the error of the specified session task, if any. - - @param task The session task. - @param delegate The alert view delegate. - */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 -+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task - delegate:(nullable id)delegate NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions."); -#endif - -/** - Shows an alert view with the error of the specified session task, if any, with a custom cancel button title and other button titles. - - @param task The session task. - @param delegate The alert view delegate. - @param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title. - @param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`. - */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 -+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task - delegate:(nullable id)delegate - cancelButtonTitle:(nullable NSString *)cancelButtonTitle - otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions."); -#endif - -///------------------------------------------ -/// @name Showing Alert for Request Operation -///------------------------------------------ - -/** - Shows an alert view with the error of the specified request operation, if any. - - @param operation The request operation. - @param delegate The alert view delegate. - */ -+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation - delegate:(nullable id)delegate NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions."); - -/** - Shows an alert view with the error of the specified request operation, if any, with a custom cancel button title and other button titles. - - @param operation The request operation. - @param delegate The alert view delegate. - @param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title. - @param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`. - */ -+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation - delegate:(nullable id)delegate - cancelButtonTitle:(nullable NSString *)cancelButtonTitle - otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions."); - -@end - -NS_ASSUME_NONNULL_END - -#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.m deleted file mode 100644 index 0d1e9e7193d..00000000000 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIAlertView+AFNetworking.m +++ /dev/null @@ -1,141 +0,0 @@ -// UIAlertView+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) -// -// 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. - -#import "UIAlertView+AFNetworking.h" - -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) - -#import "AFURLConnectionOperation.h" - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 -#import "AFURLSessionManager.h" -#endif - -static void AFGetAlertViewTitleAndMessageFromError(NSError *error, NSString * __autoreleasing *title, NSString * __autoreleasing *message) { - if (error.localizedDescription && (error.localizedRecoverySuggestion || error.localizedFailureReason)) { - *title = error.localizedDescription; - - if (error.localizedRecoverySuggestion) { - *message = error.localizedRecoverySuggestion; - } else { - *message = error.localizedFailureReason; - } - } else if (error.localizedDescription) { - *title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description"); - *message = error.localizedDescription; - } else { - *title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description"); - *message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%@ Error: %ld", @"AFNetworking", @"Fallback Error Failure Reason Format"), error.domain, (long)error.code]; - } -} - -@implementation UIAlertView (AFNetworking) - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 -+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task - delegate:(id)delegate -{ - [self showAlertViewForTaskWithErrorOnCompletion:task delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil]; -} - -+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task - delegate:(id)delegate - cancelButtonTitle:(NSString *)cancelButtonTitle - otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION -{ - NSMutableArray *mutableOtherTitles = [NSMutableArray array]; - va_list otherButtonTitleList; - va_start(otherButtonTitleList, otherButtonTitles); - { - for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) { - [mutableOtherTitles addObject:otherButtonTitle]; - } - } - va_end(otherButtonTitleList); - - __block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingTaskDidCompleteNotification object:task queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { - NSError *error = notification.userInfo[AFNetworkingTaskDidCompleteErrorKey]; - if (error) { - NSString *title, *message; - AFGetAlertViewTitleAndMessageFromError(error, &title, &message); - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]; - for (NSString *otherButtonTitle in mutableOtherTitles) { - [alertView addButtonWithTitle:otherButtonTitle]; - } - [alertView setTitle:title]; - [alertView setMessage:message]; - [alertView show]; - } - - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - }]; -} -#endif - -#pragma mark - - -+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation - delegate:(id)delegate -{ - [self showAlertViewForRequestOperationWithErrorOnCompletion:operation delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil]; -} - -+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation - delegate:(id)delegate - cancelButtonTitle:(NSString *)cancelButtonTitle - otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION -{ - NSMutableArray *mutableOtherTitles = [NSMutableArray array]; - va_list otherButtonTitleList; - va_start(otherButtonTitleList, otherButtonTitles); - { - for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) { - [mutableOtherTitles addObject:otherButtonTitle]; - } - } - va_end(otherButtonTitleList); - - __block __weak id observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingOperationDidFinishNotification object:operation queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { - - if (notification.object && [notification.object isKindOfClass:[AFURLConnectionOperation class]]) { - NSError *error = [(AFURLConnectionOperation *)notification.object error]; - if (error) { - NSString *title, *message; - AFGetAlertViewTitleAndMessageFromError(error, &title, &message); - - UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil]; - for (NSString *otherButtonTitle in mutableOtherTitles) { - [alertView addButtonWithTitle:otherButtonTitle]; - } - [alertView setTitle:title]; - [alertView setMessage:message]; - [alertView show]; - } - } - - [[NSNotificationCenter defaultCenter] removeObserver:observer]; - }]; -} - -@end - -#endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.h old mode 100644 new mode 100755 index 7289bdb4799..d33e0d4a3b0 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.h @@ -1,5 +1,5 @@ // UIButton+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,15 +21,15 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV #import NS_ASSUME_NONNULL_BEGIN -@protocol AFURLResponseSerialization, AFImageCache; +@class AFImageDownloader; /** This category adds methods to the UIKit framework's `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL. @@ -38,32 +38,21 @@ NS_ASSUME_NONNULL_BEGIN */ @interface UIButton (AFNetworking) -///---------------------------- -/// @name Accessing Image Cache -///---------------------------- - -/** - The image cache used to improve image loading performance on scroll views. By default, `UIButton` will use the `sharedImageCache` of `UIImageView`. - */ -+ (id )sharedImageCache; - -/** - Set the cache used for image loading. - - @param imageCache The image cache. - */ -+ (void)setSharedImageCache:(__nullable id )imageCache; - ///------------------------------------ -/// @name Accessing Response Serializer +/// @name Accessing the Image Downloader ///------------------------------------ /** - The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`. + Set the shared image downloader used to download images. - @discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer + @param imageDownloader The shared image downloader used to download images. +*/ ++ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader; + +/** + The shared image downloader used to download images. */ -@property (nonatomic, strong) id imageResponseSerializer; ++ (AFImageDownloader *)sharedImageDownloader; ///-------------------- /// @name Setting Image @@ -103,14 +92,14 @@ NS_ASSUME_NONNULL_BEGIN @param state The control state. @param urlRequest The URL request used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes. - @param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes two arguments: the server response and the image. If the image was returned from cache, the response parameter will be `nil`. - @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes a single argument: the error that occurred. + @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. + @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. */ - (void)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage - success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(nullable void (^)(NSError *error))failure; + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; ///------------------------------- @@ -151,14 +140,14 @@ NS_ASSUME_NONNULL_BEGIN @param state The control state. @param urlRequest The URL request used for the image request. @param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes. - @param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes two arguments: the server response and the image. If the image was returned from cache, the response parameter will be `nil`. - @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes a single argument: the error that occurred. + @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. + @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. */ - (void)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage - success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(nullable void (^)(NSError *error))failure; + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; ///------------------------------ @@ -166,18 +155,18 @@ NS_ASSUME_NONNULL_BEGIN ///------------------------------ /** - Cancels any executing image operation for the specified control state of the receiver, if one exists. + Cancels any executing image task for the specified control state of the receiver, if one exists. @param state The control state. */ -- (void)cancelImageRequestOperationForState:(UIControlState)state; +- (void)cancelImageDownloadTaskForState:(UIControlState)state; /** - Cancels any executing background image operation for the specified control state of the receiver, if one exists. + Cancels any executing background image task for the specified control state of the receiver, if one exists. @param state The control state. */ -- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state; +- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state; @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.m old mode 100644 new mode 100755 index e5c36a5592e..5bc49ddf0dc --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIButton+AFNetworking.m @@ -1,5 +1,5 @@ // UIButton+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,89 +23,76 @@ #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) - -#import "AFURLResponseSerialization.h" -#import "AFHTTPRequestOperation.h" +#if TARGET_OS_IOS || TARGET_OS_TV #import "UIImageView+AFNetworking.h" +#import "AFImageDownloader.h" @interface UIButton (_AFNetworking) @end @implementation UIButton (_AFNetworking) -+ (NSOperationQueue *)af_sharedImageRequestOperationQueue { - static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init]; - _af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; - }); - - return _af_sharedImageRequestOperationQueue; -} - #pragma mark - -static char AFImageRequestOperationNormal; -static char AFImageRequestOperationHighlighted; -static char AFImageRequestOperationSelected; -static char AFImageRequestOperationDisabled; +static char AFImageDownloadReceiptNormal; +static char AFImageDownloadReceiptHighlighted; +static char AFImageDownloadReceiptSelected; +static char AFImageDownloadReceiptDisabled; -static const char * af_imageRequestOperationKeyForState(UIControlState state) { +static const char * af_imageDownloadReceiptKeyForState(UIControlState state) { switch (state) { case UIControlStateHighlighted: - return &AFImageRequestOperationHighlighted; + return &AFImageDownloadReceiptHighlighted; case UIControlStateSelected: - return &AFImageRequestOperationSelected; + return &AFImageDownloadReceiptSelected; case UIControlStateDisabled: - return &AFImageRequestOperationDisabled; + return &AFImageDownloadReceiptDisabled; case UIControlStateNormal: default: - return &AFImageRequestOperationNormal; + return &AFImageDownloadReceiptNormal; } } -- (AFHTTPRequestOperation *)af_imageRequestOperationForState:(UIControlState)state { - return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_imageRequestOperationKeyForState(state)); +- (AFImageDownloadReceipt *)af_imageDownloadReceiptForState:(UIControlState)state { + return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_imageDownloadReceiptKeyForState(state)); } -- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation +- (void)af_setImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt forState:(UIControlState)state { - objc_setAssociatedObject(self, af_imageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, af_imageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - -static char AFBackgroundImageRequestOperationNormal; -static char AFBackgroundImageRequestOperationHighlighted; -static char AFBackgroundImageRequestOperationSelected; -static char AFBackgroundImageRequestOperationDisabled; +static char AFBackgroundImageDownloadReceiptNormal; +static char AFBackgroundImageDownloadReceiptHighlighted; +static char AFBackgroundImageDownloadReceiptSelected; +static char AFBackgroundImageDownloadReceiptDisabled; -static const char * af_backgroundImageRequestOperationKeyForState(UIControlState state) { +static const char * af_backgroundImageDownloadReceiptKeyForState(UIControlState state) { switch (state) { case UIControlStateHighlighted: - return &AFBackgroundImageRequestOperationHighlighted; + return &AFBackgroundImageDownloadReceiptHighlighted; case UIControlStateSelected: - return &AFBackgroundImageRequestOperationSelected; + return &AFBackgroundImageDownloadReceiptSelected; case UIControlStateDisabled: - return &AFBackgroundImageRequestOperationDisabled; + return &AFBackgroundImageDownloadReceiptDisabled; case UIControlStateNormal: default: - return &AFBackgroundImageRequestOperationNormal; + return &AFBackgroundImageDownloadReceiptNormal; } } -- (AFHTTPRequestOperation *)af_backgroundImageRequestOperationForState:(UIControlState)state { - return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state)); +- (AFImageDownloadReceipt *)af_backgroundImageDownloadReceiptForState:(UIControlState)state { + return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state)); } -- (void)af_setBackgroundImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation +- (void)af_setBackgroundImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt forState:(UIControlState)state { - objc_setAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, af_backgroundImageDownloadReceiptKeyForState(state), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end @@ -114,34 +101,16 @@ static const char * af_backgroundImageRequestOperationKeyForState(UIControlState @implementation UIButton (AFNetworking) -+ (id )sharedImageCache { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: [UIImageView sharedImageCache]; -#pragma clang diagnostic pop -} - -+ (void)setSharedImageCache:(__nullable id )imageCache { - objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -#pragma mark - - -- (id )imageResponseSerializer { - static id _af_defaultImageResponseSerializer = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer]; - }); ++ (AFImageDownloader *)sharedImageDownloader { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" - return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer; + return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance]; #pragma clang diagnostic pop } -- (void)setImageResponseSerializer:(id )serializer { - objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); ++ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader { + objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - @@ -164,49 +133,62 @@ static const char * af_backgroundImageRequestOperationKeyForState(UIControlState - (void)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest - placeholderImage:(UIImage *)placeholderImage - success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(void (^)(NSError *error))failure + placeholderImage:(nullable UIImage *)placeholderImage + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { - [self cancelImageRequestOperationForState:state]; + if ([self isActiveTaskURLEqualToURLRequest:urlRequest forState:state]) { + return; + } - UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; + [self cancelImageDownloadTaskForState:state]; + + AFImageDownloader *downloader = [[self class] sharedImageDownloader]; + id imageCache = downloader.imageCache; + + //Use the image from the image cache if it exists + UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; if (cachedImage) { if (success) { success(urlRequest, nil, cachedImage); } else { [self setImage:cachedImage forState:state]; } - - [self af_setImageRequestOperation:nil forState:state]; + [self af_setImageDownloadReceipt:nil forState:state]; } else { if (placeholderImage) { [self setImage:placeholderImage forState:state]; } __weak __typeof(self)weakSelf = self; - AFHTTPRequestOperation *imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; - imageRequestOperation.responseSerializer = self.imageResponseSerializer; - [imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if ([[urlRequest URL] isEqual:[operation.request URL]]) { - if (success) { - success(operation.request, operation.response, responseObject); - } else if (responseObject) { - [strongSelf setImage:responseObject forState:state]; - } - } - [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - if ([[urlRequest URL] isEqual:[operation.request URL]]) { - if (failure) { - failure(error); - } - } - }]; + NSUUID *downloadID = [NSUUID UUID]; + AFImageDownloadReceipt *receipt; + receipt = [downloader + downloadImageForURLRequest:urlRequest + withReceiptID:downloadID + success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) { + if (success) { + success(request, response, responseObject); + } else if(responseObject) { + [strongSelf setImage:responseObject forState:state]; + } + [strongSelf af_setImageDownloadReceipt:nil forState:state]; + } - [self af_setImageRequestOperation:imageRequestOperation forState:state]; - [[[self class] af_sharedImageRequestOperationQueue] addOperation:imageRequestOperation]; + } + failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([[strongSelf af_imageDownloadReceiptForState:state].receiptID isEqual:downloadID]) { + if (failure) { + failure(request, response, error); + } + [strongSelf af_setImageDownloadReceipt:nil forState:state]; + } + }]; + + [self af_setImageDownloadReceipt:receipt forState:state]; } } @@ -220,7 +202,7 @@ static const char * af_backgroundImageRequestOperationKeyForState(UIControlState - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url - placeholderImage:(UIImage *)placeholderImage + placeholderImage:(nullable UIImage *)placeholderImage { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; @@ -230,64 +212,94 @@ static const char * af_backgroundImageRequestOperationKeyForState(UIControlState - (void)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest - placeholderImage:(UIImage *)placeholderImage - success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(void (^)(NSError *error))failure + placeholderImage:(nullable UIImage *)placeholderImage + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { - [self cancelBackgroundImageRequestOperationForState:state]; + if ([self isActiveBackgroundTaskURLEqualToURLRequest:urlRequest forState:state]) { + return; + } - UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; + [self cancelBackgroundImageDownloadTaskForState:state]; + + AFImageDownloader *downloader = [[self class] sharedImageDownloader]; + id imageCache = downloader.imageCache; + + //Use the image from the image cache if it exists + UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; if (cachedImage) { if (success) { success(urlRequest, nil, cachedImage); } else { [self setBackgroundImage:cachedImage forState:state]; } - - [self af_setBackgroundImageRequestOperation:nil forState:state]; + [self af_setBackgroundImageDownloadReceipt:nil forState:state]; } else { if (placeholderImage) { [self setBackgroundImage:placeholderImage forState:state]; } __weak __typeof(self)weakSelf = self; - AFHTTPRequestOperation *backgroundImageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; - backgroundImageRequestOperation.responseSerializer = self.imageResponseSerializer; - [backgroundImageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if ([[urlRequest URL] isEqual:[operation.request URL]]) { - if (success) { - success(operation.request, operation.response, responseObject); - } else if (responseObject) { - [strongSelf setBackgroundImage:responseObject forState:state]; - } - } - [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - if ([[urlRequest URL] isEqual:[operation.request URL]]) { - if (failure) { - failure(error); - } - } - }]; + NSUUID *downloadID = [NSUUID UUID]; + AFImageDownloadReceipt *receipt; + receipt = [downloader + downloadImageForURLRequest:urlRequest + withReceiptID:downloadID + success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([[strongSelf af_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) { + if (success) { + success(request, response, responseObject); + } else if(responseObject) { + [strongSelf setBackgroundImage:responseObject forState:state]; + } + [strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state]; + } - [self af_setBackgroundImageRequestOperation:backgroundImageRequestOperation forState:state]; - [[[self class] af_sharedImageRequestOperationQueue] addOperation:backgroundImageRequestOperation]; + } + failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([[strongSelf af_backgroundImageDownloadReceiptForState:state].receiptID isEqual:downloadID]) { + if (failure) { + failure(request, response, error); + } + [strongSelf af_setBackgroundImageDownloadReceipt:nil forState:state]; + } + }]; + + [self af_setBackgroundImageDownloadReceipt:receipt forState:state]; } } #pragma mark - -- (void)cancelImageRequestOperationForState:(UIControlState)state { - [[self af_imageRequestOperationForState:state] cancel]; - [self af_setImageRequestOperation:nil forState:state]; +- (void)cancelImageDownloadTaskForState:(UIControlState)state { + AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state]; + if (receipt != nil) { + [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt]; + [self af_setImageDownloadReceipt:nil forState:state]; + } } -- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state { - [[self af_backgroundImageRequestOperationForState:state] cancel]; - [self af_setBackgroundImageRequestOperation:nil forState:state]; +- (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state { + AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state]; + if (receipt != nil) { + [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:receipt]; + [self af_setBackgroundImageDownloadReceipt:nil forState:state]; + } } +- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state { + AFImageDownloadReceipt *receipt = [self af_imageDownloadReceiptForState:state]; + return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString]; +} + +- (BOOL)isActiveBackgroundTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest forState:(UIControlState)state { + AFImageDownloadReceipt *receipt = [self af_backgroundImageDownloadReceiptForState:state]; + return [receipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString]; +} + + @end #endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImage+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImage+AFNetworking.h old mode 100644 new mode 100755 index 32929202b04..14744cddd54 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImage+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImage+AFNetworking.h @@ -22,7 +22,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV #import diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.h old mode 100644 new mode 100755 index bf619154886..8929252e5a2 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.h @@ -1,5 +1,5 @@ // UIImageView+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,47 +21,36 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV #import NS_ASSUME_NONNULL_BEGIN -@protocol AFURLResponseSerialization, AFImageCache; +@class AFImageDownloader; /** This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL. */ @interface UIImageView (AFNetworking) -///---------------------------- -/// @name Accessing Image Cache -///---------------------------- - -/** - The image cache used to improve image loading performance on scroll views. By default, this is an `NSCache` subclass conforming to the `AFImageCache` protocol, which listens for notification warnings and evicts objects accordingly. -*/ -+ (id )sharedImageCache; - -/** - Set the cache used for image loading. - - @param imageCache The image cache. - */ -+ (void)setSharedImageCache:(__nullable id )imageCache; - ///------------------------------------ -/// @name Accessing Response Serializer +/// @name Accessing the Image Downloader ///------------------------------------ /** - The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`. + Set the shared image downloader used to download images. - @discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer + @param imageDownloader The shared image downloader used to download images. */ -@property (nonatomic, strong) id imageResponseSerializer; ++ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader; + +/** + The shared image downloader used to download images. + */ ++ (AFImageDownloader *)sharedImageDownloader; ///-------------------- /// @name Setting Image @@ -100,47 +89,21 @@ NS_ASSUME_NONNULL_BEGIN @param urlRequest The URL request used for the image request. @param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes. - @param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. - @param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. + @param success A block to be executed when the image data task finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`. + @param failure A block object to be executed when the image data task finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred. */ - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage - success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, NSError *error))failure; + success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; /** Cancels any executing image operation for the receiver, if one exists. */ -- (void)cancelImageRequestOperation; +- (void)cancelImageDownloadTask; @end -#pragma mark - - -/** - The `AFImageCache` protocol is adopted by an object used to cache images loaded by the AFNetworking category on `UIImageView`. - */ -@protocol AFImageCache - -/** - Returns a cached image for the specified request, if available. - - @param request The image request. - - @return The cached image. - */ -- (nullable UIImage *)cachedImageForRequest:(NSURLRequest *)request; - -/** - Caches a particular image for the specified request. - - @param image The image to cache. - @param request The request to be used as a cache key. - */ -- (void)cacheImage:(UIImage *)image - forRequest:(NSURLRequest *)request; -@end - NS_ASSUME_NONNULL_END #endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.m old mode 100644 new mode 100755 index 2efc1606c35..5934d681fcd --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIImageView+AFNetworking.m @@ -1,5 +1,5 @@ // UIImageView+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,38 +23,22 @@ #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV -#import "AFHTTPRequestOperation.h" - -@interface AFImageCache : NSCache -@end - -#pragma mark - +#import "AFImageDownloader.h" @interface UIImageView (_AFNetworking) -@property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation; +@property (readwrite, nonatomic, strong, setter = af_setActiveImageDownloadReceipt:) AFImageDownloadReceipt *af_activeImageDownloadReceipt; @end @implementation UIImageView (_AFNetworking) -+ (NSOperationQueue *)af_sharedImageRequestOperationQueue { - static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init]; - _af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount; - }); - - return _af_sharedImageRequestOperationQueue; +- (AFImageDownloadReceipt *)af_activeImageDownloadReceipt { + return (AFImageDownloadReceipt *)objc_getAssociatedObject(self, @selector(af_activeImageDownloadReceipt)); } -- (AFHTTPRequestOperation *)af_imageRequestOperation { - return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_imageRequestOperation)); -} - -- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation { - objc_setAssociatedObject(self, @selector(af_imageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)af_setActiveImageDownloadReceipt:(AFImageDownloadReceipt *)imageDownloadReceipt { + objc_setAssociatedObject(self, @selector(af_activeImageDownloadReceipt), imageDownloadReceipt, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end @@ -62,46 +46,17 @@ #pragma mark - @implementation UIImageView (AFNetworking) -@dynamic imageResponseSerializer; -+ (id )sharedImageCache { - static AFImageCache *_af_defaultImageCache = nil; - static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ - _af_defaultImageCache = [[AFImageCache alloc] init]; - - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) { - [_af_defaultImageCache removeAllObjects]; - }]; - }); ++ (AFImageDownloader *)sharedImageDownloader { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" - return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache; + return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance]; #pragma clang diagnostic pop } -+ (void)setSharedImageCache:(__nullable id )imageCache { - objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -#pragma mark - - -- (id )imageResponseSerializer { - static id _af_defaultImageResponseSerializer = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - _af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer]; - }); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer; -#pragma clang diagnostic pop -} - -- (void)setImageResponseSerializer:(id )serializer { - objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); ++ (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader { + objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - @@ -121,93 +76,84 @@ - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage - success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success - failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, NSError *error))failure + success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success + failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { - [self cancelImageRequestOperation]; - UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest]; + if ([urlRequest URL] == nil) { + [self cancelImageDownloadTask]; + self.image = placeholderImage; + return; + } + + if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){ + return; + } + + [self cancelImageDownloadTask]; + + AFImageDownloader *downloader = [[self class] sharedImageDownloader]; + id imageCache = downloader.imageCache; + + //Use the image from the image cache if it exists + UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; if (cachedImage) { if (success) { success(urlRequest, nil, cachedImage); } else { self.image = cachedImage; } - - self.af_imageRequestOperation = nil; + [self clearActiveDownloadInformation]; } else { if (placeholderImage) { self.image = placeholderImage; } __weak __typeof(self)weakSelf = self; - self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest]; - self.af_imageRequestOperation.responseSerializer = self.imageResponseSerializer; - [self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { - if (success) { - success(urlRequest, operation.response, responseObject); - } else if (responseObject) { - strongSelf.image = responseObject; - } + NSUUID *downloadID = [NSUUID UUID]; + AFImageDownloadReceipt *receipt; + receipt = [downloader + downloadImageForURLRequest:urlRequest + withReceiptID:downloadID + success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { + if (success) { + success(request, response, responseObject); + } else if(responseObject) { + strongSelf.image = responseObject; + } + [strongSelf clearActiveDownloadInformation]; + } - if (operation == strongSelf.af_imageRequestOperation){ - strongSelf.af_imageRequestOperation = nil; - } - } + } + failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) { + __strong __typeof(weakSelf)strongSelf = weakSelf; + if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { + if (failure) { + failure(request, response, error); + } + [strongSelf clearActiveDownloadInformation]; + } + }]; - [[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) { - if (failure) { - failure(urlRequest, operation.response, error); - } - - if (operation == strongSelf.af_imageRequestOperation){ - strongSelf.af_imageRequestOperation = nil; - } - } - }]; - - [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation]; + self.af_activeImageDownloadReceipt = receipt; } } -- (void)cancelImageRequestOperation { - [self.af_imageRequestOperation cancel]; - self.af_imageRequestOperation = nil; +- (void)cancelImageDownloadTask { + if (self.af_activeImageDownloadReceipt != nil) { + [[self.class sharedImageDownloader] cancelTaskForImageDownloadReceipt:self.af_activeImageDownloadReceipt]; + [self clearActiveDownloadInformation]; + } } -@end - -#pragma mark - - -static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) { - return [[request URL] absoluteString]; +- (void)clearActiveDownloadInformation { + self.af_activeImageDownloadReceipt = nil; } -@implementation AFImageCache - -- (UIImage *)cachedImageForRequest:(NSURLRequest *)request { - switch ([request cachePolicy]) { - case NSURLRequestReloadIgnoringCacheData: - case NSURLRequestReloadIgnoringLocalAndRemoteCacheData: - return nil; - default: - break; - } - - return [self objectForKey:AFImageCacheKeyFromURLRequest(request)]; -} - -- (void)cacheImage:(UIImage *)image - forRequest:(NSURLRequest *)request -{ - if (image && request) { - [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)]; - } +- (BOOL)isActiveTaskURLEqualToURLRequest:(NSURLRequest *)urlRequest { + return [self.af_activeImageDownloadReceipt.task.originalRequest.URL.absoluteString isEqualToString:urlRequest.URL.absoluteString]; } @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIKit+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIKit+AFNetworking.h old mode 100644 new mode 100755 index 49850ede78a..febacfc7557 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIKit+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIKit+AFNetworking.h @@ -1,6 +1,6 @@ // UIKit+AFNetworking.h // -// Copyright (c) 2013 AFNetworking (http://afnetworking.com/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -20,20 +20,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_TV #import #ifndef _UIKIT_AFNETWORKING_ #define _UIKIT_AFNETWORKING_ +#if TARGET_OS_IOS + #import "AFAutoPurgingImageCache.h" + #import "AFImageDownloader.h" #import "AFNetworkActivityIndicatorManager.h" + #import "UIRefreshControl+AFNetworking.h" + #import "UIWebView+AFNetworking.h" +#endif #import "UIActivityIndicatorView+AFNetworking.h" - #import "UIAlertView+AFNetworking.h" #import "UIButton+AFNetworking.h" #import "UIImageView+AFNetworking.h" #import "UIProgressView+AFNetworking.h" - #import "UIRefreshControl+AFNetworking.h" - #import "UIWebView+AFNetworking.h" #endif /* _UIKIT_AFNETWORKING_ */ #endif diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.h old mode 100644 new mode 100755 index 5c00d6d4195..8ea0a731d54 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.h @@ -1,5 +1,5 @@ // UIProgressView+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,18 +21,17 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV #import NS_ASSUME_NONNULL_BEGIN -@class AFURLConnectionOperation; /** - This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task or request operation. + This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task. */ @interface UIProgressView (AFNetworking) @@ -46,10 +45,8 @@ NS_ASSUME_NONNULL_BEGIN @param task The session task. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL)animated; -#endif /** Binds the progress to the download progress of the specified session task. @@ -57,32 +54,8 @@ NS_ASSUME_NONNULL_BEGIN @param task The session task. @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task animated:(BOOL)animated; -#endif - -///------------------------------------ -/// @name Setting Session Task Progress -///------------------------------------ - -/** - Binds the progress to the upload progress of the specified request operation. - - @param operation The request operation. - @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. - */ -- (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation - animated:(BOOL)animated; - -/** - Binds the progress to the download progress of the specified request operation. - - @param operation The request operation. - @param animated `YES` if the change should be animated, `NO` if the change should happen immediately. - */ -- (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation - animated:(BOOL)animated; @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.m old mode 100644 new mode 100755 index ad2c280ba8d..058755e2789 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIProgressView+AFNetworking.m @@ -1,5 +1,5 @@ // UIProgressView+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,33 +23,13 @@ #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS || TARGET_OS_TV -#import "AFURLConnectionOperation.h" - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" -#endif static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext; static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext; -@interface AFURLConnectionOperation (_UIProgressView) -@property (readwrite, nonatomic, copy) void (^uploadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); -@property (readwrite, nonatomic, assign, setter = af_setUploadProgressAnimated:) BOOL af_uploadProgressAnimated; - -@property (readwrite, nonatomic, copy) void (^downloadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); -@property (readwrite, nonatomic, assign, setter = af_setDownloadProgressAnimated:) BOOL af_downloadProgressAnimated; -@end - -@implementation AFURLConnectionOperation (_UIProgressView) -@dynamic uploadProgress; // Implemented in AFURLConnectionOperation -@dynamic af_uploadProgressAnimated; - -@dynamic downloadProgress; // Implemented in AFURLConnectionOperation -@dynamic af_downloadProgressAnimated; -@end - #pragma mark - @implementation UIProgressView (AFNetworking) @@ -72,7 +52,6 @@ static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedCon #pragma mark - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL)animated { @@ -90,47 +69,6 @@ static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedCon [self af_setDownloadProgressAnimated:animated]; } -#endif - -#pragma mark - - -- (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation - animated:(BOOL)animated -{ - __weak __typeof(self)weakSelf = self; - void (^original)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) = [operation.uploadProgress copy]; - [operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { - if (original) { - original(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); - } - - dispatch_async(dispatch_get_main_queue(), ^{ - if (totalBytesExpectedToWrite > 0) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - [strongSelf setProgress:(totalBytesWritten / (totalBytesExpectedToWrite * 1.0f)) animated:animated]; - } - }); - }]; -} - -- (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation - animated:(BOOL)animated -{ - __weak __typeof(self)weakSelf = self; - void (^original)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) = [operation.downloadProgress copy]; - [operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) { - if (original) { - original(bytesRead, totalBytesRead, totalBytesExpectedToRead); - } - - dispatch_async(dispatch_get_main_queue(), ^{ - if (totalBytesExpectedToRead > 0) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - [strongSelf setProgress:(totalBytesRead / (totalBytesExpectedToRead * 1.0f)) animated:animated]; - } - }); - }]; -} #pragma mark - NSKeyValueObserving @@ -139,7 +77,6 @@ static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedCon change:(__unused NSDictionary *)change context:(void *)context { -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) { if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) { if ([object countOfBytesExpectedToSend] > 0) { @@ -174,7 +111,6 @@ static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedCon } } } -#endif } @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.h old mode 100644 new mode 100755 index a65e390d37d..215eafcf742 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.h @@ -1,6 +1,6 @@ // UIRefreshControl+AFNetworking.m // -// Copyright (c) 2014 AFNetworking (http://afnetworking.com) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,18 +22,16 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS #import NS_ASSUME_NONNULL_BEGIN -@class AFURLConnectionOperation; - /** - This category adds methods to the UIKit framework's `UIRefreshControl` class. The methods in this category provide support for automatically beginning and ending refreshing depending on the loading state of a request operation or session task. + This category adds methods to the UIKit framework's `UIRefreshControl` class. The methods in this category provide support for automatically beginning and ending refreshing depending on the loading state of a session task. */ @interface UIRefreshControl (AFNetworking) @@ -46,20 +44,7 @@ NS_ASSUME_NONNULL_BEGIN @param task The task. If `nil`, automatic updating from any previously specified operation will be disabled. */ -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task; -#endif - -///---------------------------------------- -/// @name Refreshing for Request Operations -///---------------------------------------- - -/** - Binds the refreshing state to the execution state of the specified operation. - - @param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled. - */ -- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation; @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.m old mode 100644 new mode 100755 index 4c19245a0cb..aba6d61e020 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIRefreshControl+AFNetworking.m @@ -1,6 +1,6 @@ // UIRefreshControl+AFNetworking.m // -// Copyright (c) 2014 AFNetworking (http://afnetworking.com) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,22 +23,15 @@ #import "UIRefreshControl+AFNetworking.h" #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS -#import "AFHTTPRequestOperation.h" - -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 #import "AFURLSessionManager.h" -#endif @interface AFRefreshControlNotificationObserver : NSObject @property (readonly, nonatomic, weak) UIRefreshControl *refreshControl; - (instancetype)initWithActivityRefreshControl:(UIRefreshControl *)refreshControl; -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task; -#endif -- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation; @end @@ -53,15 +46,9 @@ return notificationObserver; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task { [[self af_notificationObserver] setRefreshingWithStateOfTask:task]; } -#endif - -- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation { - [[self af_notificationObserver] setRefreshingWithStateOfOperation:operation]; -} @end @@ -76,7 +63,6 @@ return self; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; @@ -100,31 +86,6 @@ #pragma clang diagnostic pop } } -#endif - -- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation { - NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; - - [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; - [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; - - if (operation) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wreceiver-is-weak" -#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak" - if (![operation isFinished]) { - if ([operation isExecuting]) { - [self.refreshControl beginRefreshing]; - } else { - [self.refreshControl endRefreshing]; - } - - [notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingOperationDidStartNotification object:operation]; - [notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingOperationDidFinishNotification object:operation]; - } -#pragma clang diagnostic pop - } -} #pragma mark - @@ -151,14 +112,9 @@ - (void)dealloc { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; -#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000 [notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil]; [notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil]; -#endif - - [notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil]; - [notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil]; } @end diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.h b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.h old mode 100644 new mode 100755 index 5d61d6a7406..b9a56af4bf1 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.h +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.h @@ -1,5 +1,5 @@ // UIWebView+AFNetworking.h -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -21,16 +21,15 @@ #import -#import +#import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS #import NS_ASSUME_NONNULL_BEGIN -@class AFHTTPRequestSerializer, AFHTTPResponseSerializer; -@protocol AFURLRequestSerialization, AFURLResponseSerialization; +@class AFHTTPSessionManager; /** This category adds methods to the UIKit framework's `UIWebView` class. The methods in this category provide increased control over the request cycle, including progress monitoring and success / failure handling. @@ -40,25 +39,20 @@ NS_ASSUME_NONNULL_BEGIN @interface UIWebView (AFNetworking) /** - The request serializer used to serialize requests made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPRequestSerializer`. + The session manager used to download all requests. */ -@property (nonatomic, strong) AFHTTPRequestSerializer * requestSerializer; - -/** - The response serializer used to serialize responses made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPResponseSerializer`. - */ -@property (nonatomic, strong) AFHTTPResponseSerializer * responseSerializer; +@property (nonatomic, strong) AFHTTPSessionManager *sessionManager; /** Asynchronously loads the specified request. @param request A URL request identifying the location of the content to load. This must not be `nil`. - @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. + @param progress A progress object monitoring the current download progress. @param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. + @param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. */ - (void)loadRequest:(NSURLRequest *)request - progress:(nullable void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress + progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(nullable void (^)(NSError *error))failure; @@ -68,14 +62,14 @@ NS_ASSUME_NONNULL_BEGIN @param request A URL request identifying the location of the content to load. This must not be `nil`. @param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified. @param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified. - @param progress A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes three arguments: the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the main thread. +@param progress A progress object monitoring the current download progress. @param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data. - @param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. + @param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred. */ - (void)loadRequest:(NSURLRequest *)request MIMEType:(nullable NSString *)MIMEType textEncodingName:(nullable NSString *)textEncodingName - progress:(nullable void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress + progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(nullable void (^)(NSError *error))failure; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.m b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.m old mode 100644 new mode 100755 index 93eacaa0c1b..751c499eab6 --- a/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.m +++ b/ios/SAPTracker/src/xcode/SAPTracker/Libraries/UIKit+AFNetworking/UIWebView+AFNetworking.m @@ -1,5 +1,5 @@ // UIWebView+AFNetworking.m -// Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) +// Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -23,24 +23,24 @@ #import -#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#if TARGET_OS_IOS -#import "AFHTTPRequestOperation.h" +#import "AFHTTPSessionManager.h" #import "AFURLResponseSerialization.h" #import "AFURLRequestSerialization.h" @interface UIWebView (_AFNetworking) -@property (readwrite, nonatomic, strong, setter = af_setHTTPRequestOperation:) AFHTTPRequestOperation *af_HTTPRequestOperation; +@property (readwrite, nonatomic, strong, setter = af_setURLSessionTask:) NSURLSessionDataTask *af_URLSessionTask; @end @implementation UIWebView (_AFNetworking) -- (AFHTTPRequestOperation *)af_HTTPRequestOperation { - return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_HTTPRequestOperation)); +- (NSURLSessionDataTask *)af_URLSessionTask { + return (NSURLSessionDataTask *)objc_getAssociatedObject(self, @selector(af_URLSessionTask)); } -- (void)af_setHTTPRequestOperation:(AFHTTPRequestOperation *)operation { - objc_setAssociatedObject(self, @selector(af_HTTPRequestOperation), operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)af_setURLSessionTask:(NSURLSessionDataTask *)af_URLSessionTask { + objc_setAssociatedObject(self, @selector(af_URLSessionTask), af_URLSessionTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end @@ -49,21 +49,23 @@ @implementation UIWebView (AFNetworking) -- (AFHTTPRequestSerializer *)requestSerializer { - static AFHTTPRequestSerializer *_af_defaultRequestSerializer = nil; +- (AFHTTPSessionManager *)sessionManager { + static AFHTTPSessionManager *_af_defaultHTTPSessionManager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - _af_defaultRequestSerializer = [AFHTTPRequestSerializer serializer]; + _af_defaultHTTPSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; + _af_defaultHTTPSessionManager.requestSerializer = [AFHTTPRequestSerializer serializer]; + _af_defaultHTTPSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer]; }); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" - return objc_getAssociatedObject(self, @selector(requestSerializer)) ?: _af_defaultRequestSerializer; + return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager; #pragma clang diagnostic pop } -- (void)setRequestSerializer:(AFHTTPRequestSerializer *)requestSerializer { - objc_setAssociatedObject(self, @selector(requestSerializer), requestSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager { + objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (AFHTTPResponseSerializer *)responseSerializer { @@ -86,7 +88,7 @@ #pragma mark - - (void)loadRequest:(NSURLRequest *)request - progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress + progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(void (^)(NSError *error))failure { @@ -111,43 +113,44 @@ - (void)loadRequest:(NSURLRequest *)request MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName - progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress + progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(void (^)(NSError *error))failure { NSParameterAssert(request); - if (self.af_HTTPRequestOperation) { - [self.af_HTTPRequestOperation cancel]; + if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) { + [self.af_URLSessionTask cancel]; } - - request = [self.requestSerializer requestBySerializingRequest:request withParameters:nil error:nil]; - - self.af_HTTPRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; - self.af_HTTPRequestOperation.responseSerializer = self.responseSerializer; + self.af_URLSessionTask = nil; __weak __typeof(self)weakSelf = self; - [self.af_HTTPRequestOperation setDownloadProgressBlock:progress]; - [self.af_HTTPRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id __unused responseObject) { - NSData *data = success ? success(operation.response, operation.responseData) : operation.responseData; + NSURLSessionDataTask *dataTask; + dataTask = [self.sessionManager + GET:request.URL.absoluteString + parameters:nil + progress:nil + success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { + __strong __typeof(weakSelf) strongSelf = weakSelf; + if (success) { + success((NSHTTPURLResponse *)task.response, responseObject); + } + [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]]; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wgnu" - __strong __typeof(weakSelf) strongSelf = weakSelf; - [strongSelf loadData:data MIMEType:(MIMEType ?: [operation.response MIMEType]) textEncodingName:(textEncodingName ?: [operation.response textEncodingName]) baseURL:[operation.response URL]]; - - if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) { - [strongSelf.delegate webViewDidFinishLoad:strongSelf]; - } - -#pragma clang diagnostic pop - } failure:^(AFHTTPRequestOperation * __unused operation, NSError *error) { - if (failure) { - failure(error); - } - }]; - - [self.af_HTTPRequestOperation start]; + if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { + [strongSelf.delegate webViewDidFinishLoad:strongSelf]; + } + } + failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) { + if (failure) { + failure(error); + } + }]; + self.af_URLSessionTask = dataTask; + if (progress != nil) { + *progress = [self.sessionManager downloadProgressForTask:dataTask]; + } + [self.af_URLSessionTask resume]; if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) { [self.delegate webViewDidStartLoad:self]; @@ -156,4 +159,4 @@ @end -#endif +#endif \ No newline at end of file diff --git a/ios/SAPTracker/src/xcode/SAPTracker/SignUpRequestManager.swift b/ios/SAPTracker/src/xcode/SAPTracker/SignUpRequestManager.swift index d18c7404ba1..93aed0a40e2 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/SignUpRequestManager.swift +++ b/ios/SAPTracker/src/xcode/SAPTracker/SignUpRequestManager.swift @@ -41,12 +41,12 @@ class SignUpRequestManager: NSObject { } let baseURLString: String - let manager: AFHTTPRequestOperationManager + let manager: AFHTTPSessionManager let sessionManager: AFURLSessionManager init(baseURLString: String = "") { self.baseURLString = baseURLString - manager = AFHTTPRequestOperationManager(baseURL: URL(string: baseURLString)) + manager = AFHTTPSessionManager(baseURL: URL(string: baseURLString)) manager.requestSerializer = AFJSONRequestSerializer() as AFHTTPRequestSerializer manager.requestSerializer.timeoutInterval = Application.RequestTimeout manager.responseSerializer = AFJSONResponseSerializer() as AFHTTPResponseSerializer @@ -65,7 +65,7 @@ class SignUpRequestManager: NSObject { { let urlString = "\(basePathString)/access_token" manager.requestSerializer.setAuthorizationHeaderFieldWithUsername(userName, password: password) - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postAccessTokenSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postAccessTokenFailure(error: error, failure: failure) @@ -113,7 +113,7 @@ class SignUpRequestManager: NSObject { failure: @escaping (_ error: Error, _ message: String?) -> Void) { if let urlString = "\(basePathString)/create_user?username=\(userName)&email=\(email)&fullName=\(fullName)&company=\(company)&password=\(password)".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) { - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postCreateUserSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postCreateUserFailure(error: error, failure: failure) @@ -180,7 +180,7 @@ class SignUpRequestManager: NSObject { success: @escaping () -> Void, failure: @escaping (_ error: Error, _ message: String?) -> Void) { - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postForgotPasswordSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postForgotPasswordFailure(error: error, failure: failure) @@ -209,7 +209,7 @@ class SignUpRequestManager: NSObject { failure: @escaping (_ error: Error, _ message: String?) -> Void) { let urlString = "\(basePathString)/hello" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postHelloSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postHelloFailure(error: error, failure: failure) @@ -254,7 +254,7 @@ class SignUpRequestManager: NSObject { { let urlString = "\(basePathString)/logout/" manager.requestSerializer.clearAuthorizationHeader() - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postLogoutSuccess(success: success) }) { (requestOperation, error) in self.postLogoutFailure(error: error, failure: failure) diff --git a/ios/SAPTracker/src/xcode/SAPTracker/TrainingRequestManager.swift b/ios/SAPTracker/src/xcode/SAPTracker/TrainingRequestManager.swift index be93e846a2d..e4c6a35d977 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/TrainingRequestManager.swift +++ b/ios/SAPTracker/src/xcode/SAPTracker/TrainingRequestManager.swift @@ -19,12 +19,12 @@ class TrainingRequestManager: NSObject { fileprivate let basePathString = "/sailingserver/api/v1" let baseURLString: String - let manager: AFHTTPRequestOperationManager + let manager: AFHTTPSessionManager let sessionManager: AFURLSessionManager init(baseURLString: String = "") { self.baseURLString = baseURLString - manager = AFHTTPRequestOperationManager(baseURL: URL(string: baseURLString)) + manager = AFHTTPSessionManager(baseURL: URL(string: baseURLString)) manager.requestSerializer = AFHTTPRequestSerializer() manager.requestSerializer.timeoutInterval = Application.RequestTimeout manager.responseSerializer = AFHTTPResponseSerializer() @@ -59,7 +59,7 @@ class TrainingRequestManager: NSObject { body["createregatta"] = "true" as AnyObject body["boatclassname"] = boatClassName as AnyObject body["numberofraces"] = "0" as AnyObject - manager.post(urlString, parameters: body, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: body, progress: nil, success: { (requestOperation, responseObject) in self.postCreateEventSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postCreateEventFailure(error: error, failure: failure) @@ -133,7 +133,7 @@ class TrainingRequestManager: NSObject { let encodedRegattaName = regattaName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let encodedCompetitorID = competitorID.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/regattas/\(encodedRegattaName)/competitors/\(encodedCompetitorID)/add" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postRegattaCompetitorAddSuccess(responseObject: responseObject, success: success) }) { (requestOperation, error) in self.postRegattaCompetitorAddFailure(error: error, failure: failure) @@ -165,7 +165,7 @@ class TrainingRequestManager: NSObject { let encodedSailID = sailID.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let encodedNationality = nationality.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/regattas/\(encodedRegattaName)/competitors/createandadd?boatclass=\(encodedBoatClassName)&sailid=\(encodedSailID)&nationalityIOC=\(encodedNationality)" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postRegattaCompetitorCreateAndAddSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postRegattaCompetitorCreateAndAddFailure(error: error, failure: failure) @@ -203,7 +203,7 @@ class TrainingRequestManager: NSObject { { let encodedRegattaName = regattaName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let urlString = "\(basePathString)/regattas/\(encodedRegattaName)/addracecolumns" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postRegattaRaceColumnAddSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postRegattaRaceColumnAddAddFailure(error: error, failure: failure) @@ -280,7 +280,7 @@ class TrainingRequestManager: NSObject { let encodedRaceColumnName = raceColumnName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let encodedFleetName = fleetName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/leaderboards/\(encodedLeaderboardName)/settrackingtimes?race_column=\(encodedRaceColumnName)&fleet=\(encodedFleetName)&\(millisParameter)=\(millisSince1970())" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postLeaderboardRaceSetTrackingTimeMillisSuccess(responseObject: responseObject, success: success) }) { (requestOperation, error) in self.postLeaderboardRaceSetTrackingTimeMillisFailure(error: error, failure: failure) @@ -310,7 +310,7 @@ class TrainingRequestManager: NSObject { let encodedRaceColumnName = raceColumnName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let encodedFleetName = fleetName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/leaderboards/\(encodedLeaderboardName)/starttracking?race_column=\(encodedRaceColumnName)&fleet=\(encodedFleetName)" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postLeaderboardStartTrackingSuccess(responseObject: responseObject, success: success) }) { (requestOperation, error) in self.postLeaderboardStartTrackingFailure(error: error, failure: failure) @@ -366,7 +366,7 @@ class TrainingRequestManager: NSObject { let encodedLeaderboardName = leaderboardName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let encodedQuery = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/leaderboards/\(encodedLeaderboardName)/stoptracking?\(encodedQuery)" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postLeaderboardStopTrackingSuccess(responseObject: responseObject, success: success) }) { (requestOperation, error) in self.postLeaderboardStopTrackingFailure(error: error, failure: failure) @@ -392,7 +392,7 @@ class TrainingRequestManager: NSObject { { let encodedLeaderboardName = leaderboardName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let urlString = "\(basePathString)/leaderboardgroups/\(encodedLeaderboardName)" - manager.get(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.get(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.getLeaderboardGroupSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.getLeaderboardGroupFailure(error: error, failure: failure) @@ -432,7 +432,7 @@ class TrainingRequestManager: NSObject { let encodedRegattaName = regattaName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" let encodedRaceName = raceName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/regattas/\(encodedRegattaName)/races/\(encodedRaceName)/course" - manager.get(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.get(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.getRegattaRaceCourseSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.getRegattaRaceCourseFailure(error: error, failure: failure) @@ -474,7 +474,7 @@ class TrainingRequestManager: NSObject { let encodedRaceColumnName = raceColumnName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let encodedFleetName = fleetName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" let urlString = "\(basePathString)/leaderboards/\(encodedLeaderboardName)/autocourse?race_column=\(encodedRaceColumnName)&fleet=\(encodedFleetName)" - manager.post(urlString, parameters: nil, success: { (requestOperation, responseObject) in + manager.post(urlString, parameters: nil, progress: nil, success: { (requestOperation, responseObject) in self.postLeaderboardAutoCourseSuccess(responseObject: responseObject, success: success, failure: failure) }) { (requestOperation, error) in self.postLeaderboardAutoCourseFailure(error: error, failure: failure) diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java index 9ca74a9fbdf..57e263299b0 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/DashboardClientFactoryImpl.java @@ -1,12 +1,9 @@ package com.sap.sailing.dashboards.gwt.client; -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.sap.sailing.dashboards.gwt.shared.dispatch.DashboardDispatchSystem; import com.sap.sailing.dashboards.gwt.shared.dispatch.impl.DashboardDispatchSystemImpl; -import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sailing.gwt.ui.client.SailingServiceHelper; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; @@ -24,8 +21,7 @@ public class DashboardClientFactoryImpl implements DashboardClientFactory{ public DashboardClientFactoryImpl() { dispatch = new DashboardDispatchSystemImpl(); - sailingService = GWT.create(SailingService.class); - EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, RemoteServiceMappingConstants.sailingServiceRemotePath); + sailingService = SailingServiceHelper.createSailingServiceInstance(false); dashboardFiveSecondsTimer = new Timer(PlayModes.Live); dashboardFiveSecondsTimer.setRefreshInterval(DASHBOARD_RERFRESH_INTERVAL); diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java index 66858859563..497dc402680 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java @@ -163,7 +163,7 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna defaultRaceMapSettings.isShowWindStreamletOverlay(), defaultRaceMapSettings.isShowSimulationOverlay(), defaultRaceMapSettings.isShowMapControls(), defaultRaceMapSettings.getManeuverTypesToShow(), defaultRaceMapSettings.isShowDouglasPeuckerPoints(), defaultRaceMapSettings.isShowEstimatedDuration(), - defaultRaceMapSettings.getStartCountDownFontSizeScaling()); + defaultRaceMapSettings.getStartCountDownFontSizeScaling(), defaultRaceMapSettings.isShowManeuverLossVisualization()); RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync, diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java index 8a30b2eecfe..758c07200c9 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java @@ -26,7 +26,7 @@ public interface HasManeuverContext extends HasManeuver, HasTrackedLegOfCompetit Double getAbsoluteDirectionChangeInDegrees(); @Statistic(messageKey = "ManeuverLoss", resultDecimals = 0, ordinal = 4) - Distance getManeuverLoss(); + Distance getManeuverLossDistanceLost(); @Statistic(messageKey = "WindSpeedVsManeuverLoss", resultDecimals = 2, ordinal = 4) Pair getWindSpeedVsManeuverLoss(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java index f11191a86e7..eff334d61f5 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/CompleteManeuverCurveWithEstimationDataRetrievalProcessor.java @@ -11,8 +11,9 @@ import com.sap.sailing.datamining.impl.data.CompleteManeuverCurveWithEstimationD import com.sap.sailing.datamining.shared.ManeuverSettings; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; -import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport; import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; @@ -45,7 +46,9 @@ public class CompleteManeuverCurveWithEstimationDataRetrievalProcessor extends List result = new ArrayList<>(); TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace(); Competitor competitor = element.getCompetitor(); - ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + ManeuverDetectorWithEstimationDataSupport maneuverDetector = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + new ManeuverDetectorImpl(trackedRace, competitor), + element.getTrackedRaceContext().getLeaderboardContext().getPolarDataService()); Iterable maneuvers = trackedRace.getManeuvers(competitor, false); Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); Iterable maneuversWithEstimationData = maneuverDetector diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java index a11d028e9a0..7dcd5f52e79 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java @@ -107,8 +107,8 @@ public class ManeuverWithContext implements HasManeuverContext { } @Override - public Distance getManeuverLoss() { - return getManeuver().getManeuverLoss(); + public Distance getManeuverLossDistanceLost() { + return getManeuver().getManeuverLoss() == null ? null : getManeuver().getManeuverLoss().getProjectedDistanceLost(); } @Override @@ -147,7 +147,7 @@ public class ManeuverWithContext implements HasManeuverContext { public Pair getWindSpeedVsManeuverLoss(){ Wind wind = getTrackedLegOfCompetitorContext().getTrackedRace().getWind(maneuver.getPosition(), getTimePointBeforeForAnalysis()); - return new Pair<>(wind.getKnots(), getManeuverLoss().getMeters()); + return new Pair<>(wind.getKnots(), getManeuverLossDistanceLost().getMeters()); } @Override diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java index e1b5013233e..e3258e846e6 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java @@ -259,22 +259,29 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { } private int getNumberOf(ManeuverType maneuverType) { - TrackedRace trackedRace = getTrackedRace(); int number = 0; - if (trackedRace != null && trackedRace.getStartOfRace() != null) { - final TimePoint end; - final TimePoint endOfTracking = trackedRace.getEndOfTracking(); - if (trackedRace.getEndOfRace() != null) { - end = trackedRace.getEndOfRace(); + TrackedRace trackedRace = getTrackedRace(); + if (trackedRace != null) { + Course course = trackedRace.getRace().getCourse(); + Waypoint startWaypoint = course.getFirstWaypoint(); + MarkPassing startPassing = trackedRace.getMarkPassing(getCompetitor(), startWaypoint); + TimePoint start = startPassing != null ? startPassing.getTimePoint() : trackedRace.getStartOfRace(); + + Waypoint finishWaypoint = course.getLastWaypoint(); + MarkPassing finishPassing = trackedRace.getMarkPassing(getCompetitor(), finishWaypoint); + TimePoint end; + if (finishPassing != null) { + end = finishPassing.getTimePoint(); } else { - final TimePoint now = MillisecondsTimePoint.now(); - if (endOfTracking != null && endOfTracking.before(now)) { - end = endOfTracking; - } else { - end = now; + end = trackedRace.getEndOfRace(); + if (end == null) { + TimePoint endOfTracking = trackedRace.getEndOfTracking(); + TimePoint now = MillisecondsTimePoint.now(); + end = endOfTracking != null && endOfTracking.before(now) ? endOfTracking : now; } } - for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), trackedRace.getStartOfRace(), end, false)) { + + for (Maneuver maneuver : trackedRace.getManeuvers(getCompetitor(), start, end, false)) { if (maneuver.getType() == maneuverType) { number++; } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java index f0e7b4a0831..3648c6802b7 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java @@ -209,15 +209,8 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti @Override public Boolean isWin() { - Leaderboard leaderboard = getLeaderboard(); final TimePoint now = MillisecondsTimePoint.now(); - double points = leaderboard.getTotalPoints(competitor, raceColumn, now); - if (leaderboard.getScoringScheme().isHigherBetter()) { - double competitorCount = Util.size(leaderboard.getCompetitors()); - return points >= (competitorCount - 0.05); - } else { - return points <= 1.05; - } + return getLeaderboard().isWin(competitor, raceColumn, now); } } 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 98840fcc156..366d8cd72b9 100644 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -17,6 +17,7 @@ Export-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.quadtree, com.sap.sailing.domain.common.quadtree.impl;x-friends:="com.sap.sailing.domain.test", com.sap.sailing.domain.common.racelog, + com.sap.sailing.domain.common.sharding, com.sap.sailing.domain.common.racelog.tracking, com.sap.sailing.domain.common.racelog.utils, com.sap.sailing.domain.common.scalablevalue.impl, diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index 79b6493febb..a5d8736439f 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -31,6 +31,7 @@ public enum BoatClassMasterdata { CANOE_TAIFUN ("Canoe Taifun", true, 5.20, 1.32, BoatHullType.MONOHULL, false, "Taifun", "Taifun Kanu", "Kanu Taifun"), CONTENDER ("Contender", true, 4.88, 1.42, BoatHullType.MONOHULL, false), CC_30 ("C&C 30", true, 9.12, 3.25, BoatHullType.MONOHULL, true), + CLUB_SWAN_50 ("Club Swan 50", true, 16.74, 4.20, BoatHullType.MONOHULL, true, "ClubSwan50"), D_ONE ("D-One", true, 4.23, 2.31, BoatHullType.MONOHULL, true, "Devoti D-One", "DOne", "D_One"), DRAGON_INT ("Dragon Int.", true, 8.89, 1.96, BoatHullType.MONOHULL, true, "Drachen", "Dragon"), DELPHIA_24 ("Delphia 24", true, 7.70, 2.50, BoatHullType.MONOHULL, true, "Delphia 24 One Design", "Delphia 24 OD"), @@ -44,6 +45,7 @@ public enum BoatClassMasterdata { FARR_280 ("Farr 280", true, 8.72, 2.87, BoatHullType.MONOHULL, true, "F280", "F-280", "Farr-280"), FINN ("Finn", true, 4.50, 1.51, BoatHullType.MONOHULL, false), FLYING_DUTCHMAN ("Flying Dutchman", true, 6.10, 1.80, BoatHullType.MONOHULL, true, "FD"), + FLYING_JUNIOR ("Flying Junior", true, 4.03, 1.50, BoatHullType.MONOHULL, true, "FJ"), FLYING_PHANTOM ("Flying Phantom", true, 5.52, 3.00, BoatHullType.CATAMARAN, true), FOLKBOAT ("Folkboat", true, 7.68, 2.20, BoatHullType.MONOHULL, false, "Folke", "Folkeboot"), FUN ("FUN", true, 7.20, 2.45, BoatHullType.MONOHULL, true, "FUN O.D.", "FUN OD", "Open FUN"), @@ -115,6 +117,8 @@ public enum BoatClassMasterdata { TOM_28_MAX ("Tom 28 MAX", true, 8.48, 2.48, BoatHullType.MONOHULL, true, "Tom 28"), TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true), TP52 ("TP52", true, 15.85, 4.35, BoatHullType.MONOHULL, true, "TP 52", "Transpac 52", "Transpac52"), + VARIANTA ("Varianta", true, 6.40, 2.10, BoatHullType.MONOHULL, true), + VAURIEN ("Vaurien", true, 4.08, 1.47, BoatHullType.MONOHULL, true), VENT_D_OUEST ("Vent d'Ouest", true, 5.85, 1.75, BoatHullType.MONOHULL, true, "VENTDOUEST", "VENTD'OUEST"), VIPER_640 ("Viper 640", true, 6.43, 2.49, BoatHullType.MONOHULL, true), VO60 ("VO60", true, 19.5, 5.25, BoatHullType.MONOHULL, true, "Volvo Ocean 60", "VolvoOcean60", "W60", "Whitbread60", "Whitbread 60"), diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java index 4382e6d5fcd..530eb891855 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java @@ -5,10 +5,9 @@ import static com.sap.sse.common.SortingOrder.DESCENDING; import static com.sap.sse.common.SortingOrder.NONE; import java.io.Serializable; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.List; +import java.util.LinkedHashSet; import com.sap.sse.common.SortingOrder; @@ -246,11 +245,11 @@ public enum DetailType implements Serializable { } /** - * Special List of DetailTypes, that allows operators to select for example the RideHeight, that is usually only - * selectable, if it already has data. + * Special collection of {@link DetailType}s, that allows operators to select for example the RideHeight, that is + * usually only selectable, if it already has data. */ - public static List getAutoplayDetailTypesForChart() { - List availableDetailsTypes = new ArrayList<>(); + public static Collection getAutoplayDetailTypesForChart() { + final Collection availableDetailsTypes = new LinkedHashSet<>(); availableDetailsTypes.add(DetailType.CHART_WINDWARD_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD); availableDetailsTypes.add(DetailType.LEG_CURRENT_SPEED_OVER_GROUND_IN_KNOTS); availableDetailsTypes.add(DetailType.LEG_DISTANCE_TRAVELED); @@ -268,8 +267,8 @@ public enum DetailType implements Serializable { return availableDetailsTypes; } - public static List getRaceExpeditionDetailTypes() { - ArrayList allowed = new ArrayList<>(); + public static Collection getRaceExpeditionDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); allowed.add(EXPEDITION_RACE_AWA); allowed.add(EXPEDITION_RACE_AWS); allowed.add(EXPEDITION_RACE_TWA); @@ -312,15 +311,8 @@ public enum DetailType implements Serializable { return allowed; } - public static List getLegExtendedBravoDetailTypes() { - ArrayList allowed = new ArrayList<>(); - allowed.add(DetailType.BRAVOEXTENDED_LEG_CURRENT_DISTANCE_FOILED_IN_METERS); - allowed.add(DetailType.BRAVOEXTENDED_LEG_CURRENT_DURATION_FOILED_IN_SECONDS); - return allowed; - } - - public static List getRaceExtendedBravoDetailTypes() { - ArrayList allowed = new ArrayList<>(); + public static Collection getRaceExtendedBravoDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); allowed.add(DetailType.BRAVOEXTENDED_RACE_CURRENT_PORT_DAGGERBOARD_RAKE); allowed.add(DetailType.BRAVOEXTENDED_RACE_CURRENT_STBD_DAGGERBOARD_RAKE); allowed.add(DetailType.BRAVOEXTENDED_RACE_CURRENT_PORT_RUDDER_RAKE); @@ -342,24 +334,28 @@ public enum DetailType implements Serializable { return allowed; } - public static List getRaceBravoDetailTypes() { - ArrayList allowed = new ArrayList<>(); + public static Collection getRaceBravoDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); allowed.add(DetailType.BRAVO_RACE_HEEL_IN_DEGREES); allowed.add(DetailType.BRAVO_RACE_PITCH_IN_DEGREES); allowed.add(DetailType.BRAVO_RACE_CURRENT_RIDE_HEIGHT_IN_METERS); + allowed.add(DetailType.RACE_CURRENT_DISTANCE_FOILED_IN_METERS); + allowed.add(DetailType.RACE_CURRENT_DURATION_FOILED_IN_SECONDS); return allowed; } - public static List getLegBravoDetailTypes() { - ArrayList allowed = new ArrayList<>(); + public static Collection getLegBravoDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); allowed.add(DetailType.BRAVO_LEG_CURRENT_HEEL_IN_DEGREES); allowed.add(DetailType.BRAVO_LEG_CURRENT_PITCH_IN_DEGREES); allowed.add(DetailType.BRAVO_LEG_CURRENT_RIDE_HEIGHT_IN_METERS); + allowed.add(DetailType.BRAVOEXTENDED_LEG_CURRENT_DISTANCE_FOILED_IN_METERS); + allowed.add(DetailType.BRAVOEXTENDED_LEG_CURRENT_DURATION_FOILED_IN_SECONDS); return allowed; } - public static List getAllRaceDetailTypes() { - ArrayList allowed = new ArrayList<>(); + public static Collection getAllRaceDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); allowed.add(RACE_GAP_TO_LEADER_IN_SECONDS); allowed.add(RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS); allowed.add(RACE_DISTANCE_TRAVELED); @@ -368,7 +364,6 @@ public enum DetailType implements Serializable { allowed.add(RACE_CALCULATED_TIME_TRAVELED); allowed.add(RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD); allowed.add(RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS); - allowed.add(BRAVO_RACE_CURRENT_RIDE_HEIGHT_IN_METERS); allowed.add(RACE_CURRENT_DISTANCE_FOILED_IN_METERS); allowed.add(RACE_CURRENT_DURATION_FOILED_IN_SECONDS); allowed.add(RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS); @@ -390,7 +385,7 @@ public enum DetailType implements Serializable { * @return */ public static Collection getAllNonRestrictedDetailTypes() { - List all = new ArrayList<>(Arrays.asList(values())); + final Collection all = new LinkedHashSet<>(Arrays.asList(values())); all.removeAll(getRaceBravoDetailTypes()); all.removeAll(getLegBravoDetailTypes()); all.removeAll(getRaceExtendedBravoDetailTypes()); @@ -400,83 +395,150 @@ public enum DetailType implements Serializable { return all; } - public static List getAvailableOverallDetailColumnTypes() { - return Arrays.asList(new DetailType[] { DetailType.REGATTA_RANK, DetailType.OVERALL_TOTAL_DISTANCE_TRAVELED, - DetailType.OVERALL_TOTAL_AVERAGE_SPEED_OVER_GROUND, DetailType.OVERALL_TOTAL_TIME_SAILED_IN_SECONDS, - DetailType.OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, DetailType.OVERALL_TIME_ON_TIME_FACTOR, - DetailType.OVERALL_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE, - DetailType.OVERALL_TOTAL_SCORED_RACE_COUNT, TOTAL_TIME_SAILED_UPWIND_IN_SECONDS, - TOTAL_TIME_SAILED_REACHING_IN_SECONDS, TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS }); + public static Collection getAvailableOverallDetailColumnTypes() { + final Collection allowed = new LinkedHashSet<>(); + allowed.add(REGATTA_RANK); + allowed.add(OVERALL_TOTAL_DISTANCE_TRAVELED); + allowed.add(OVERALL_TOTAL_AVERAGE_SPEED_OVER_GROUND); + allowed.add(OVERALL_TOTAL_TIME_SAILED_IN_SECONDS); + allowed.add(OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS); + allowed.add(OVERALL_TIME_ON_TIME_FACTOR); + allowed.add(OVERALL_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE); + allowed.add(OVERALL_TOTAL_SCORED_RACE_COUNT); + allowed.add(TOTAL_TIME_SAILED_UPWIND_IN_SECONDS); + allowed.add(TOTAL_TIME_SAILED_REACHING_IN_SECONDS); + allowed.add(TOTAL_TIME_SAILED_DOWNWIND_IN_SECONDS); + return allowed; } - public static List getOverallBravoDetailTypes() { - return Arrays.asList(new DetailType[] { DetailType.OVERALL_TOTAL_DISTANCE_FOILED_IN_METERS, - OVERALL_TOTAL_DURATION_FOILED_IN_SECONDS }); + public static Collection getOverallBravoDetailTypes() { + final Collection allowed = new LinkedHashSet<>(); + allowed.add(OVERALL_TOTAL_DISTANCE_FOILED_IN_METERS); + allowed.add(OVERALL_TOTAL_DURATION_FOILED_IN_SECONDS); + return allowed; } - public static List getLegDetailColumnTypes() { - return Arrays.asList(new DetailType[] { DetailType.LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, - DetailType.LEG_DISTANCE_TRAVELED, DetailType.LEG_DISTANCE_TRAVELED_INCLUDING_GATE_START, - DetailType.LEG_GAP_TO_LEADER_IN_SECONDS, DetailType.LEG_GAP_CHANGE_SINCE_LEG_START_IN_SECONDS, - DetailType.LEG_SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED, - DetailType.LEG_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, DetailType.LEG_WINDWARD_DISTANCE_TO_GO_IN_METERS, - DetailType.NUMBER_OF_MANEUVERS, DetailType.LEG_ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS, - DetailType.LEG_VELOCITY_MADE_GOOD_IN_KNOTS, DetailType.LEG_TIME_TRAVELED, - DetailType.LEG_CORRECTED_TIME_TRAVELED, DetailType.LEG_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, - DetailType.LEG_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, DetailType.LEG_RANK_GAIN }); + public static Collection getLegDetailColumnTypes() { + final Collection allowed = new LinkedHashSet<>(); + allowed.add(LEG_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS); + allowed.add(LEG_DISTANCE_TRAVELED); + allowed.add(LEG_DISTANCE_TRAVELED_INCLUDING_GATE_START); + allowed.add(LEG_GAP_TO_LEADER_IN_SECONDS); + allowed.add(LEG_GAP_CHANGE_SINCE_LEG_START_IN_SECONDS); + allowed.add(LEG_SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED); + allowed.add(LEG_CURRENT_SPEED_OVER_GROUND_IN_KNOTS); + allowed.add(LEG_WINDWARD_DISTANCE_TO_GO_IN_METERS); + allowed.add(NUMBER_OF_MANEUVERS); + allowed.add(LEG_ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS); + allowed.add(LEG_VELOCITY_MADE_GOOD_IN_KNOTS); + allowed.add(LEG_TIME_TRAVELED); + allowed.add(LEG_CORRECTED_TIME_TRAVELED); + allowed.add(LEG_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS); + allowed.add(LEG_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS); + allowed.add(LEG_RANK_GAIN); + return allowed; } - public static List getRaceStartAnalysisColumnTypes() { - return Arrays.asList(new DetailType[] { DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START, - DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, DetailType.DISTANCE_TO_START_AT_RACE_START, - DetailType.TIME_BETWEEN_RACE_START_AND_COMPETITOR_START, DetailType.SPEED_OVER_GROUND_AT_RACE_START, - DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START, - DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS, - DetailType.START_TACK }); + public static Collection getRaceStartAnalysisColumnTypes() { + final Collection allowed = new LinkedHashSet<>(); + allowed.add(RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START); + allowed.add(RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START); + allowed.add(DISTANCE_TO_START_AT_RACE_START); + allowed.add(TIME_BETWEEN_RACE_START_AND_COMPETITOR_START); + allowed.add(SPEED_OVER_GROUND_AT_RACE_START); + allowed.add(SPEED_OVER_GROUND_WHEN_PASSING_START); + allowed.add(DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS); + allowed.add(START_TACK); + return allowed; } - public static List getLegExpeditionDetailColumnTypes() { - return Arrays.asList(new DetailType[] { EXPEDITION_LEG_AWA, EXPEDITION_LEG_AWS, EXPEDITION_LEG_TWA, - EXPEDITION_LEG_TWS, EXPEDITION_LEG_TWD, EXPEDITION_LEG_TARG_TWA, EXPEDITION_LEG_BOAT_SPEED, - EXPEDITION_LEG_TARG_BOAT_SPEED, EXPEDITION_LEG_SOG, EXPEDITION_LEG_COG, EXPEDITION_LEG_FORESTAY_LOAD, - EXPEDITION_LEG_RAKE, EXPEDITION_LEG_COURSE, EXPEDITION_LEG_HEADING, EXPEDITION_LEG_VMG, - EXPEDITION_LEG_VMG_TARG_VMG_DELTA, EXPEDITION_LEG_RATE_OF_TURN, EXPEDITION_LEG_RUDDER_ANGLE, - EXPEDITION_LEG_TARGET_HEEL, EXPEDITION_LEG_TIME_TO_PORT_LAYLINE, - EXPEDITION_LEG_TIME_TO_STB_LAYLINE, EXPEDITION_LEG_DIST_TO_PORT_LAYLINE, - EXPEDITION_LEG_DIST_TO_STB_LAYLINE, EXPEDITION_LEG_TIME_TO_GUN, EXPEDITION_LEG_TIME_TO_COMMITTEE_BOAT, - EXPEDITION_LEG_TIME_TO_PIN, EXPEDITION_LEG_TIME_TO_BURN_TO_LINE, - EXPEDITION_LEG_TIME_TO_BURN_TO_COMMITTEE_BOAT, EXPEDITION_LEG_TIME_TO_BURN_TO_PIN, - EXPEDITION_LEG_DISTANCE_TO_COMMITTEE_BOAT, EXPEDITION_LEG_DISTANCE_TO_PIN, - EXPEDITION_LEG_DISTANCE_BELOW_LINE, EXPEDITION_LEG_LINE_SQUARE_FOR_WIND_DIRECTION, EXPEDITION_LEG_BARO, - EXPEDITION_LEG_LOAD_S, EXPEDITION_LEG_LOAD_P, EXPEDITION_LEG_JIB_CAR_PORT, EXPEDITION_LEG_JIB_CAR_STBD, - EXPEDITION_LEG_MAST_BUTT }); + public static Collection getLegExpeditionDetailColumnTypes() { + final Collection allowed = new LinkedHashSet<>(); + allowed.add(EXPEDITION_LEG_AWA); + allowed.add(EXPEDITION_LEG_AWS); + allowed.add(EXPEDITION_LEG_TWA); + allowed.add(EXPEDITION_LEG_TWS); + allowed.add(EXPEDITION_LEG_TWD); + allowed.add(EXPEDITION_LEG_TARG_TWA); + allowed.add(EXPEDITION_LEG_BOAT_SPEED); + allowed.add(EXPEDITION_LEG_TARG_BOAT_SPEED); + allowed.add(EXPEDITION_LEG_SOG); + allowed.add(EXPEDITION_LEG_COG); + allowed.add(EXPEDITION_LEG_FORESTAY_LOAD); + allowed.add(EXPEDITION_LEG_RAKE); + allowed.add(EXPEDITION_LEG_COURSE); + allowed.add(EXPEDITION_LEG_HEADING); + allowed.add(EXPEDITION_LEG_VMG); + allowed.add(EXPEDITION_LEG_VMG_TARG_VMG_DELTA); + allowed.add(EXPEDITION_LEG_RATE_OF_TURN); + allowed.add(EXPEDITION_LEG_RUDDER_ANGLE); + allowed.add(EXPEDITION_LEG_TARGET_HEEL); + allowed.add(EXPEDITION_LEG_TIME_TO_PORT_LAYLINE); + allowed.add(EXPEDITION_LEG_TIME_TO_STB_LAYLINE); + allowed.add(EXPEDITION_LEG_DIST_TO_PORT_LAYLINE); + allowed.add(EXPEDITION_LEG_DIST_TO_STB_LAYLINE); + allowed.add(EXPEDITION_LEG_TIME_TO_GUN); + allowed.add(EXPEDITION_LEG_TIME_TO_COMMITTEE_BOAT); + allowed.add(EXPEDITION_LEG_TIME_TO_PIN); + allowed.add(EXPEDITION_LEG_TIME_TO_BURN_TO_LINE); + allowed.add(EXPEDITION_LEG_TIME_TO_BURN_TO_COMMITTEE_BOAT); + allowed.add(EXPEDITION_LEG_TIME_TO_BURN_TO_PIN); + allowed.add(EXPEDITION_LEG_DISTANCE_TO_COMMITTEE_BOAT); + allowed.add(EXPEDITION_LEG_DISTANCE_TO_PIN); + allowed.add(EXPEDITION_LEG_DISTANCE_BELOW_LINE); + allowed.add(EXPEDITION_LEG_LINE_SQUARE_FOR_WIND_DIRECTION); + allowed.add(EXPEDITION_LEG_BARO); + allowed.add(EXPEDITION_LEG_LOAD_S); + allowed.add(EXPEDITION_LEG_LOAD_P); + allowed.add(EXPEDITION_LEG_JIB_CAR_PORT); + allowed.add(EXPEDITION_LEG_JIB_CAR_STBD); + allowed.add(EXPEDITION_LEG_MAST_BUTT); + return allowed; } /** * Not yet implemented types can be added here, they will be hidden from any user under all circumstances */ - public static List getDisabledDetailColumTypes() { - return Arrays.asList(new DetailType[] { EXPEDITION_LEG_TARG_TWA, EXPEDITION_LEG_VMG, - EXPEDITION_LEG_VMG_TARG_VMG_DELTA, EXPEDITION_LEG_RATE_OF_TURN, EXPEDITION_LEG_RUDDER_ANGLE, - EXPEDITION_LEG_TIME_TO_PORT_LAYLINE, EXPEDITION_LEG_TIME_TO_STB_LAYLINE, - EXPEDITION_LEG_DIST_TO_PORT_LAYLINE, EXPEDITION_LEG_DIST_TO_STB_LAYLINE, - EXPEDITION_LEG_TIME_TO_COMMITTEE_BOAT, EXPEDITION_LEG_TIME_TO_PIN, EXPEDITION_LEG_TIME_TO_BURN_TO_COMMITTEE_BOAT, - EXPEDITION_LEG_TIME_TO_BURN_TO_PIN, EXPEDITION_LEG_DISTANCE_TO_COMMITTEE_BOAT, - EXPEDITION_LEG_DISTANCE_TO_PIN, EXPEDITION_LEG_LINE_SQUARE_FOR_WIND_DIRECTION, EXPEDITION_RACE_TARG_TWA, - EXPEDITION_RACE_VMG, EXPEDITION_RACE_VMG_TARG_VMG_DELTA, - EXPEDITION_RACE_RUDDER_ANGLE, EXPEDITION_RACE_TIME_TO_PORT_LAYLINE, EXPEDITION_RACE_TIME_TO_STB_LAYLINE, - EXPEDITION_RACE_DIST_TO_PORT_LAYLINE, EXPEDITION_RACE_DIST_TO_STB_LAYLINE, - EXPEDITION_RACE_TIME_TO_COMMITTEE_BOAT, EXPEDITION_RACE_TIME_TO_PIN, - EXPEDITION_RACE_TIME_TO_BURN_TO_PIN, EXPEDITION_RACE_TIME_TO_BURN_TO_COMMITTEE_BOAT, - EXPEDITION_RACE_DISTANCE_TO_COMMITTEE_BOAT, EXPEDITION_RACE_DISTANCE_TO_PIN, - EXPEDITION_RACE_LINE_SQUARE_FOR_WIND_DIRECTION }); + public static Collection getDisabledDetailColumTypes() { + final Collection disabled = new LinkedHashSet<>(); + disabled.add(EXPEDITION_LEG_TARG_TWA); + disabled.add(EXPEDITION_LEG_VMG); + disabled.add(EXPEDITION_LEG_VMG_TARG_VMG_DELTA); + disabled.add(EXPEDITION_LEG_RATE_OF_TURN); + disabled.add(EXPEDITION_LEG_RUDDER_ANGLE); + disabled.add(EXPEDITION_LEG_TIME_TO_PORT_LAYLINE); + disabled.add(EXPEDITION_LEG_TIME_TO_STB_LAYLINE); + disabled.add(EXPEDITION_LEG_DIST_TO_PORT_LAYLINE); + disabled.add(EXPEDITION_LEG_DIST_TO_STB_LAYLINE); + disabled.add(EXPEDITION_LEG_TIME_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_LEG_TIME_TO_PIN); + disabled.add(EXPEDITION_LEG_TIME_TO_BURN_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_LEG_TIME_TO_BURN_TO_PIN); + disabled.add(EXPEDITION_LEG_DISTANCE_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_LEG_DISTANCE_TO_PIN); + disabled.add(EXPEDITION_LEG_LINE_SQUARE_FOR_WIND_DIRECTION); + disabled.add(EXPEDITION_RACE_TARG_TWA); + disabled.add(EXPEDITION_RACE_VMG); + disabled.add(EXPEDITION_RACE_VMG_TARG_VMG_DELTA); + disabled.add(EXPEDITION_RACE_RUDDER_ANGLE); + disabled.add(EXPEDITION_RACE_TIME_TO_PORT_LAYLINE); + disabled.add(EXPEDITION_RACE_TIME_TO_STB_LAYLINE); + disabled.add(EXPEDITION_RACE_DIST_TO_PORT_LAYLINE); + disabled.add(EXPEDITION_RACE_DIST_TO_STB_LAYLINE); + disabled.add(EXPEDITION_RACE_TIME_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_RACE_TIME_TO_PIN); + disabled.add(EXPEDITION_RACE_TIME_TO_BURN_TO_PIN); + disabled.add(EXPEDITION_RACE_TIME_TO_BURN_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_RACE_DISTANCE_TO_COMMITTEE_BOAT); + disabled.add(EXPEDITION_RACE_DISTANCE_TO_PIN); + disabled.add(EXPEDITION_RACE_LINE_SQUARE_FOR_WIND_DIRECTION); + return disabled; } - public static List getAllLegDetailColumnTypes() { - ArrayList all = new ArrayList<>(); + public static Collection getAllLegDetailColumnTypes() { + final Collection all = new LinkedHashSet<>(); all.addAll(getLegDetailColumnTypes()); all.addAll(getLegBravoDetailTypes()); - all.addAll(getLegExtendedBravoDetailTypes()); all.addAll(getLegExpeditionDetailColumnTypes()); return all; } @@ -500,11 +562,10 @@ public enum DetailType implements Serializable { return t; } } - } } } throw new IllegalArgumentException("Could not restore " + value + " to an DetailType enum"); } -} \ No newline at end of file +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java index c419b8d1bd1..c90803d5eda 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java @@ -5,5 +5,5 @@ public enum ScoringSchemeType { LOW_POINT, LOW_POINT_WITH_AUTOMATIC_RDG, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_WINNER_GETS_EIGHT, HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT_2017, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07, - LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY + LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY, LOW_POINT_FIRST_TO_WIN_TWO_RACES } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sharding/ShardingType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sharding/ShardingType.java new file mode 100644 index 00000000000..1ed04cadfaa --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sharding/ShardingType.java @@ -0,0 +1,61 @@ +package com.sap.sailing.domain.common.sharding; + +/** + * Enum that defines all sharding types known by the system. + * + * For a sharding to work, we need both client and backend properly identifying a shard. + * + * While the client has to properly deliver sharding information on each request, the server side + * infrastructure must be able to identify the information and properly route the requests. + * + * The current implementation uses a SUFFIX appended the GWT RPC URLs (allowing path based routing), so that each call to the server + * propagates the sharding information through the url. + * + * A load balancer can be used to properly route each request to the corresponding server node. The first + * sharding implemented is the sharding by leaderboard name. + * + * The suffix appended to the request URL consists of two parts: a prefix that is used to identify the type of shard + * and a payload that will be used to identify the shard. + * + * + */ +public enum ShardingType { + LEADERBOARDNAME("/leaderboard/"); + + private String prefix; + + private ShardingType(String prefix) { + this.prefix = prefix; + } + + public String encodeIfNeeded(String shardingInfo) { + if (shardingInfo.startsWith(prefix)) { + return shardingInfo; + } + return new StringBuilder().append(prefix) + .append(normalize(shardingInfo)) + .toString(); + } + + private String normalize(String replace) { + char[] chars = replace.toCharArray(); + StringBuilder answer = new StringBuilder(); + for(int i = 0;i 47 && c < 58) { // digits + answer.append(c); + } else if (c > 64 && c < 91) { // uppercase letters + answer.append(c); + } else if (c > 69 && c < 123) { // lowercase letters + answer.append(c); + } else { + answer.append("_"); + } + } + return answer.toString(); + } + + public String getPrefix() { + return prefix; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java index cc14f625de5..6b6868ad95e 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java @@ -38,7 +38,7 @@ public class PreciseCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingI private double cachedEstimatedSpeedInKnots; - private class PreciseCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing implements SpeedWithBearing { + public class PreciseCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing implements SpeedWithBearing { private static final long serialVersionUID = 1802065090733146728L; @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java index 77c271e87c2..287cf22c770 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java @@ -72,7 +72,7 @@ public class VeryCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingImpl } } - private class VeryCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing { + public class VeryCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing { private static final long serialVersionUID = 1802065090733146728L; @Override @@ -104,7 +104,7 @@ public class VeryCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingImpl } } - private class VeryCompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl { + public class VeryCompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl { private static final long serialVersionUID = -5871855443391817248L; @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/AvailableWindFinderSpotCollections.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/AvailableWindFinderSpotCollections.java new file mode 100755 index 00000000000..992525c3dde --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/AvailableWindFinderSpotCollections.java @@ -0,0 +1,42 @@ +package com.sap.sailing.domain.common.windfinder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public enum AvailableWindFinderSpotCollections { + KIELERFOERDE("kielerfoerde"), + CHIEMSEE("chiemsee"), + STARNBERGERSEE("starnbergersee"), + WANNSEE("wannsee"), + TRAVEMUENDE("travemuende"), + BALTIC_OFFSHORE("baltic_offshore"), + LITAUEN("litauen"), + HAMBURG_ALSTER("hamburg_alster"), + GDANSK("gdansk"), + BREST("brest"), + SZCZECIN("szczecin"), + SANKT_PETERSBURG("sankt_petersburg"), + SANKT_MORITZ("sankt_moritz"), + PORTO_CERVO("porto_cervo"), + MUEGGELSEE("mueggelsee"); + + private final String name; + + private AvailableWindFinderSpotCollections(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static List getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder() { + List result = new ArrayList<>(); + for (AvailableWindFinderSpotCollections awsc : values()) { + result.add(awsc.getName()); + } + Collections.sort(result); + return result; + } +} diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java index b1baf2b4f61..32e38f5afd9 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java @@ -619,9 +619,10 @@ public class FixLoaderAndTracker implements TrackingDataLoader { private void startTracking() { setStatusAndProgress(TrackedRaceStatusEnum.TRACKING, 0.0); - trackedRace.addListener(raceChangeListener); this.deviceMappings = new FixLoaderDeviceMappings(trackedRace.getAttachedRegattaLogs(), trackedRace.getRace().getName()); + trackedRace.addListener(raceChangeListener); + this.deviceMappings.updateMappings(); } private void loadFixesForExtendedTimeRange(final TimeRange extendedTimeRange) { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java index bcf7c25dc7c..9ccc123f26d 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java @@ -146,7 +146,7 @@ public abstract class RegattaLogDeviceMappings { }); } - private void updateMappings() { + public void updateMappings() { try { updateMappingsInternal(); } catch (Exception e) { 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 index 1828d8c0ef1..2819e8ee4e3 100644 --- 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 @@ -10,6 +10,7 @@ import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; import com.sap.sailing.domain.base.configuration.procedures.SWCStartConfiguration; import com.sap.sailing.domain.common.CourseDesignerMode; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sse.common.Duration; /** * Interface holding configuration options for a Regatta, including configurations for @@ -26,6 +27,12 @@ public interface RegattaConfiguration extends Serializable, Cloneable { * Gets the default {@link RacingProcedureType} for races configured. */ RacingProcedureType getDefaultRacingProcedureType(); + + /** + * Gets the default protest time duration. May be {@code null} in case no default is set for the + * regatta. + */ + Duration getDefaultProtestTimeDuration(); /** * Gets the configuration for RRS26 races. 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 index 4b39cbeed7f..0363327b55b 100644 --- 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 @@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.configuration.ConfigurationLoader; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.common.CourseDesignerMode; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sse.common.Duration; /** * Is empty, does nothing on store and load. @@ -17,6 +18,7 @@ public class EmptyRegattaConfiguration extends RegattaConfigurationImpl implemen public RegattaConfiguration load() { setDefaultRacingProcedureType(RacingProcedureType.UNKNOWN); setDefaultCourseDesignerMode(CourseDesignerMode.UNKNOWN); + setDefaultProtestTimeDuration(Duration.ONE_MINUTE.times(90)); setRRS26Configuration(new RRS26ConfigurationImpl()); setSWCStartConfiguration(new SWCStartConfigurationImpl()); setGateStartConfiguration(new GateStartConfigurationImpl()); 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 index 5810aa96e41..55c1f89b320 100644 --- 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 @@ -9,6 +9,7 @@ import com.sap.sailing.domain.base.configuration.procedures.RRS26Configuration; import com.sap.sailing.domain.base.configuration.procedures.SWCStartConfiguration; import com.sap.sailing.domain.common.CourseDesignerMode; import com.sap.sailing.domain.common.racelog.RacingProcedureType; +import com.sap.sse.common.Duration; public class RegattaConfigurationImpl implements RegattaConfiguration { @@ -16,6 +17,7 @@ public class RegattaConfigurationImpl implements RegattaConfiguration { private RacingProcedureType defaultRacingProcedureType; private CourseDesignerMode defaultCourseDesignerMode; + private Duration defaultProtestTimeDuration; private RRS26Configuration rrs26Configuration; private SWCStartConfiguration swcStartConfiguration; @@ -42,6 +44,15 @@ public class RegattaConfigurationImpl implements RegattaConfiguration { defaultCourseDesignerMode = mode; } + @Override + public Duration getDefaultProtestTimeDuration() { + return defaultProtestTimeDuration; + } + + public void setDefaultProtestTimeDuration(Duration defaultProtestTimeDuration) { + this.defaultProtestTimeDuration = defaultProtestTimeDuration; + } + @Override public RRS26Configuration getRRS26Configuration() { return rrs26Configuration; @@ -101,6 +112,7 @@ public class RegattaConfigurationImpl implements RegattaConfiguration { RegattaConfigurationImpl copy = new RegattaConfigurationImpl(); copy.setDefaultRacingProcedureType(defaultRacingProcedureType); copy.setDefaultCourseDesignerMode(defaultCourseDesignerMode); + copy.setDefaultProtestTimeDuration(defaultProtestTimeDuration); copy.setRRS26Configuration(rrs26Configuration); copy.setSWCStartConfiguration(swcStartConfiguration); copy.setGateStartConfiguration(gateStartConfiguration); @@ -120,6 +132,9 @@ public class RegattaConfigurationImpl implements RegattaConfiguration { if (update.getDefaultRacingProcedureType() != null) { target.setDefaultRacingProcedureType(update.getDefaultRacingProcedureType()); } + if (update.getDefaultProtestTimeDuration() != null) { + target.setDefaultProtestTimeDuration(update.getDefaultProtestTimeDuration()); + } if (update.getRRS26Configuration() != null) { target.setRRS26Configuration( (RRS26Configuration) target.getRRS26Configuration().merge(update.getRRS26Configuration())); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java index 269a2f3461c..82f3d8f636f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TeamImpl.java @@ -5,6 +5,7 @@ import java.io.ObjectInputStream; import java.net.URI; import java.util.HashSet; import java.util.Set; +import java.util.logging.Logger; import com.sap.sailing.domain.base.Nationality; import com.sap.sailing.domain.base.NationalityChangeListener; @@ -15,6 +16,7 @@ import com.sap.sse.common.impl.NamedImpl; public class TeamImpl extends NamedImpl implements DynamicTeam { private static final long serialVersionUID = 4646922280429210183L; + private static final Logger logger = Logger.getLogger(TeamImpl.class.getName()); private final Iterable sailors; private final DynamicPerson coach; private final NationalityChangeListener personNationalityChangeForwarder; @@ -116,14 +118,19 @@ public class TeamImpl extends NamedImpl implements DynamicTeam { public void setNationality(Nationality newNationality) { Nationality oldNationality = getNationality(); if (!Util.equalsWithNull(oldNationality, newNationality)) { - for (Person sailor : getSailors()) { - ((DynamicPerson) sailor).setNationality(newNationality); - } - if (getCoach() != null) { - getCoach().setNationality(newNationality); - } - for (NationalityChangeListener listener : getNationalityChangeListeners()) { - listener.nationalityChanged(this, oldNationality, newNationality); + if (Util.isEmpty(getSailors()) && getCoach() == null) { + logger.warning("Would like to update nationality of team "+this+ + " to "+newNationality+" but cannot because neither sailors nor coach are set"); + } else { + for (Person sailor : getSailors()) { + ((DynamicPerson) sailor).setNationality(newNationality); + } + if (getCoach() != null) { + getCoach().setNationality(newNationality); + } + for (NationalityChangeListener listener : getNationalityChangeListeners()) { + listener.nationalityChanged(this, oldNationality, newNationality); + } } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java index de43e8d22f7..b4ed5ace655 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java @@ -144,7 +144,7 @@ public class TimeRangeCache { // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry // modifies the internal parts of the data structure which is not thread safe. - synchronized (lruCache) { + synchronized (lruCache) { // ping the "perfect match" although it may not even have existed in the cache lruCache.get(new Util.Pair(from, to)); } return result; 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 4f6a752bbcc..0811adebd09 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 @@ -506,12 +506,14 @@ public class TrackImpl implements Track { result = nullElement; } } + // run the cache update while still holding the read lock; this avoids bug4629 where a cache invalidation + // caused by fix insertions can come after the result calculation and before the cache update + if (!perfectCacheHit && recursionDepth == 0) { + cache.cache(from, to, result); + } } finally { unlockAfterRead(); } - if (!perfectCacheHit && recursionDepth == 0) { - cache.cache(from, to, result); - } } return result; } 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 8c8a96f8a0c..5c493877887 100644 --- 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 @@ -127,10 +127,16 @@ public class DomainFactoryImpl implements DomainFactory { CompetitorWithBoat domainCompetitor = competitorAndBoatStore.getExistingCompetitorWithBoatById(competitorId); if (domainCompetitor == null || competitorAndBoatStore.isCompetitorToUpdateDuringGetOrCreate(domainCompetitor)) { List teamMembers = new ArrayList(); - for (CrewMember crewMember: competitor.getCrew()) { - DynamicPerson person = new PersonImpl(crewMember.getName().trim(), getOrCreateNationality(crewMember.getNationality()), - /* dateOfBirth */ null, crewMember.getPosition()); - teamMembers.add(person); + if (competitor.getCrew().isEmpty()) { + DynamicPerson dummyPerson = new PersonImpl(competitor.getName().trim(), getOrCreateNationality(competitor.getThreeLetterIOCCode()), + /* dateOfBirth */ null, /* description */ "Team"); + teamMembers.add(dummyPerson); + } else { + for (CrewMember crewMember: competitor.getCrew()) { + DynamicPerson person = new PersonImpl(crewMember.getName().trim(), getOrCreateNationality(crewMember.getNationality()), + /* dateOfBirth */ null, crewMember.getPosition()); + teamMembers.add(person); + } } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); final DynamicBoat domainBoat = competitorAndBoatStore.getOrCreateBoat(competitorId, /* name */ null, boatClass, competitor.getBoatID(), /* color */ null); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java index 462b90ca2fe..9d386a29c6b 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java @@ -120,9 +120,7 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { person.getGivenName() + " " + person.getFamilyName(), person.getNOC().name(), crewMember.getPosition().name())); } - Crew firstCrewMember = team.getCrew().get(0); - Person person = persons.get(firstCrewMember.getPersonID()); - String nationality = person.getNOC().name(); + String nationality = team.getNOC().name(); CompetitorWithID competitor = new CompetitorWithID(team.getTeamID(), boat.getSailNumber(), nationality, team.getTeamName(), crew); competitors.add(competitor); 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 2a32b9987da..7e6ebc98f4d 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 @@ -101,6 +101,7 @@ public abstract class AbstractTracTracLiveTest extends StoredTrackBasedTest { try { final IRace race = ModelLocator.getEventFactory().createRace(new URI(paramUrl.toString()), (int) /* timeout in milliseconds */ Duration.ONE_MINUTE.asMillis()); this.race = race; + logger.info("Using race "+race.getName()+" with ID "+race.getId()+" for this test"); ISubscriberFactory subscriberFactory = SubscriptionLocator.getSusbcriberFactory(); if (storedUri == null) { eventSubscriber = subscriberFactory.createEventSubscriber(race.getEvent()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackFoiledDistanceCacheTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackFoiledDistanceCacheTest.java new file mode 100755 index 00000000000..791fe2effdf --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackFoiledDistanceCacheTest.java @@ -0,0 +1,229 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.NauticalMileDistance; +import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl; +import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.BravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicBravoFixTrack; +import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl; +import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; +import com.sap.sailing.domain.tracking.impl.TimeRangeCache; +import com.sap.sse.common.Distance; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.DegreeBearingImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * See bug4629. This test reproduces an order of fix insertion into a {@link BravoFixTrack}, cache invalidation, + * cache value calculation and cache value insertion that with bug 4629 existing will lead to an inconsistent + * cache entry that should have been invalidated by the fix insertion. + * + * @author Axel Uhl (d043530) + * + */ +public class BravoFixTrackFoiledDistanceCacheTest { + private DynamicBravoFixTrack track; + private DynamicGPSFixMovingTrackImpl gpsTrack; + private TimeRangeCacheWithParallelTestSupport foilingDistanceCache; + + /** + * Supports blocking and releasing calls to {@link #invalidateAllAtOrLaterThan(TimePoint)} and + * {@link #cache(TimePoint, TimePoint, Object)}, so as to force lock acquisition and release + * in a specific order. + * + * @author Axel Uhl (d043530) + * + * @param + */ + private static class TimeRangeCacheWithParallelTestSupport extends TimeRangeCache { + private static Map> caches = new HashMap<>(); + private int callsToCache; + private int callsToInvalidateAllAtOrLaterThan; + private CyclicBarrier cacheBarrier; + private CyclicBarrier invalidateBarrier; + private CyclicBarrier cacheInformBarrier; + + public TimeRangeCacheWithParallelTestSupport(String nameForLockLogging) { + super(nameForLockLogging); + caches.put(nameForLockLogging, this); + } + + static public TimeRangeCacheWithParallelTestSupport getCacheByName(String nameForLockLogging) { + @SuppressWarnings("unchecked") + TimeRangeCacheWithParallelTestSupport timeRangeCacheWithParallelTestSupport = (TimeRangeCacheWithParallelTestSupport) caches.get(nameForLockLogging); + return timeRangeCacheWithParallelTestSupport; + } + + @Override + public void invalidateAllAtOrLaterThan(TimePoint timePoint) { + super.invalidateAllAtOrLaterThan(timePoint); + callsToInvalidateAllAtOrLaterThan++; + if (invalidateBarrier != null) { + try { + invalidateBarrier.await(); + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void cache(TimePoint from, TimePoint to, T result) { + try { + if (cacheInformBarrier != null) { + cacheInformBarrier.await(); + } + if (cacheBarrier != null) { + cacheBarrier.await(); + } + } catch (InterruptedException | BrokenBarrierException e) { + throw new RuntimeException(e); + } + super.cache(from, to, result); + callsToCache++; + } + + public int getCallsToCache() { + return callsToCache; + } + + public int getCallsToInvalidateAllAtOrLaterThan() { + return callsToInvalidateAllAtOrLaterThan; + } + + public void waitForCacheInvalidation() throws InterruptedException, BrokenBarrierException { + invalidateBarrier.await(); + invalidateBarrier = null; + } + + public void allowWaitingForCacheInvalidation() { + invalidateBarrier = new CyclicBarrier(2); + } + + public void letFoilingDistanceCacheContinueWithCaching() throws InterruptedException, BrokenBarrierException { + cacheBarrier.await(); + cacheBarrier = null; + } + + public void letFoilingDistanceCacheWaitBeforeCaching() { + cacheBarrier = new CyclicBarrier(2); + } + + public void letFoilingDistanceCacheInformUsBeforeCaching() { + cacheInformBarrier = new CyclicBarrier(2); + } + + public void waitForCacheToBeEntered() throws InterruptedException, BrokenBarrierException { + cacheInformBarrier.await(); + cacheInformBarrier = null; + } + } + + @Before + public void setUp() { + final CourseAreaImpl courseArea = new CourseAreaImpl("Test", UUID.randomUUID()); + gpsTrack = new DynamicGPSFixMovingTrackImpl<>(courseArea, /* millisecondsOverWhichToAverage */ 15000); + track = new BravoFixTrackImpl(courseArea, "test", /* hasExtendedFixes */ true, gpsTrack) { + private static final long serialVersionUID = 1473560197177750211L; + + @Override + protected TimeRangeCache createTimeRangeCache(CourseArea trackedItem, final String cacheName) { + return new TimeRangeCacheWithParallelTestSupport<>(cacheName); + } + }; + track.add(createFix(1000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.)); + track.add(createFix(2000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.)); + track.add(createFix(3000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.)); + gpsTrack.add(createGPSFix(1000l, 0, 0, 0, 1)); + gpsTrack.add(createGPSFix(2000l, 1./3600./60., 0, 0, 1)); + gpsTrack.add(createGPSFix(3000l, 2./3600./60., 0, 0, 1)); + foilingDistanceCache = TimeRangeCacheWithParallelTestSupport.getCacheByName("foilingDistanceCache"); + } + + @Test + public void testDistanceSpentFoiling() throws InterruptedException, ExecutionException, BrokenBarrierException { + assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01); + assertEquals(1, foilingDistanceCache.getCallsToCache()); + assertEquals(6, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // the three sensor and three GPS fixes + assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01); + assertEquals(1, foilingDistanceCache.getCallsToCache()); // still the same perfect cache hit, no new cached value + track.add(createFix(2500l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.)); + assertEquals(7, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // now one more sensor fix + assertEquals(new NauticalMileDistance(2./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(3000l)).getMeters(), 0.01); + assertEquals(2, foilingDistanceCache.getCallsToCache()); // had to be re-calculated and then was expected to be put to cache + gpsTrack.add(createGPSFix(4000l, 3./3600./60., 0, 0, 1)); + assertEquals(8, foilingDistanceCache.getCallsToInvalidateAllAtOrLaterThan()); // now one more GPS fix + + // now modify the cache such that it will stop before updating the cache + foilingDistanceCache.letFoilingDistanceCacheWaitBeforeCaching(); + foilingDistanceCache.letFoilingDistanceCacheInformUsBeforeCaching(); + FutureTask getDistanceFuture = new FutureTask<>(()->track.getDistanceSpentFoiling(t(1000l), t(4000l))); + new Thread(getDistanceFuture).start(); + foilingDistanceCache.waitForCacheToBeEntered(); + // now insert another sensor fix at t(4000l) that will have to invalidate the result of the previous request; + // adding the fix will trigger a cache invalidation; the TimeRangeCache.cache(...) call caused by the query above + // is still blocked: + FutureTask addFuture = new FutureTask<>(()->track.add(createFix(4000l, /* rideHeightPort */ 0.6, /* rideHeightStarboard */ 0.6, /* heel */ 10., /* pitch */ 5.))); + foilingDistanceCache.allowWaitingForCacheInvalidation(); + new Thread(addFuture).start(); + // with the fix for bug4629 the cache invalidation won't be reached because fix addition will require the write lock + // which isn't possible until the cache update has succeeded which now happens under the track's read lock. + Thread.sleep(500); // to continue to let the old broken version fail more or less reliably by waiting for track.add(...) to reach the invalidation + // So let the request from above continue with its call to cache(...) which eventually will release the track's read lock... + foilingDistanceCache.letFoilingDistanceCacheContinueWithCaching(); + // ...so that now the cache invalidation will finally get on its way + foilingDistanceCache.waitForCacheInvalidation(); + // wait until the caching has completed: + getDistanceFuture.get(); + // and until adding the fixes has completed + addFuture.get(); + // Now for the getDistanceFuture, either it has delivered the new value already because it was passed by + // the addition of the fix, or it delivered the old value, but then the cache entry will have been invalidated. + // Now ask again; if the invalidation worked correctly, we should get a greater result now: + assertEquals(new NauticalMileDistance(3./3600.).getMeters(), track.getDistanceSpentFoiling(t(1000l), t(4000l)).getMeters(), 0.01); + } + + private BravoExtendedFixImpl createFix(long timePointAsMillis, Double rideHeightPort, Double rideHeightStarboard, Double heel, Double pitch) { + final Double[] fixData = new Double[Collections.max(Arrays.asList( + BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()+1, + BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()+1, + BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()+1, + BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()+1))]; + fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel; + fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch; + fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeightPort; + fixData[BravoExtendedSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeightStarboard; + return new BravoExtendedFixImpl(new DoubleVectorFixImpl(t(timePointAsMillis), fixData)); + } + + private GPSFixMoving createGPSFix(long timePointAsMillis, double lat, double lng, double cogInDeg, double sogInKnots) { + return new GPSFixMovingImpl(new DegreePosition(lat, lng), new MillisecondsTimePoint(timePointAsMillis), + new KnotSpeedWithBearingImpl(sogInKnots, new DegreeBearingImpl(cogInDeg))); + } + + private MillisecondsTimePoint t(long timePointAsMillis) { + return new MillisecondsTimePoint(timePointAsMillis); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java index 822847e5edf..a9a4d7f29a4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java @@ -86,7 +86,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { domainRegatta = domainFactory.getOrCreateDefaultRegatta(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, getTracTracRace(), /* trackedRegattaRegistry */ null); trackedRegatta = new DynamicTrackedRegattaImpl(domainRegatta); - IRace tractracRace = getTracTracEvent().getRaces().iterator().next(); + IRace tractracRace = getTracTracRace(); ArrayList receivers = new ArrayList(); receivers.add(new RaceCourseReceiver(domainFactory, trackedRegatta, getTracTracEvent(), tractracRace, EmptyWindStore.INSTANCE, new DynamicRaceDefinitionSet() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestForLowPoints.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestForLowPoints.java new file mode 100644 index 00000000000..8bbc958e8b1 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTestForLowPoints.java @@ -0,0 +1,900 @@ +package com.sap.sailing.domain.test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Fleet; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.impl.DelegatingRegattaLeaderboardWithCompetitorElimination; +import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinTwoRaces; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.test.mock.MockedTrackedRaceWithStartTimeAndRanks; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +import junit.framework.Assert; + +/** + * This class contains several tests for the {@link LowPointFirstToWinTwoRaces} scoring rule defined by + * {@link ScoringSchemeType#LOW_POINT_FIRST_TO_WIN_TWO_RACES}. It tests that the carry is correctly applied and that the + * ordering is as expected. Furthermore it contains several negative tests, that validate, that the normal low point + * behavior is not changed and still works for those same cases in case the {@link ScoringSchemeType#LOW_POINT} scoring + * scheme is used. + */ +public class LeaderboardScoringAndRankingTestForLowPoints extends LeaderboardScoringAndRankingTestBase { + private static final double EPSILON = 0.000001; + + protected DelegatingRegattaLeaderboardWithCompetitorElimination createDelegatingRegattaLeaderboardWithCompetitorElimination( + Regatta regatta, String leaderboardName, int[] discardingThresholds) { + return new DelegatingRegattaLeaderboardWithCompetitorElimination( + () -> createLeaderboard(regatta, discardingThresholds), leaderboardName); + } + + private void executePreSeries(List yellow, List blue, TimePoint now) { + RaceColumn qColumn = series.get(0).getRaceColumnByName("Q"); + TrackedRace qYellow = new MockedTrackedRaceWithStartTimeAndRanks(now, yellow); + qColumn.setTrackedRace(qColumn.getFleetByName("Yellow"), qYellow); + TrackedRace qBlue = new MockedTrackedRaceWithStartTimeAndRanks(now, blue); + qColumn.setTrackedRace(qColumn.getFleetByName("Blue"), qBlue); + } + + private void manuallyTransferCarry(Leaderboard leaderboard, List medalCompetitors) { + int carryScore = 1; + for (Competitor medalCompetitor : medalCompetitors) { + leaderboard.getScoreCorrection().correctScore(medalCompetitor, leaderboard.getRaceColumnByName("Carry"), carryScore++); + } + } + + private Regatta setupRegatta(boolean useFirstTwoWins) { + final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("470", /* typicallyStartsUpwind */ true); + Regatta regatta = new RegattaImpl(RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, + false, /* startDate */ null, /* endDate */ null, series, /* persistent */false, + DomainFactory.INSTANCE + .createScoringScheme(useFirstTwoWins ? ScoringSchemeType.LOW_POINT_FIRST_TO_WIN_TWO_RACES + : ScoringSchemeType.LOW_POINT), + "123", /* course area */null, OneDesignRankingMetric::new); + return regatta; + } + + private void setupMedalSeriesWithCarryOverAndFourRaceColumns() { + Set medalFleets = Collections.singleton(new FleetImpl("Default")); + List medalRaceColumnNames = new ArrayList(); + medalRaceColumnNames.add("Carry"); + medalRaceColumnNames.add("M1"); + medalRaceColumnNames.add("M2"); + medalRaceColumnNames.add("M3"); + medalRaceColumnNames.add("M4"); + Series medalSeries = new SeriesImpl("Medal", /* isMedal */true, /* isFleetsCanRunInParallel */ true, + medalFleets, medalRaceColumnNames, /* trackedRegattaRegistry */ null); + medalSeries.setFirstColumnIsNonDiscardableCarryForward(true); + medalSeries.setStartsWithZeroScore(true); + series.add(medalSeries); + } + + private void setupQualificationSeriesWithOneRaceColumn() { + List qualificationFleets = new ArrayList(); + for (String qualificationFleetName : new String[] { "Yellow", "Blue" }) { + qualificationFleets.add(new FleetImpl(qualificationFleetName)); + } + List qualificationRaceColumnNames = new ArrayList(); + qualificationRaceColumnNames.add("Q"); + Series qualificationSeries = new SeriesImpl("Qualification", /* isMedal */false, + /* isFleetsCanRunInParallel */ true, qualificationFleets, qualificationRaceColumnNames, + /* trackedRegattaRegistry */ null); + series.add(qualificationSeries); + } + + private List> createCompetitorResultForTimestamp(TimePoint time, Leaderboard leaderboard) { + List> list = new ArrayList<>(); + for (Competitor competitor : leaderboard.getCompetitorsFromBestToWorst(time)) { + list.add(new Pair(competitor, leaderboard.getNetPoints(competitor, time))); + } + return list; + } + + private void assertNonFinalistsAreBehindFinalistsAndNotChanged( + List> preSeriesScoreRankResult, List> afterFinalResults) { + List> nonFinalistsPreScore = preSeriesScoreRankResult.subList(4, + preSeriesScoreRankResult.size()); + List> nonFinalistsAfterScore = afterFinalResults.subList(4, afterFinalResults.size()); + Assert.assertEquals(nonFinalistsPreScore, nonFinalistsAfterScore); + } + + /** + * In this test the preseries winner will win the first race, and should get a score of 2, all other finalists + * should be scored with Low_Points restarting at 0 for the medal series. The non finalists score should not change + * during the medalseries. + */ + @Test + public void testFirstPreseriesWinsAgain() throws NoWindException { + series = new ArrayList(); + setupQualificationSeriesWithOneRaceColumn(); + + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(true); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + m1Results.add(preFourth); + + TrackedRace mDefault = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), mDefault); + + Assert.assertEquals(2.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(4, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(6, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preFourth, later), EPSILON); + + List> afterFinalResults = createCompetitorResultForTimestamp(later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * In this test the second best in the preseries wins the first two medal races, and should get a score of 2, all + * other finalists should be scored with Low_Points restarting at 0 for the medal series. The non finalists score + * should not change during the medalseries. + */ + @Test + public void testSecondPreSeriesWinsTwice() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(true); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preSecond); + m1Results.add(preFirst); + m1Results.add(preThird); + m1Results.add(preFourth); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(3, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(3, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(6, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preFourth, later), EPSILON); + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preSecond); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preSecond); + m2Results.add(preFirst); + m2Results.add(preThird); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(4.0, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(5.0, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(9, leaderboard.getNetPoints(preThird, m2later), EPSILON); + Assert.assertEquals(12, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + + List res2 = leaderboard.getCompetitorsFromBestToWorst(m2later); + Assert.assertEquals(res2.get(0), preSecond); + Assert.assertEquals(res2.get(1), preFirst); + Assert.assertEquals(res2.get(2), preThird); + Assert.assertEquals(res2.get(3), preFourth); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * In this test the best in the preseries wins the second medal race, and should get a score of 2, all other + * finalists should be scored with Low_Points restarting at 0 for the medal series. The non finalists score should + * not change during the medalseries. Also asserts that during tie-breaking the pre-series carry score takes + * precedence. + */ + @Test + public void testFirstWinsSecondRace() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(true); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preSecond); + m1Results.add(preFirst); + m1Results.add(preThird); + m1Results.add(preFourth); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(3, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(3, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(6, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preFourth, later), EPSILON); + // assert that during tie-breaking the pre-series carry score takes precedence + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preSecond); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preThird); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(4, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(5, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(9, leaderboard.getNetPoints(preThird, m2later), EPSILON); + Assert.assertEquals(12, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + + List res2 = leaderboard.getCompetitorsFromBestToWorst(m2later); + Assert.assertEquals(res2.get(0), preFirst); + Assert.assertEquals(res2.get(1), preSecond); + Assert.assertEquals(res2.get(2), preThird); + Assert.assertEquals(res2.get(3), preFourth); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * In this test the worst finalist of the qualification wins the first and second medal races, and should get a + * score of 2, all other finalists should be scored with Low_Points restarting at 0 for the medal series. The non + * finalists score should not change during the medalseries + */ + @Test + public void testLastWinsTwoRaces() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(true); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFourth); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(5, leaderboard.getNetPoints(preFourth, later), EPSILON); + Assert.assertEquals(3.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(5, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(7, leaderboard.getNetPoints(preThird, later), EPSILON); + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preSecond); + Assert.assertEquals(res.get(2), preFourth); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preFourth); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preThird); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(6.0, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + Assert.assertEquals(5.0, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(11, leaderboard.getNetPoints(preThird, m2later), EPSILON); + + List res2 = leaderboard.getCompetitorsFromBestToWorst(m2later); + Assert.assertEquals(res2.get(0), preFourth); + Assert.assertEquals(res2.get(1), preFirst); + Assert.assertEquals(res2.get(2), preSecond); + Assert.assertEquals(res2.get(3), preThird); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * In this test no finalist reaches two wins before terminating (eg. due to bad weather) all finalists should be + * scored with Low_Points restarting at 0 for the medal series. The non finalists score should not change during the + * medalseries. + */ + @Test + public void noTwoWinsAbortAfterTwoRacesAndTieBreaker() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(true); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFourth); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(5, leaderboard.getNetPoints(preFourth, later), EPSILON); + Assert.assertEquals(3.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(5, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(7, leaderboard.getNetPoints(preThird, later), EPSILON); + + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preSecond); + Assert.assertEquals(res.get(2), preFourth); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preThird); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(9, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + Assert.assertEquals(5.0, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preThird, m2later), EPSILON); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * Using normal lowpoints, being the best in the preseries and having an additional win in the medals, should not + * make you the overall winner in case another finalist has a better score. + */ + @Test + public void negativeTestFirstPreseriesWinsAgain() throws NoWindException { + series = new ArrayList(); + setupQualificationSeriesWithOneRaceColumn(); + + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(false); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + List medalCompetitors = preSeriesRankResult.subList(0, 4); + + manuallyTransferCarry(leaderboard, medalCompetitors); + + Competitor preFirst = medalCompetitors.get(0); + Competitor preSecond = medalCompetitors.get(1); + Competitor preThird = medalCompetitors.get(2); + Competitor preFourth = medalCompetitors.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + m1Results.add(preFourth); + + TrackedRace mDefault = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), mDefault); + + Assert.assertEquals(4, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(8, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(12, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preFourth, later), EPSILON); + Assert.assertEquals(1, leaderboard.getTotalRankOfCompetitor(preFirst, later)); + Assert.assertEquals(2, leaderboard.getTotalRankOfCompetitor(preSecond, later)); + Assert.assertEquals(3, leaderboard.getTotalRankOfCompetitor(preThird, later)); + Assert.assertEquals(4, leaderboard.getTotalRankOfCompetitor(preFourth, later)); + + List> afterFinalResults = createCompetitorResultForTimestamp(later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * Using normal lowpoints, having two wins should not matter, also the racecolum factor is expected to be 2 instead + * of 1. + */ + @Test + public void negativeTestSecondPreSeriesWinsTwice() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(false); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preSecond); + m1Results.add(preFirst); + m1Results.add(preThird); + m1Results.add(preFourth); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(6, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(6.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(12, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preFourth, later), EPSILON); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preSecond); + m2Results.add(preFirst); + m2Results.add(preThird); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(8, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(10.0, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(18, leaderboard.getNetPoints(preThird, m2later), EPSILON); + Assert.assertEquals(24, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + Assert.assertEquals(1, leaderboard.getTotalRankOfCompetitor(preSecond, m2later)); + Assert.assertEquals(2, leaderboard.getTotalRankOfCompetitor(preFirst, m2later)); + Assert.assertEquals(3, leaderboard.getTotalRankOfCompetitor(preThird, m2later)); + Assert.assertEquals(4, leaderboard.getTotalRankOfCompetitor(preFourth, m2later)); + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * Using normal lowpoints, having two wins should not matter, also the racecolum factor is expected to be 2 instead + * of 1. + */ + @Test + public void negativeTestFirstWinsSecondRace() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(false); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preSecond); + m1Results.add(preFirst); + m1Results.add(preThird); + m1Results.add(preFourth); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(6, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(6, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(12, leaderboard.getNetPoints(preThird, later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preFourth, later), EPSILON); + + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preSecond); + Assert.assertEquals(res.get(1), preFirst); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preThird); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(8, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(10, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(18, leaderboard.getNetPoints(preThird, m2later), EPSILON); + Assert.assertEquals(24, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * Using normal lowpoints, the racecolum factor is expected to be 2 instead of 1. + */ + @Test + public void negativeTestLastWinsTwoRaces() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(false); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFourth); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(10, leaderboard.getNetPoints(preFourth, later), EPSILON); + Assert.assertEquals(6.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(10, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(14, leaderboard.getNetPoints(preThird, later), EPSILON); + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preFourth); + Assert.assertEquals(res.get(2), preSecond); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preFourth); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preThird); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(12, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + Assert.assertEquals(10, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(22, leaderboard.getNetPoints(preThird, m2later), EPSILON); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + + /** + * Using normal lowpoints, tie braking is done with manual carry decimals. + */ + @Test + public void negativeNoTwoWinsAbortAfterTwoRacesAndTieBreaker() throws NoWindException { + series = new ArrayList(); + // -------- qualification series ------------ + setupQualificationSeriesWithOneRaceColumn(); + setupMedalSeriesWithCarryOverAndFourRaceColumns(); + Regatta regatta = setupRegatta(false); + List competitors = createCompetitors(12); + List yellow = new ArrayList<>(competitors.subList(0, 6)); + List blue = new ArrayList<>(competitors); + blue.removeAll(yellow); + Collections.shuffle(yellow); + Collections.shuffle(blue); + + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis() + 1000); + executePreSeries(yellow, blue, now); + + List preSeriesRankResult = leaderboard.getCompetitorsFromBestToWorst(later); + List medalCompetitorsBestToWorst = preSeriesRankResult.subList(0, 4); + List> preSeriesScoreRankResult = createCompetitorResultForTimestamp(later, + leaderboard); + manuallyTransferCarry(leaderboard, medalCompetitorsBestToWorst); + + Competitor preFirst = medalCompetitorsBestToWorst.get(0); + Competitor preSecond = medalCompetitorsBestToWorst.get(1); + Competitor preThird = medalCompetitorsBestToWorst.get(2); + Competitor preFourth = medalCompetitorsBestToWorst.get(3); + + RaceColumn m1 = leaderboard.getRaceColumnByName("M1"); + + ArrayList m1Results = new ArrayList<>(); + m1Results.add(preFourth); + m1Results.add(preFirst); + m1Results.add(preSecond); + m1Results.add(preThird); + + TimePoint m1now = new MillisecondsTimePoint(later.asMillis() + 1000); + TimePoint m1later = new MillisecondsTimePoint(m1now.asMillis() + 1000); + + TrackedRace m1Default = new MockedTrackedRaceWithStartTimeAndRanks(now, m1Results); + m1.setTrackedRace(m1.getFleetByName("Default"), m1Default); + + Assert.assertEquals(10, leaderboard.getNetPoints(preFourth, later), EPSILON); + Assert.assertEquals(6.0, leaderboard.getNetPoints(preFirst, later), EPSILON); + Assert.assertEquals(10, leaderboard.getNetPoints(preSecond, later), EPSILON); + Assert.assertEquals(14, leaderboard.getNetPoints(preThird, later), EPSILON); + + List res = leaderboard.getCompetitorsFromBestToWorst(later); + Assert.assertEquals(res.get(0), preFirst); + Assert.assertEquals(res.get(1), preFourth); + Assert.assertEquals(res.get(2), preSecond); + + RaceColumn m2 = leaderboard.getRaceColumnByName("M2"); + + ArrayList m2Results = new ArrayList<>(); + m2Results.add(preThird); + m2Results.add(preFirst); + m2Results.add(preSecond); + m2Results.add(preFourth); + + TimePoint m2now = new MillisecondsTimePoint(m1later.asMillis() + 1000); + TimePoint m2later = new MillisecondsTimePoint(m2now.asMillis() + 1000); + TrackedRace m2Default = new MockedTrackedRaceWithStartTimeAndRanks(m2now, m2Results); + m2.setTrackedRace(m1.getFleetByName("Default"), m2Default); + + Assert.assertEquals(18, leaderboard.getNetPoints(preFourth, m2later), EPSILON); + Assert.assertEquals(10, leaderboard.getNetPoints(preFirst, m2later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preSecond, m2later), EPSILON); + Assert.assertEquals(16, leaderboard.getNetPoints(preThird, m2later), EPSILON); + + List res2 = leaderboard.getCompetitorsFromBestToWorst(m2later); + Assert.assertEquals(res2.get(0), preFirst); + Assert.assertEquals(res2.get(1), preThird); + Assert.assertEquals(res2.get(2), preSecond); + Assert.assertEquals(res2.get(3), preFourth); + + List> afterFinalResults = createCompetitorResultForTimestamp(m2later, leaderboard); + assertNonFinalistsAreBehindFinalistsAndNotChanged(preSeriesScoreRankResult, afterFinalResults); + } + +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverLossOnPenaltyCircleTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverLossOnPenaltyCircleTest.java index 0a113cd183d..612e6943436 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverLossOnPenaltyCircleTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ManeuverLossOnPenaltyCircleTest.java @@ -56,6 +56,6 @@ public class ManeuverLossOnPenaltyCircleTest extends OnlineTracTracBasedTest { Competitor canottieri = getCompetitorByName("Club Canottieri Roggero di Lauria"); final Iterable maneuversCanottieri = getTrackedRace().getManeuvers(canottieri, getTrackedRace().getStartOfRace(), getTrackedRace().getEndOfRace(), /* waitForLatest */ true); final Optional penaltyCircleCanottieri = StreamSupport.stream(maneuversCanottieri.spliterator(), /* parallel */ false).filter(m->m.getType()==ManeuverType.PENALTY_CIRCLE).findAny(); - assertThat(penaltyCircleCanottieri.get().getManeuverLoss(), new IsGreaterThan<>(new MeterDistance(30))); + assertThat(penaltyCircleCanottieri.get().getManeuverLoss().getProjectedDistanceLost(), new IsGreaterThan<>(new MeterDistance(30))); } } 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 e875657165d..569ba976618 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 @@ -172,8 +172,8 @@ public class OfflineSerializationTest extends AbstractSerializationTest { DomainFactory receiverDomainFactory = new DomainFactoryImpl((srlid)->null); LeaderboardGroup clone = cloneBySerialization(leaderboardGroup, receiverDomainFactory); - assertEquals(FACTOR, overallLeaderboard.getRaceColumnByName("Test Leaderboard").getFactor(), 0.00000001); - assertEquals(FACTOR, clone.getOverallLeaderboard().getRaceColumnByName("Test Leaderboard").getFactor(), 0.00000001); + assertEquals(FACTOR, overallLeaderboard.getScoringScheme().getScoreFactor(overallLeaderboard.getRaceColumnByName("Test Leaderboard")), 0.00000001); + assertEquals(FACTOR, clone.getOverallLeaderboard().getScoringScheme().getScoreFactor(clone.getOverallLeaderboard().getRaceColumnByName("Test Leaderboard")), 0.00000001); } @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java index a69fdcdbbea..cf000465485 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java @@ -8,6 +8,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; import org.junit.Before; import org.junit.Test; @@ -36,6 +37,7 @@ import com.tractrac.model.lib.api.event.IRaceCompetitor; import com.tractrac.subscription.lib.api.control.IControlPassingsListener; public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { + private static final Logger logger = Logger.getLogger(ReceiveMarkPassingDataTest.class.getName()); final private Object semaphor = new Object(); final private IControlPassings[] firstData = new IControlPassings[1]; private RaceDefinition raceDefinition; @@ -54,6 +56,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { @Before public void setupListener() { final IRace race = getTracTracRace(); + logger.info("Setting up listener for race "+race.getName()+" with ID "+race.getId()); Receiver receiver = new Receiver() { @Override public void stopPreemptively() { @@ -82,10 +85,14 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { @Override public void gotControlPassings(IRaceCompetitor raceCompetitor, IControlPassings controlPassings) { + logger.info("Received control passings "+controlPassings+" for competitor "+raceCompetitor); if (first) { + logger.info("Was first"); synchronized (semaphor) { firstData[0] = controlPassings; + logger.info("Notifying all"); semaphor.notifyAll(); + logger.info("unsubscribing"); getRaceSubscriber().unsubscribeControlPassings(this); } first = false; @@ -100,10 +107,13 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { }; List receivers = new ArrayList(); receivers.add(receiver); + logger.info("Getting update receivers for TracTrac race "+getTracTracRace().getName()+" with ID "+getTracTracRace().getId()+ + " where the event has as its first race "+getTracTracEvent().getRaces().iterator().next().getName()+" with ID "+ + getTracTracEvent().getRaces().iterator().next().getId()); for (Receiver r : DomainFactory.INSTANCE.getUpdateReceivers( new DynamicTrackedRegattaImpl(DomainFactory.INSTANCE.getOrCreateDefaultRegatta( EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, getTracTracRace(), /* trackedRegattaRegistry */null)), - getTracTracEvent().getRaces().iterator().next(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l, + getTracTracRace(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l, /* simulator */null, new DynamicRaceDefinitionSet() { @Override @@ -117,6 +127,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { addReceiverToStopDuringTearDown(r); } addListenersForStoredDataAndStartController(receivers); + logger.info("Waiting for race definition "+race.getName()+" with ID "+race.getId()); raceDefinition = DomainFactory.INSTANCE.getAndWaitForRaceDefinition(race.getId()); synchronized (semaphor) { while (firstData[0] == null) { diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index 5567e3bc181..6f6d9a68bbe 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -31,6 +31,7 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.trackimport, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, + com.sap.sailing.domain.sharding, com.sap.sailing.domain.windfinder, com.sap.sailing.util, com.sap.sailing.util.impl;x-friends:="com.sap.sailing.server.replication,com.sap.sailing.domain.test" diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java index 8f6c1afbf14..253ab268a12 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/RaceColumn.java @@ -162,14 +162,6 @@ public interface RaceColumn extends Named { */ void releaseTrackedRace(Fleet fleet); - /** - * Usually, the scores in each leaderboard column count as they are for the overall score. However, if a column is - * a medal race column it usually counts double. Under certain circumstances, columns may also count with factors different - * from 1 or 2. For example, we've seen cases in the Extreme Sailing Series where the race committee defined that in the - * overall series leaderboard the last two columns each count 1.5 times their scores. - */ - double getFactor(); - /** * By default, a competitor's total score is computed by summing up the non-discarded total points of each race * across the leaderboard, considering the {@link RaceColumn#getFactor() column factors}. Some race columns, 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 f86247656d9..01e73aeacfe 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 @@ -58,6 +58,7 @@ import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsEightAndInterp import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsFive; import com.sap.sailing.domain.leaderboard.impl.HighPointWinnerGetsSix; import com.sap.sailing.domain.leaderboard.impl.LowPoint; +import com.sap.sailing.domain.leaderboard.impl.LowPointFirstToWinTwoRaces; import com.sap.sailing.domain.leaderboard.impl.LowPointForLeagueOverallLeaderboard; import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeriesOnly; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; @@ -144,6 +145,10 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new LowPointTieBreakBasedOnLastSeriesOnly(); case LOW_POINT_WITH_AUTOMATIC_RDG: return new LowPointWithAutomaticRDG(); + case LOW_POINT_FIRST_TO_WIN_TWO_RACES: + return new LowPointFirstToWinTwoRaces(); + default: + break; } throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java index e76b1f77ca8..19966720fd0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/SimpleAbstractRaceColumn.java @@ -20,13 +20,6 @@ public abstract class SimpleAbstractRaceColumn implements RaceColumn { private static final long serialVersionUID = -3590156714385187908L; private final RaceColumnListeners raceColumnListeners; - /** - * The factor by which a medal race score is multiplied by default in the overall point scheme. - * - * @see #getFactor() - */ - private static final double DEFAULT_MEDAL_RACE_FACTOR = 2.0; - /** * If null, the {@link #getFactor() factor} defaults to 1 for non-medal and {@link #DEFAULT_MEDAL_RACE_FACTOR} for * medal races. Otherwise, the explicit factor is used. @@ -66,11 +59,6 @@ public abstract class SimpleAbstractRaceColumn implements RaceColumn { return result; } - @Override - public double getFactor() { - return explicitFactor == null ? (isMedalRace() ? DEFAULT_MEDAL_RACE_FACTOR : 1.) : explicitFactor; - } - @Override public void setFactor(Double factor) { Double oldExplicitFactor = getExplicitFactor(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index 098bdb90cf9..1149ec9004c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -604,4 +604,22 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * leaderboard. */ boolean hasScores(Competitor competitor, TimePoint timePoint); -} + + /** + * Returns true if a racecolumn evaluates to be a win for the given competitor at the given timepoint. + * If the competitor is not scored for this race, false is returned + */ + default boolean isWin(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + final Double points = getTotalPoints(competitor, raceColumn, timePoint); + final boolean result; + if (points == null) { + result = false; + } else if (getScoringScheme().isHigherBetter()) { + double competitorCount = Util.size(getCompetitors()); + result = points >= (competitorCount - 0.05); + } else { + result = points <= 1.05; + } + return result; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java index ec65a4d43be..8964a073e76 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java @@ -8,6 +8,7 @@ import java.util.concurrent.Callable; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.ScoringSchemeType; @@ -25,6 +26,13 @@ import com.sap.sse.common.Util; * */ public interface ScoringScheme extends Serializable { + /** + * The factor by which a medal race score is multiplied by default in the overall point scheme. + * + * @see #getFactor() + */ + static final double DEFAULT_MEDAL_RACE_FACTOR = 2.0; + /** * If this returns true, a higher score is better. For example, the Extreme Sailing Series uses this * scoring scheme, as opposed to the olympic sailing classes which use a low-point system. @@ -119,4 +127,51 @@ public interface ScoringScheme extends Serializable { * @throws NoWindException */ int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint); + + /** + * Returning {@code true} makes the number of wins in a medal series the primary ranking criteria. + * The number of wins that makes a competitor the overall winner must be returned by {@link #getTargetAmountOfMedalRaceWins()}. + */ + default boolean isMedalWinAmountCriteria() { + return false; + } + + /** + * Returning {@code true} makes the {@link RaceColumn#isCarryForward() carry forward score} in a + * {@link Series#isMedal() medal series} a secondary ranking criteria for competitors that have an equal overall + * score. + */ + default boolean isCarryForwardInMedalsCriteria() { + return false; + } + + /** + * Returning {@code true} makes the last medal race (having valid scores) a secondary ranking criteria for + * competitors that have an equal overall score. + */ + default boolean isLastMedalRaceCriteria() { + return false; + } + + /** + * If {@link #isMedalWinAmountCriteria()} returns {@code true}, this will be the amount of races that must be won, + * in order to win the medal series instantly + */ + default int getTargetAmountOfMedalRaceWins() { + throw new IllegalStateException("This call is not valid for this scoringSheme"); + } + + /** + * Usually, the scores in each leaderboard column count as they are for the overall score. However, if a column is a + * medal race column it usually counts double. Under certain circumstances, columns may also count with factors + * different from 1 or 2. For example, we've seen cases in the Extreme Sailing Series where the race committee + * defined that in the overall series leaderboard the last two columns each count 1.5 times their scores. + */ + default double getScoreFactor(RaceColumn raceColumn) { + Double factor = raceColumn.getExplicitFactor(); + if (factor == null) { + factor = raceColumn.isMedalRace() ? DEFAULT_MEDAL_RACE_FACTOR : 1.0; + } + return factor; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java index ed3880b105b..ec56da0c10f 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/caching/LiveLeaderboardUpdater.java @@ -12,7 +12,9 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.sharding.ShardingType; import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sailing.domain.tracking.TrackedRegattaRegistry; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -274,6 +276,7 @@ public class LiveLeaderboardUpdater implements Runnable { public void run() { assert running; try { + ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboard.getName()); logger.info("Starting " + LiveLeaderboardUpdater.class.getSimpleName() + " thread for leaderboard " + leaderboard.getName()); // interrupt the current thread if not producing a single result within the overall timeout @@ -326,6 +329,8 @@ public class LiveLeaderboardUpdater implements Runnable { notifyAll(); } logger.log(Level.SEVERE, "exception updating live leaderboard "+leaderboard.getName(), e); + } finally { + ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java index 36237e81bf8..48d0964a3b9 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java @@ -72,7 +72,7 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP private int getWins(List> scores) { int wins = 0; for (com.sap.sse.common.Util.Pair score : scores) { - if (Math.abs(score.getB() - maxPoints * score.getA().getFactor()) < 0.0000001) { + if (Math.abs(score.getB() - maxPoints * getScoreFactor(score.getA())) < 0.0000001) { wins++; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java index ff804e487f6..800f4125459 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java @@ -46,8 +46,8 @@ public abstract class AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie extends private double getHighestScore(List> scores) { double highestScore = 0; for (com.sap.sse.common.Util.Pair score : scores) { - if ((score.getB() * score.getA().getFactor()) > highestScore) { - highestScore = score.getB() * score.getA().getFactor(); + if ((score.getB() * getScoreFactor(score.getA())) > highestScore) { + highestScore = score.getB() * getScoreFactor(score.getA()); } } return highestScore; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java old mode 100755 new mode 100644 index a49dc0d95bd..c2d8203bbb5 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardImpl.java @@ -281,5 +281,5 @@ public abstract class AbstractLeaderboardImpl extends AbstractSimpleLeaderboardI RegattaLogBoatDeregistrator deregisterer = new RegattaLogBoatDeregistrator<>(regattaLog, boats, regattaLogEventAuthorForAbstractLeaderboard); deregisterer.deregister(deregisterer.analyze()); } - + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java index e3bd3246dff..c4143993c59 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java @@ -55,6 +55,7 @@ import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.dto.MetaLeaderboardRaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.sharding.ShardingType; import com.sap.sailing.domain.common.tracking.BravoExtendedFix; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; @@ -68,6 +69,7 @@ import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.ranking.RankingMetric.CompetitorRankingInfo; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sailing.domain.tracking.BravoFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Maneuver; @@ -355,6 +357,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { final boolean waitForLatestAnalyses, TrackedRegattaRegistry trackedRegattaRegistry, final DomainFactory baseDomainFactory, final boolean fillTotalPointsUncorrected) throws NoWindException { + ShardingContext.checkConstraint(ShardingType.LEADERBOARDNAME, getName()); long startOfRequestHandling = System.currentTimeMillis(); final LeaderboardDTOCalculationReuseCache cache = new LeaderboardDTOCalculationReuseCache(timePoint); final BoatClass boatClass = getBoatClass(); @@ -403,7 +406,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { } // Note: the RaceColumnDTO won't be created by the following addRace call because it has been created // above by the result.createEmptyRaceColumn call - result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + result.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), getScoringScheme().getScoreFactor(raceColumn), raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, fleetDTO, raceColumn.isMedalRace(), raceIdentifier, race, isMetaLeaderboardColumn); @@ -1011,7 +1014,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { result.numberOfManeuvers.get(maneuver.getType()) + 1); totalManeuverLossInMeters.put(maneuver.getType(), totalManeuverLossInMeters.get(maneuver.getType()) - + maneuver.getManeuverLoss().getMeters()); + + maneuver.getManeuverLoss().getProjectedDistanceLost().getMeters()); } } break; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index cc53f6ebf81..cf443cfb28c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -129,11 +129,11 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { // needs to compare net points; therefore, divide the total points by the column factor for comparison: List o1NetScores = new ArrayList<>(); for (com.sap.sse.common.Util.Pair o1ColumnAndScore : o1Scores) { - o1NetScores.add(o1ColumnAndScore.getB()/o1ColumnAndScore.getA().getFactor()); + o1NetScores.add(o1ColumnAndScore.getB() / getScoreFactor(o1ColumnAndScore.getA())); } List o2NetScores = new ArrayList<>(); for (com.sap.sse.common.Util.Pair o2ColumnAndScore : o2Scores) { - o2NetScores.add(o2ColumnAndScore.getB()/o2ColumnAndScore.getA().getFactor()); + o2NetScores.add(o2ColumnAndScore.getB() / getScoreFactor(o2ColumnAndScore.getA())); } Collections.sort(o1NetScores, pureScoreComparator); Collections.sort(o2NetScores, pureScoreComparator); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index 808d4bca7b5..992edf20d05 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -52,16 +52,17 @@ import com.sap.sse.concurrent.NamedReentrantReadWriteLock; /** * Base implementation for various types of leaderboards. The {@link RaceColumnListener} implementation forwards events - * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard subscribes - * as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem like useful - * candidates. + * received to all {@link RaceColumnListener} subscribed with this leaderboard. To which objects this leaderboard + * subscribes as {@link RaceColumnListener} is left to the concrete subclasses to implement, but the race columns seem + * like useful candidates. * * @author Axel Uhl (d043530) * */ -public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardWithCache implements Leaderboard, RaceColumnListener { +public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardWithCache + implements Leaderboard, RaceColumnListener { private static final long serialVersionUID = 330156778603279333L; - + static final Double DOUBLE_0 = new Double(0); private final SettableScoreCorrection scoreCorrection; @@ -75,15 +76,15 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW private final Map displayNames; /** - * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard - * with aggregated results of races not tracked / displayed by this leaderboard in detail. The points - * provided by this map are considered by {@link #getNetPoints(Competitor, TimePoint)}. + * Backs the {@link #getCarriedPoints(Competitor)} API with data. Can be used to prime this leaderboard with + * aggregated results of races not tracked / displayed by this leaderboard in detail. The points provided by this + * map are considered by {@link #getNetPoints(Competitor, TimePoint)}. */ private final Map carriedPoints; /** - * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access - * is controlled by the {@link #suppressedCompetitorsLock} lock. + * A set that manages the difference between {@link #getCompetitors()} and {@link #getAllCompetitors()}. Access is + * controlled by the {@link #suppressedCompetitorsLock} lock. */ private final Set suppressedCompetitors; private final NamedReentrantReadWriteLock suppressedCompetitorsLock; @@ -107,8 +108,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW private final Fleet fleet; private EntryImpl(Callable trackedRankProvider, Double totalPoints, - Callable totalPointsUncorrectedProvider, boolean isTotalPointsCorrected, - Double netPoints, MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { + Callable totalPointsUncorrectedProvider, boolean isTotalPointsCorrected, Double netPoints, + MaxPointsReason maxPointsReason, boolean discarded, Fleet fleet) { super(); this.trackedRankProvider = trackedRankProvider; this.totalPoints = totalPoints; @@ -119,6 +120,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW this.discarded = discarded; this.fleet = fleet; } + @Override public int getTrackedRank() { try { @@ -127,30 +129,37 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW throw new RuntimeException(e); } } + @Override public Double getTotalPoints() { return totalPoints; } + @Override public boolean isTotalPointsCorrected() { return isTotalPointsCorrected; } + @Override public Double getNetPoints() { return netPoints; } + @Override public MaxPointsReason getMaxPointsReason() { return maxPointsReason; } + @Override public boolean isDiscarded() { return discarded; } + @Override public Fleet getFleet() { return fleet; } + @Override public Double getTotalPointsUncorrected() { try { @@ -160,7 +169,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } } } - + /** * Computing the competitors can be a bit expensive, particularly if the fleet is large and there may be suppressed * competitors, and the leaderboard may be a meta-leaderboard that refers to other leaderboards which each have @@ -171,10 +180,10 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW * * As it turns out, one of the most frequent uses of the {@link AbstractSimpleLeaderboardImpl#getCompetitors} * competitors list is to determine their number which in turn is only required for high-point scoring systems and - * for computing the default score for penalties. Again, the most frequently used low-point family of scoring schemes - * does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those that - * need it. Instead of computing it for each call, this interface lets us defer the actual calculation until the - * point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be + * for computing the default score for penalties. Again, the most frequently used low-point family of scoring + * schemes does not require this number. Yet, the scoring scheme requires an argument for polymorphic use by those + * that need it. Instead of computing it for each call, this interface lets us defer the actual calculation until + * the point when it's really needed. Once asked, this object will cache the result. Therefore, a new one should be * constructed each time the number shall be computed. * * @author Axel Uhl (D043530) @@ -183,7 +192,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public class NumberOfCompetitorsFetcherImpl implements NumberOfCompetitorsInLeaderboardFetcher { private int numberOfCompetitors = -1; private int numberOfCompetitorsWithoutMaxPointReason = -1; - + @Override public int getNumberOfCompetitorsInLeaderboard() { if (numberOfCompetitors == -1) { @@ -191,7 +200,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } return numberOfCompetitors; } - + @Override public int getNumberOfCompetitorsWithoutMaxPointReason(RaceColumn column, TimePoint timePoint) { if (numberOfCompetitorsWithoutMaxPointReason == -1) { @@ -215,17 +224,17 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW this.suppressedCompetitors = new HashSet(); this.suppressedCompetitorsLock = new NamedReentrantReadWriteLock("suppressedCompetitorsLock", /* fair */ false); } - + protected RaceColumnListeners getRaceColumnListeners() { return raceColumnListeners; } - + @Override public void trackedRaceUnlinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { getRaceColumnListeners().notifyListenersAboutTrackedRaceUnlinked(raceColumn, fleet, trackedRace); super.trackedRaceUnlinked(raceColumn, fleet, trackedRace); } - + @Override public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleet, TrackedRace trackedRace) { getRaceColumnListeners().notifyListenersAboutTrackedRaceLinked(raceColumn, fleet, trackedRace); @@ -235,10 +244,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public void isMedalRaceChanged(RaceColumn raceColumn, boolean newIsMedalRace) { getRaceColumnListeners().notifyListenersAboutIsMedalRaceChanged(raceColumn, newIsMedalRace); } - + @Override public void isFleetsCanRunInParallelChanged(RaceColumn raceColumn, boolean newIsFleetsCanRunInParallel) { - getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, newIsFleetsCanRunInParallel); + getRaceColumnListeners().notifyListenersAboutIsFleetsCanRunInParallelChanged(raceColumn, + newIsFleetsCanRunInParallel); } @Override @@ -247,13 +257,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, boolean firstColumnIsNonDiscardableCarryForward) { - getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, firstColumnIsNonDiscardableCarryForward); + public void isFirstColumnIsNonDiscardableCarryForwardChanged(RaceColumn raceColumn, + boolean firstColumnIsNonDiscardableCarryForward) { + getRaceColumnListeners().notifyListenersAboutIsFirstColumnIsNonDiscardableCarryForwardChanged(raceColumn, + firstColumnIsNonDiscardableCarryForward); } @Override public void hasSplitFleetContiguousScoringChanged(RaceColumn raceColumn, boolean hasSplitFleetContiguousScoring) { - getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, hasSplitFleetContiguousScoring); + getRaceColumnListeners().notifyListenersAboutHasSplitFleetContiguousScoringChanged(raceColumn, + hasSplitFleetContiguousScoring); } @Override @@ -283,11 +296,13 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW @Override public void competitorDisplayNameChanged(Competitor competitor, String oldDisplayName, String displayName) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, + displayName); } @Override - public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, ResultDiscardingRule newDiscardingRule) { + public void resultDiscardingRuleChanged(ResultDiscardingRule oldDiscardingRule, + ResultDiscardingRule newDiscardingRule) { getRaceColumnListeners().notifyListenersAboutResultDiscardingRuleChanged(oldDiscardingRule, newDiscardingRule); } @@ -304,7 +319,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW protected SettableScoreCorrection createScoreCorrection() { return new ScoreCorrectionImpl(this); } - + @Override public SettableScoreCorrection getScoreCorrection() { return scoreCorrection; @@ -372,7 +387,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW String oldDisplayName = displayNames.get(competitor); displayNames.put(competitor, displayName); if (!Util.equalsWithNull(oldDisplayName, displayName)) { - getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, displayName); + getRaceColumnListeners().notifyListenersAboutCompetitorDisplayNameChanged(competitor, oldDisplayName, + displayName); } } @@ -385,9 +401,9 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW @Override public Double getTotalPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { - return getScoreCorrection().getCorrectedScore( - ()->getTrackedRank(competitor, raceColumn, timePoint), competitor, - raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); + return getScoreCorrection().getCorrectedScore(() -> getTrackedRank(competitor, raceColumn, timePoint), + competitor, raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()) + .getCorrectedScore(); } @Override @@ -399,19 +415,19 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { return isDiscarded(competitor, raceColumn, getRaceColumns(), timePoint); } - + private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, Iterable raceColumnsToConsider, TimePoint timePoint) { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, + raceColumnsToConsider, timePoint); return isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns); } /** * Same as {@link #isDiscarded(Competitor, RaceColumn, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when net points are to be computed for more than one column for the same - * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done - * once and not again for each column (which would lead to quadratic effort). + * be specified which is useful when net points are to be computed for more than one column for the same competitor + * because then the calculation of discards (which requires looking at all columns) only needs to be done once and + * not again for each column (which would lead to quadratic effort). * * @param discardedRaceColumns * expected to be the result of what we would get if we called {@link #getResultDiscardingRule()}. @@ -420,8 +436,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW */ private boolean isDiscarded(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, final Set discardedRaceColumns) { - return !raceColumn.isMedalRace() - && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() + return !raceColumn.isMedalRace() && getMaxPointsReason(competitor, raceColumn, timePoint).isDiscardable() && discardedRaceColumns.contains(raceColumn); } @@ -431,16 +446,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) { - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, Iterable raceColumnsToConsider, + TimePoint timePoint) { + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, + raceColumnsToConsider, timePoint); return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns); } /** - * Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race columns can - * be specified which is useful when net points are to be computed for more than one column for the same + * Same as {@link #getNetPoints(Competitor, RaceColumn, Iterable, TimePoint)}, only that the set of discarded race + * columns can be specified which is useful when net points are to be computed for more than one column for the same * competitor because then the calculation of discards (which requires looking at all columns) only needs to be done * once and not again for each column (which would lead to quadratic effort). * @@ -450,7 +465,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW * getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint)}. */ @Override - public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, Set discardedRaceColumns) { + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint, + Set discardedRaceColumns) { Double result; if (isDiscarded(competitor, raceColumn, timePoint, discardedRaceColumns)) { result = 0.0; @@ -459,7 +475,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW if (totalPoints == null) { result = null; } else { - result = raceColumn.getFactor() * totalPoints; + result = getScoringScheme().getScoreFactor(raceColumn) * totalPoints; } } return result; @@ -473,11 +489,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW @Override public Double getNetPoints(Competitor competitor, final Iterable raceColumnsToConsider, TimePoint timePoint) { - // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race from there on + // when a column with isStartsWithZeroScore() is found, only reset score if the competitor scored in any race + // from there on boolean needToResetScoreUponNextNonEmptyEntry = false; double result = getCarriedPoints(competitor); - final Set discardedRaceColumns = getResultDiscardingRule() - .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, + raceColumnsToConsider, timePoint); for (RaceColumn raceColumn : raceColumnsToConsider) { if (raceColumn.isStartsWithZeroScore()) { needToResetScoreUponNextNonEmptyEntry = true; @@ -497,16 +514,19 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } /** - * All competitors with non-null total points are added to the result which is then sorted by total points in ascending - * order. The fleet, if ordered, is the primary ordering criterion, followed by the total points. + * All competitors with non-null total points are added to the result which is then sorted by total + * points in ascending order. The fleet, if ordered, is the primary ordering criterion, followed by the total + * points. */ @Override - public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + public List getCompetitorsFromBestToWorst(final RaceColumn raceColumn, TimePoint timePoint) + throws NoWindException { final Map> totalPointsAndFleet = new HashMap>(); for (Competitor competitor : getCompetitors()) { Double totalPoints = getTotalPoints(competitor, raceColumn, timePoint); if (totalPoints != null) { - totalPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(totalPoints, raceColumn.getFleetOfCompetitor(competitor))); + totalPointsAndFleet.put(competitor, new com.sap.sse.common.Util.Pair(totalPoints, + raceColumn.getFleetOfCompetitor(competitor))); } } List result = new ArrayList(totalPointsAndFleet.keySet()); @@ -518,7 +538,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW comparisonResult = 0; } else { if (raceColumn.hasSplitFleets() && !raceColumn.hasSplitFleetContiguousScoring()) { - // only check fleets if there are more than one and the column is not to be contiguously scored even in case + // only check fleets if there are more than one and the column is not to be contiguously scored + // even in case // of split fleets final Fleet o1Fleet = totalPointsAndFleet.get(o1).getB(); final Fleet o2Fleet = totalPointsAndFleet.get(o2).getB(); @@ -537,13 +558,14 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } } } else { - // either there are no split fleets or the split isn't relevant for scoring as for ordered fleets + // either there are no split fleets or the split isn't relevant for scoring as for ordered + // fleets // the scoring runs contiguously from top to bottom comparisonResult = 0; } if (comparisonResult == 0) { - comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false).compare( - totalPointsAndFleet.get(o1).getA(), totalPointsAndFleet.get(o2).getA()); + comparisonResult = getScoringScheme().getScoreComparator(/* nullScoresAreBetter */ false) + .compare(totalPointsAndFleet.get(o1).getA(), totalPointsAndFleet.get(o2).getA()); } } return comparisonResult; @@ -559,11 +581,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public List getCompetitorsFromBestToWorst(TimePoint timePoint) { return getCompetitorsFromBestToWorst(getRaceColumns(), timePoint); } - + /** * suppressed competitors are removed from the result */ - private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, TimePoint timePoint) { + private List getCompetitorsFromBestToWorst(Iterable raceColumnsToConsider, + TimePoint timePoint) { List result = new ArrayList(); for (Competitor competitor : getCompetitors()) { result.add(competitor); @@ -571,9 +594,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW Collections.sort(result, getTotalRankComparator(raceColumnsToConsider, timePoint)); return result; } - - protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, TimePoint timePoint) { - return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, raceColumnsToConsider); + + protected Comparator getTotalRankComparator(Iterable raceColumnsToConsider, + TimePoint timePoint) { + return new LeaderboardTotalRankComparator(this, timePoint, getScoringScheme(), /* nullScoresAreBetter */ false, + raceColumnsToConsider); } @Override @@ -599,15 +624,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { + public boolean countRaceForComparisonWithDiscardingThresholds(Competitor competitor, RaceColumn raceColumn, + TimePoint timePoint) { TrackedRace trackedRaceForCompetitorInColumn; - return getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint) && - (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) || - ((trackedRaceForCompetitorInColumn=raceColumn.getTrackedRace(competitor)) != null && - trackedRaceForCompetitorInColumn.hasStarted(timePoint) && - trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); + return getScoringScheme().isValidInNetScore(this, raceColumn, competitor, timePoint) + && (getScoreCorrection().isScoreCorrected(competitor, raceColumn, timePoint) + || ((trackedRaceForCompetitorInColumn = raceColumn.getTrackedRace(competitor)) != null + && trackedRaceForCompetitorInColumn.hasStarted(timePoint) + && trackedRaceForCompetitorInColumn.getRank(competitor, timePoint) != 0)); } - + @Override public void addRaceColumnListener(RaceColumnListener listener) { getRaceColumnListeners().addRaceColumnListener(listener); @@ -619,27 +645,31 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) throws NoWindException { - final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint) + throws NoWindException { + final Set discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, + getRaceColumns(), timePoint); return getEntry(competitor, race, timePoint, discardedRaceColumns); } - + @Override public Entry getEntry(final Competitor competitor, final RaceColumn race, final TimePoint timePoint, Set discardedRaceColumns) throws NoWindException { - Callable trackedRankProvider = ()->getTrackedRank(competitor, race, timePoint); + Callable trackedRankProvider = () -> getTrackedRank(competitor, race, timePoint); final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); final Double correctedScore = correctedResults.getCorrectedScore(); - return new EntryImpl(trackedRankProvider, correctedScore, ()->correctedResults.getUncorrectedScore(), - correctedResults.isCorrected(), discarded ? DOUBLE_0 : correctedScore == null ? null - : Double.valueOf(correctedScore * race.getFactor()), correctedResults.getMaxPointsReason(), discarded, - race.getFleetOfCompetitor(competitor)); + return new EntryImpl(trackedRankProvider, correctedScore, () -> correctedResults.getUncorrectedScore(), + correctedResults.isCorrected(), + discarded ? DOUBLE_0 + : correctedScore == null ? null : Double.valueOf(correctedScore * getScoringScheme().getScoreFactor(race)), + correctedResults.getMaxPointsReason(), discarded, race.getFleetOfCompetitor(competitor)); } @Override - public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) throws NoWindException { + public Map> getRankedCompetitorsFromBestToWorstAfterEachRaceColumn(TimePoint timePoint) + throws NoWindException { Map> result = new LinkedHashMap<>(); List raceColumnsToConsider = new ArrayList<>(); for (RaceColumn raceColumn : getRaceColumns()) { @@ -663,7 +693,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public Map call() { Map netPointsSumPerCompetitorInColumn = new HashMap<>(); for (Competitor competitor : getCompetitors()) { - netPointsSumPerCompetitorInColumn.put(competitor, getNetPoints(competitor, finalRaceColumnsToConsider, timePoint)); + netPointsSumPerCompetitorInColumn.put(competitor, + getNetPoints(competitor, finalRaceColumnsToConsider, timePoint)); } synchronized (result) { return netPointsSumPerCompetitorInColumn; @@ -686,27 +717,29 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public Map, Entry> getContent(final TimePoint timePoint) throws NoWindException { + public Map, Entry> getContent(final TimePoint timePoint) + throws NoWindException { Map, Entry> result = new HashMap, Entry>(); Map> discardedRaces = new HashMap>(); for (final RaceColumn raceColumn : getRaceColumns()) { for (final Competitor competitor : getCompetitors()) { - Callable trackedRankProvider = ()->getTrackedRank(competitor, raceColumn, timePoint); - final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, - this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + Callable trackedRankProvider = () -> getTrackedRank(competitor, raceColumn, timePoint); + final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, + raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); Set discardedRacesForCompetitor = discardedRaces.get(competitor); if (discardedRacesForCompetitor == null) { - discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); + discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, + getRaceColumns(), timePoint); discardedRaces.put(competitor, discardedRacesForCompetitor); } boolean discarded = discardedRacesForCompetitor.contains(raceColumn); final Double correctedScore = correctedResults.getCorrectedScore(); Entry entry = new EntryImpl(trackedRankProvider, correctedScore, - ()->correctedResults.getUncorrectedScore(), - correctedResults.isCorrected(), - discarded ? DOUBLE_0 : (correctedScore==null?null: - Double.valueOf((correctedScore * raceColumn.getFactor()))), correctedResults.getMaxPointsReason(), - discarded, raceColumn.getFleetOfCompetitor(competitor)); + () -> correctedResults.getUncorrectedScore(), correctedResults.isCorrected(), + discarded ? DOUBLE_0 + : (correctedScore == null ? null + : Double.valueOf((correctedScore * getScoringScheme().getScoreFactor(raceColumn)))), + correctedResults.getMaxPointsReason(), discarded, raceColumn.getFleetOfCompetitor(competitor)); result.put(new com.sap.sse.common.Util.Pair(competitor, raceColumn), entry); } } @@ -714,7 +747,8 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } /** - * A leaderboard will only accept the addition of a race column if the column's name is unique across the leaderboard. + * A leaderboard will only accept the addition of a race column if the column's name is unique across the + * leaderboard. */ @Override public boolean canAddRaceColumnToContainer(RaceColumn newRaceColumn) { @@ -731,17 +765,18 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW /** * Finds out the time point when any of the {@link Leaderboard#getTrackedRaces() tracked races currently attached to * the leaderboard} and the {@link Leaderboard#getScoreCorrection() score corrections} have last been - * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the leaderboard, - * null is returned. The time point computed this way is a good choice for normalizing queries for later time - * points in an attempt to achieve more cache hits.

+ * modified. If no tracked race is attached and no time-stamped score corrections have been applied to the + * leaderboard, null is returned. The time point computed this way is a good choice for normalizing + * queries for later time points in an attempt to achieve more cache hits. + *

* - * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be used - * to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the time point - * returned by this method but that column's attached tracked race has finished before the time point returned by this method, - * the result of this method won't change. Still, the contents of the leaderboard will change by a change in column structure. - * A different means to determine the possibility of changes that happened to this leaderboard must be used for cache - * management. Such a facility has to listen for score correction changes, tracked races being attached or detached and - * the column structure changing. + * Note, however, that the result does not tell about structural changes to the leaderboard and therefore cannot be + * used to determine the need for cache invalidation. For example, if a column is added to a leaderboard after the + * time point returned by this method but that column's attached tracked race has finished before the time point + * returned by this method, the result of this method won't change. Still, the contents of the leaderboard will + * change by a change in column structure. A different means to determine the possibility of changes that happened + * to this leaderboard must be used for cache management. Such a facility has to listen for score correction + * changes, tracked races being attached or detached and the column structure changing. * * @see TrackedRace#getTimePointOfNewestEvent() * @see SettableScoreCorrection#getTimePointOfLastCorrectionsValidity() @@ -750,19 +785,22 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public TimePoint getTimePointOfLatestModification() { TimePoint result = null; for (TrackedRace trackedRace : getTrackedRaces()) { - if (result == null || (trackedRace.getTimePointOfNewestEvent() != null && trackedRace.getTimePointOfNewestEvent().after(result))) { + if (result == null || (trackedRace.getTimePointOfNewestEvent() != null + && trackedRace.getTimePointOfNewestEvent().after(result))) { result = trackedRace.getTimePointOfNewestEvent(); } } TimePoint timePointOfLastScoreCorrection = getScoreCorrection().getTimePointOfLastCorrectionsValidity(); - if (timePointOfLastScoreCorrection != null && (result == null || timePointOfLastScoreCorrection.after(result))) { + if (timePointOfLastScoreCorrection != null + && (result == null || timePointOfLastScoreCorrection.after(result))) { result = timePointOfLastScoreCorrection; } return result; } @Override - public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint) { + public com.sap.sse.common.Util.Pair getMaximumSpeedOverGround(Competitor competitor, + TimePoint timePoint) { com.sap.sse.common.Util.Pair result = null; // TODO should we ensure that competitor participated in all race columns? for (TrackedRace trackedRace : getTrackedRaces()) { @@ -771,16 +809,17 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW if (!markPassings.isEmpty()) { TimePoint from = markPassings.first().getTimePoint(); TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { + if (timePoint.after(markPassings.last().getTimePoint()) && markPassings.last() + .getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { // stop counting when competitor finished the race to = markPassings.last().getTimePoint(); } else { to = timePoint; } - com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor).getMaximumSpeedOverGround(from, to); - if (result == null || result.getB() == null || - (maxSpeed != null && maxSpeed.getB() != null && maxSpeed.getB().compareTo(result.getB()) > 0)) { + com.sap.sse.common.Util.Pair maxSpeed = trackedRace.getTrack(competitor) + .getMaximumSpeedOverGround(from, to); + if (result == null || result.getB() == null || (maxSpeed != null && maxSpeed.getB() != null + && maxSpeed.getB().compareTo(result.getB()) > 0)) { result = maxSpeed; } } @@ -799,7 +838,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } return result; } - + @Override public Iterable getCompetitors() { final Iterable result; @@ -815,10 +854,11 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } return result; } - + @Override public Iterable getCompetitors(RaceColumn raceColumn, Fleet fleet) { - return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), getSuppressedCompetitors()); + return getCompetitorIterableSkippingSuppressed(getAllCompetitors(raceColumn, fleet), + getSuppressedCompetitors()); } /** @@ -838,7 +878,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW LockUtil.unlockAfterRead(suppressedCompetitorsLock); } } - + @Override public boolean isSuppressed(Competitor competitor) { LockUtil.lockForRead(suppressedCompetitorsLock); @@ -848,12 +888,12 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW LockUtil.unlockAfterRead(suppressedCompetitorsLock); } } - + @Override public void setSuppressed(Competitor competitor, boolean suppressed) { - if (competitor == null) { - throw new IllegalArgumentException("Cannot change suppression for a null competitor"); - } + if (competitor == null) { + throw new IllegalArgumentException("Cannot change suppression for a null competitor"); + } LockUtil.lockForWrite(suppressedCompetitorsLock); try { if (suppressed) { @@ -874,7 +914,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW TimePoint timePoint = delayToLiveInMillis == null ? now : now.minus(delayToLiveInMillis); return timePoint; } - + @Override public void raceLogEventAdded(RaceColumn raceColumn, RaceLogIdentifier raceLogIdentifier, RaceLogEvent event) { getRaceColumnListeners().notifyListenersAboutRaceLogEventAdded(raceColumn, raceLogIdentifier, event); @@ -887,14 +927,16 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } public String toString() { - return getName() + " " + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); + return getName() + " " + + (getDefaultCourseArea() != null ? getDefaultCourseArea().getName() : "") + " " + + (getScoringScheme() != null ? getScoringScheme().getType().name() : ""); } @Override public NumberOfCompetitorsInLeaderboardFetcher getNumberOfCompetitorsInLeaderboardFetcher() { return new NumberOfCompetitorsFetcherImpl(); } - + @Override public Pair getRaceColumnAndFleet(TrackedRace trackedRace) { for (final RaceColumn raceColumn : getRaceColumns()) { @@ -914,8 +956,10 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW Map competitorsAndTheirBoats = raceColumn.getAllCompetitorsAndTheirBoats(); allBoats.addAll(competitorsAndTheirBoats.values()); } - return Util.getDominantObject(StreamSupport.stream(allBoats.spliterator(), /* parallel */ false). - map(b->b.getBoatClass()).collect(Collectors.toList())); + return Util.getDominantObject(StreamSupport.stream(allBoats.spliterator(), /* parallel */ false) + .map(b -> b.getBoatClass()).collect(Collectors.toList())); } + + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java old mode 100755 new mode 100644 index b6e1e2af35c..5a94b862a3e --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java @@ -111,6 +111,10 @@ public class LeaderboardTotalRankComparator implements Comparator { double o2ScoreSum = getLeaderboard().getCarriedPoints(o2); Double o1MedalRaceScore = 0.0; Double o2MedalRaceScore = 0.0; + Double o1CarryForwardScoreInMedals = null; + Double o2CarryForwardScoreInMedals = null; + Double o1LastMedalScore = null; + Double o2LastMedalScore = null; // When a column has isStartsWithZeroScore, the competitor's score only need to be reset to zero if from there on // the competitor scored in this or any subsequent columns boolean needToResetO1ScoreUponNextValidResult = false; @@ -124,6 +128,8 @@ public class LeaderboardTotalRankComparator implements Comparator { // it is guaranteed to have equal ordering as all fleets that the competitor is assigned to within the key series. final Map> fleetWithCorrectOrderingForCompetitorBySeries = new HashMap<>(); int defaultFleetBasedComparisonResult = 0; // relevant if no authoritative fleet-based comparison result was determined; based on extreme fleet vs. no fleet comparison + int numberOfMedalRacesWonO1 = 0; + int numberOfMedalRacesWonO2 = 0; for (RaceColumn raceColumn : getLeaderboard().getRaceColumns()) { needToResetO1ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore(); needToResetO2ScoreUponNextValidResult = raceColumn.isStartsWithZeroScore(); @@ -178,10 +184,21 @@ public class LeaderboardTotalRankComparator implements Comparator { if (o2Score != null) { o2MedalRaceScore += o2Score; } + if (o1Score != null || o2Score != null) { + o1LastMedalScore = o1Score; + o2LastMedalScore = o2Score; + } + } else { + o1CarryForwardScoreInMedals = o1Score; + o2CarryForwardScoreInMedals = o2Score; } // similar to compareByFleet, however, tracking is not required; having medal race column points // (tracked or manual) is sufficient preemptiveColumnResult = compareByMedalRaceParticipation(o1Score, o2Score); + if (scoringScheme.isMedalWinAmountCriteria()) { + numberOfMedalRacesWonO1 += leaderboard.isWin(o1, raceColumn, timePoint) ? 1 : 0; + numberOfMedalRacesWonO2 += leaderboard.isWin(o2, raceColumn, timePoint) ? 1 : 0; + } } if (preemptiveColumnResult == 0 && raceColumn.isTotalOrderDefinedByFleet()) { final FleetComparisonResult compareByFleetResult = compareByFleet(raceColumn, o1, o2, fleetWithCorrectOrderingForCompetitorBySeries); @@ -205,18 +222,35 @@ public class LeaderboardTotalRankComparator implements Comparator { // break tie by sorting scores and looking for the first score difference. int result = compareByNumberOfRacesScored(o1Scores.size(), o2Scores.size()); if (result == 0) { - result = compareByScoreSum(o1ScoreSum, o2ScoreSum); + if (scoringScheme.isMedalWinAmountCriteria()) { + // if one reaches the target amount of races won then this has priority, else proceed with normal low + // points scoring (e.g., not enough races yet) + result = compareByMedalRacesWon(numberOfMedalRacesWonO1, numberOfMedalRacesWonO2); + } if (result == 0) { - result = compareByMedalRaceScore(o1MedalRaceScore, o2MedalRaceScore); + result = compareByScoreSum(o1ScoreSum, o2ScoreSum); if (result == 0) { - result = compareByBetterScore(o1, Collections.unmodifiableList(o1Scores), o2, Collections.unmodifiableList(o2Scores), timePoint); + if (scoringScheme.isCarryForwardInMedalsCriteria()) { + result = compareBySingleRaceColumnScore(o1CarryForwardScoreInMedals, o2CarryForwardScoreInMedals); + } if (result == 0) { - // compare by last race: - result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter, o1, o2); + if (scoringScheme.isLastMedalRaceCriteria()) { + result = compareBySingleRaceColumnScore(o1LastMedalScore, o2LastMedalScore); + } if (result == 0) { - result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint); + result = compareByMedalRaceScore(o1MedalRaceScore, o2MedalRaceScore); if (result == 0) { - result = compareByArbitraryButStableCriteria(o1, o2); + result = compareByBetterScore(o1, Collections.unmodifiableList(o1Scores), o2, Collections.unmodifiableList(o2Scores), timePoint); + if (result == 0) { + // compare by last race: + result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter, o1, o2); + if (result == 0) { + result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint); + if (result == 0) { + result = compareByArbitraryButStableCriteria(o1, o2); + } + } + } } } } @@ -226,6 +260,33 @@ public class LeaderboardTotalRankComparator implements Comparator { return result; } + /** + * Compares by the number of races won in the medal series. If one of the competitors reached the target of races + * won (defined by {@link ScoringScheme#getTargetAmountOfMedalRaceWins()} it is ranked better than the other. If + * none of the competitors reached the target, they are ranked equally even if one has more wins. This is because + * only reaching the exact number of wins counts for this criteria but not the general comparison by the number of + * wins. If both competitors exactly reached the target, they are also ranked equally (this can e.g. occur while + * entering score corrections). + */ + private int compareByMedalRacesWon(int numberOfMedalRacesWonO1, int numberOfMedalRacesWonO2) { + final int result; + final int targetAmount = scoringScheme.getTargetAmountOfMedalRaceWins(); + if (numberOfMedalRacesWonO1 >= targetAmount || numberOfMedalRacesWonO2 >= targetAmount) { + result = Integer.compare(numberOfMedalRacesWonO2, numberOfMedalRacesWonO1); + } else { + result = 0; + } + return result; + } + + /** + * Compares by the scores of a single race column. If only one of the competitors has a result this competitor is + * ranked better than the other one. + */ + private int compareBySingleRaceColumnScore(Double o1Score, Double o2Score) { + return Comparator.nullsLast((Double o1s, Double o2s)->getScoreComparator().compare(o1s, o2s)).compare(o1Score, o2Score); + } + private int compareByArbitraryButStableCriteria(Competitor o1, Competitor o2) { return o1.getName().compareTo(o2.getName()); } @@ -236,11 +297,13 @@ public class LeaderboardTotalRankComparator implements Comparator { */ private int compareByMedalRaceScore(Double o1MedalRaceScore, Double o2MedalRaceScore) { assert o1MedalRaceScore != null || o2MedalRaceScore == null; + final int result; if (o1MedalRaceScore != null) { - return getScoreComparator().compare(o1MedalRaceScore, o2MedalRaceScore); + result = getScoreComparator().compare(o1MedalRaceScore, o2MedalRaceScore); } else { - return 0; + result = 0; } + return result; } private static class FleetComparisonResult { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinTwoRaces.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinTwoRaces.java new file mode 100644 index 00000000000..575e652f03b --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointFirstToWinTwoRaces.java @@ -0,0 +1,57 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.ScoringSchemeType; + +/** + * This class is a derivation of the LowPointScoring scheme. The following additional rules apply: + *

    + *
  1. The first participant in the medal series, that wins two races is ranked as the first.
  2. + *
  3. Having 1 point in the carry forward column of the medal series explicitly counts as an additional win which + * requires the winner of the qualification to only need one additional win in the medals.
  4. + *
  5. If none of the participants in the medal series reached the target amount of 2 wins yet, the ranking is done + * based on the points only.
  6. + *
  7. If there is a carry forward column in the medal series, the carry forward points are used as the primary tie + * breaking criteria in case two participants in the medal series have the same overall score.
  8. + *
  9. If there is no carry forward column in the medal series or the carry forward score is not sufficient, the points + * of the last medal race are used as a secondary tie breaking criteria.
  10. + *
  11. This Scheme will change the default scoring factor for medal races to 1, usually 2 is used
  12. + *
      + */ +public class LowPointFirstToWinTwoRaces extends LowPoint { + private static final long serialVersionUID = 7072175334160798617L; + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.LOW_POINT_FIRST_TO_WIN_TWO_RACES; + } + + @Override + public boolean isMedalWinAmountCriteria() { + return true; + } + + @Override + public boolean isCarryForwardInMedalsCriteria() { + return true; + } + + @Override + public boolean isLastMedalRaceCriteria() { + return true; + } + + @Override + public int getTargetAmountOfMedalRaceWins() { + return 2; + } + + @Override + public double getScoreFactor(RaceColumn raceColumn) { + Double factor = raceColumn.getExplicitFactor(); + if (factor == null) { + factor = 1.0; + } + return factor; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java index bd41ca3d81a..9738718cca4 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/CompleteManeuverCurveWithEstimationData.java @@ -5,6 +5,7 @@ import com.sap.sailing.domain.common.Positioned; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; import com.sap.sse.datamining.annotations.Connector; @@ -96,6 +97,12 @@ public interface CompleteManeuverCurveWithEstimationData extends Timed, Position * boundaries, the boundaries of the curve with unstable course and speed are used. */ Bearing getRelativeBearingToNextMarkAfterManeuver(); + + Distance getDistanceToClosestMark(); + + Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees(); + + Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees(); /** * Gets whether a mark was crossed within the maneuver curve. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/GpsFixWithEstimationData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/GpsFixWithEstimationData.java new file mode 100644 index 00000000000..e0126209f4a --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/GpsFixWithEstimationData.java @@ -0,0 +1,20 @@ +package com.sap.sailing.domain.maneuverdetection; + +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; + +/** + * + * @author Vladislav Chumak (D069712) + * + */ +public interface GpsFixWithEstimationData extends GPSFixMoving { + + Wind getWind(); + + Bearing getRelativeBearingToNextMarkAfterManeuver(); + + Distance getDistanceToClosestMark(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java index b31f723b038..dc7a9118502 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetector.java @@ -36,50 +36,4 @@ public interface ManeuverDetector { */ List detectManeuvers(); - /** - * Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already - * include the calculated boundaries of each complete maneuvering spot, this method operates in a very - * performance-efficient manner. - * - * @param maneuverCurves - * The maneuver curves from which the maneuvers shall be derived - * @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver - * curves list is also empty. - */ - List detectManeuvers(Iterable maneuverCurves); - - /** - * Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this - * {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the - * complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and - * "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy. - * - * @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves. - * @see CompleteManeuverCurve - * @see ManeuverDetector - */ - List detectCompleteManeuverCurves(); - - /** - * Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs - * significantly faster than {@link #detectCompleteManeuverCurves()}. - * - * @param maneuvers - * The maneuvers to parse into complete maneuver curves - * @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves - * derived from provided maneuvers. - * @see CompleteManeuverCurve - * @see Maneuver - */ - List getCompleteManeuverCurves(Iterable maneuvers); - - /** - * Converts provided {@link CompleteManeuverCurve}-instances into - * {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to - * {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within - * {@link #detectManeuvers()}. - */ - List getCompleteManeuverCurvesWithEstimationData( - Iterable maneuverCurves); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java new file mode 100644 index 00000000000..3724d130b25 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/ManeuverDetectorWithEstimationDataSupport.java @@ -0,0 +1,61 @@ +package com.sap.sailing.domain.maneuverdetection; + +import java.util.List; + +import com.sap.sailing.domain.tracking.CompleteManeuverCurve; +import com.sap.sailing.domain.tracking.Maneuver; + +/** + * A maneuver detector which additional support for management of estimation data for wind estimation. + * + * @author Vladislav Chumak (D069712) + * @see ManeuverDetector + * + */ +public interface ManeuverDetectorWithEstimationDataSupport extends ManeuverDetector { + /** + * Derives maneuvers from the provided {@code maneuverCurves}. Since the provided complete maneuver curves already + * include the calculated boundaries of each complete maneuvering spot, this method operates in a very + * performance-efficient manner. + * + * @param maneuverCurves + * The maneuver curves from which the maneuvers shall be derived + * @return The maneuvers derived from the provided maneuver curves. The list gets empty, if the provided maneuver + * curves list is also empty. + */ + List detectManeuvers(Iterable maneuverCurves); + + /** + * Detects the complete maneuver curves performed within a GPS-track of the competitor associated with this + * {@link ManeuverDetector}-instance. In contrast to maneuvers determined by {@link #detectManeuvers()}, the + * complete maneuver curves are not subject to any splitting logic for maneuvers with multiple "tacking" and + * "jibing". See {@link ManeuverDetector} description for more info regarding the detection strategy. + * + * @return an empty list if no maneuver spots were detected, otherwise the list with detected maneuver curves. + * @see CompleteManeuverCurve + * @see ManeuverDetector + */ + List detectCompleteManeuverCurves(); + + /** + * Parses {@link CompleteManeuverCurve}-instances from provided {@link Maneuver}-instances. This method performs + * significantly faster than {@link #detectCompleteManeuverCurves()}. + * + * @param maneuvers + * The maneuvers to parse into complete maneuver curves + * @return an empty list if provided maneuvers list is empty, otherwise the list with complete maneuver curves + * derived from provided maneuvers. + * @see CompleteManeuverCurve + * @see Maneuver + */ + List getCompleteManeuverCurves(Iterable maneuvers); + + /** + * Converts provided {@link CompleteManeuverCurve}-instances into + * {@link CompleteManeuverCurveWithEstimationData}-instances. For this, additional information to + * {@code maneuverCurves} is computed. This computation is regarded as complex as the computation within + * {@link #detectManeuvers()}. + */ + List getCompleteManeuverCurvesWithEstimationData( + Iterable maneuverCurves); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java new file mode 100644 index 00000000000..a5d63b7e908 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/AbstractManeuverDetectorImpl.java @@ -0,0 +1,155 @@ +package com.sap.sailing.domain.maneuverdetection.impl; + +import java.util.NavigableSet; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.BearingChangeAnalyzer; +import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; + +public abstract class AbstractManeuverDetectorImpl implements ManeuverDetector { + + /** + * Tracked race whose tracks are being processed for maneuver detection. + */ + protected final TrackedRace trackedRace; + + /** + * The competitor, whose maneuvers are being discovered + */ + protected final Competitor competitor; + + /** + * The track of competitor + */ + protected final GPSFixTrack track; + + /** + * Constructs maneuver detector which is supposed to be used for maneuver detection within the provided tracked race + * for provided competitor. + * + * @param trackedRace + * The tracked race whose maneuvers are supposed to be detected + * @param competitor + * The competitor, whose maneuvers shall be discovered + */ + public AbstractManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) { + this.trackedRace = trackedRace; + this.competitor = competitor; + this.track = trackedRace != null ? trackedRace.getTrack(competitor) : null; + } + + /** + * Gets track's start time point, end time point and the time point of last raw fix. + * + * @return {@code null} when there are no appropriate fixes contained within the analyzed track + */ + public TrackTimeInfo getTrackTimeInfo() { + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + TimePoint earliestTrackRecord = null; + TimePoint latestRawFixTimePoint = null; + MarkPassing crossedFinishLine = null; + // getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid + // deadlocks + final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint(); + if (lastWaypoint != null) { + trackedRace.lockForRead(markPassings); + try { + if (markPassings != null && !markPassings.isEmpty()) { + earliestTrackRecord = markPassings.iterator().next().getTimePoint(); + crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint); + } + } finally { + trackedRace.unlockAfterRead(markPassings); + } + } + if (earliestTrackRecord == null) { + GPSFixMoving firstRawFix = track.getFirstRawFix(); + if (firstRawFix != null) { + earliestTrackRecord = firstRawFix.getTimePoint(); + } + } + if (earliestTrackRecord != null) { + TimePoint latestTrackRecord; + if (crossedFinishLine != null) { + latestTrackRecord = crossedFinishLine.getTimePoint(); + } else { + final GPSFixMoving lastRawFix = track.getLastRawFix(); + if (lastRawFix != null) { + latestTrackRecord = lastRawFix.getTimePoint(); + latestRawFixTimePoint = latestTrackRecord; + } else { + latestTrackRecord = null; + } + } + if (latestTrackRecord != null) { + if (latestRawFixTimePoint == null) { + final GPSFixMoving lastRawFix = track.getLastRawFix(); + if (lastRawFix != null) { + latestRawFixTimePoint = lastRawFix.getTimePoint(); + } + } + if (latestRawFixTimePoint != null) { + if (!earliestTrackRecord.equals(latestTrackRecord)) { + return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint); + } + GPSFixMoving firstRawFix = track.getFirstRawFix(); + if (firstRawFix != null) { + return new TrackTimeInfo(firstRawFix.getTimePoint(), latestRawFixTimePoint, + latestRawFixTimePoint); + } + } + } + } + return null; + } + + /** + * Gets the number of cases, when the boats bow was headed through the wind coming from behind. + */ + protected int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) { + BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; + int numberOfJibes = wind == null ? 0 + : bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(), + maneuverBoundaries.getDirectionChangeInDegrees(), + maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing()); + return numberOfJibes; + } + + /** + * Gets the number of cases, when the boats bow was headed through the wind coming from the front. + */ + protected int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) { + BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; + int numberOfTacks = wind == null ? 0 + : bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(), + maneuverBoundaries.getDirectionChangeInDegrees(), + maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom()); + return numberOfTacks; + } + + /** + * Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}. + */ + protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) { + return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD; + } + + /** + * Gets the approximated duration of the maneuver main curve considering the boat class of the competitor. + */ + protected Duration getApproximateManeuverDuration() { + return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration(); + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java index 38ac9040bbc..89c43715903 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/CompleteManeuverCurveWithEstimationDataImpl.java @@ -6,6 +6,7 @@ import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimat import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData; import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; /** * @@ -24,13 +25,16 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane private final Bearing relativeBearingToNextMarkBeforeManeuver; private final Bearing relativeBearingToNextMarkAfterManeuver; private final boolean markPassing; - private Position position; + private final Position position; + private final Distance distanceToClosestMark; + private final Double deviationOfManeuverAngleFromTargetTackAngleInDegrees; + private final Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees; public CompleteManeuverCurveWithEstimationDataImpl(Position position, ManeuverMainCurveWithEstimationData mainCurve, ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed, Wind wind, int tackingCount, int jibingCount, boolean maneuverStartsByRunningAwayFromWind, Bearing relativeBearingToNextMarkBeforeManeuver, Bearing relativeBearingToNextMarkAfterManeuver, - boolean markPassing) { + boolean markPassing, Distance distanceToClosestMark, Double deviationOfManeuverAngleFromTargetTackAngleInDegrees, Double deviationOfManeuverAngleFromTargetJibeAngleInDegrees) { this.position = position; this.mainCurve = mainCurve; this.curveWithUnstableCourseAndSpeed = curveWithUnstableCourseAndSpeed; @@ -41,6 +45,9 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane this.relativeBearingToNextMarkBeforeManeuver = relativeBearingToNextMarkBeforeManeuver; this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver; this.markPassing = markPassing; + this.distanceToClosestMark = distanceToClosestMark; + this.deviationOfManeuverAngleFromTargetTackAngleInDegrees = deviationOfManeuverAngleFromTargetTackAngleInDegrees; + this.deviationOfManeuverAngleFromTargetJibeAngleInDegrees = deviationOfManeuverAngleFromTargetJibeAngleInDegrees; } @Override @@ -92,5 +99,20 @@ public class CompleteManeuverCurveWithEstimationDataImpl implements CompleteMane public Position getPosition() { return position; } + + @Override + public Distance getDistanceToClosestMark() { + return distanceToClosestMark; + } + + @Override + public Double getDeviationOfManeuverAngleFromTargetTackAngleInDegrees() { + return deviationOfManeuverAngleFromTargetTackAngleInDegrees; + } + + @Override + public Double getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees() { + return deviationOfManeuverAngleFromTargetJibeAngleInDegrees; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/GpsFixWithEstimationDataImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/GpsFixWithEstimationDataImpl.java new file mode 100644 index 00000000000..a9566317518 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/GpsFixWithEstimationDataImpl.java @@ -0,0 +1,48 @@ +package com.sap.sailing.domain.maneuverdetection.impl; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.maneuverdetection.GpsFixWithEstimationData; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.TimePoint; + +/** + * + * @author Vladislav Chumak (D069712) + * + */ +public class GpsFixWithEstimationDataImpl extends GPSFixMovingImpl implements GpsFixWithEstimationData { + + private static final long serialVersionUID = -6952863819352430365L; + + private Wind wind; + private Bearing relativeBearingToNextMarkAfterManeuver; + private Distance distanceToClosestMark; + + public GpsFixWithEstimationDataImpl(Position position, TimePoint timePoint, SpeedWithBearing speedWithBearing, + Wind wind, Bearing relativeBearingToNextMarkAfterManeuver, Distance distanceToClosestMark) { + super(position, timePoint, speedWithBearing); + this.wind = wind; + this.relativeBearingToNextMarkAfterManeuver = relativeBearingToNextMarkAfterManeuver; + this.distanceToClosestMark = distanceToClosestMark; + } + + @Override + public Wind getWind() { + return wind; + } + + @Override + public Bearing getRelativeBearingToNextMarkAfterManeuver() { + return relativeBearingToNextMarkAfterManeuver; + } + + @Override + public Distance getDistanceToClosestMark() { + return distanceToClosestMark; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java new file mode 100644 index 00000000000..26de57c6b24 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/LowGPSSamplingRateManeuverDetectorImpl.java @@ -0,0 +1,120 @@ +package com.sap.sailing.domain.maneuverdetection.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.CourseChange; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl; +import com.sap.sailing.domain.tracking.impl.ManeuverWithCoarseGrainedBoundariesImpl; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; + +/** + * Maneuver detector implementation for GPS tracks with extremely low sampling rate such as 1 fix per 30 seconds. + * + * @author Vladislav Chumak (D069712) + * + */ +public class LowGPSSamplingRateManeuverDetectorImpl extends AbstractManeuverDetectorImpl implements ManeuverDetector { + + public LowGPSSamplingRateManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) { + super(trackedRace, competitor); + } + + @Override + public List detectManeuvers() { + List result = new ArrayList<>(); + TrackTimeInfo startAndEndTimePoints = getTrackTimeInfo(); + if (startAndEndTimePoints != null) { + ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace, + competitor); + Iterable approximatedFixes = approximatedFixesCalculator.approximate( + startAndEndTimePoints.getTrackStartTimePoint(), startAndEndTimePoints.getTrackEndTimePoint()); + if (Util.size(approximatedFixes) > 2) { + Iterator approximationPointsIter = approximatedFixes.iterator(); + GPSFixMoving previous = approximationPointsIter.next(); + GPSFixMoving current = approximationPointsIter.next(); + // the bearings in these variables are between approximation points + do { + GPSFixMoving next = approximationPointsIter.next(); + SpeedWithBearing speedWithBearingOnApproximationFromPreviousToCurrent = previous + .getSpeedAndBearingRequiredToReach(current); + SpeedWithBearing speedWithBearingOnApproximationFromCurrentToNext = current + .getSpeedAndBearingRequiredToReach(next); + CourseChange courseChange = speedWithBearingOnApproximationFromPreviousToCurrent + .getCourseChangeRequiredToReach(speedWithBearingOnApproximationFromCurrentToNext); + speedWithBearingOnApproximationFromPreviousToCurrent = speedWithBearingOnApproximationFromCurrentToNext; + Maneuver maneuver = createManeuverFromGroupOfCourseChanges(competitor, + speedWithBearingOnApproximationFromPreviousToCurrent, current, + speedWithBearingOnApproximationFromCurrentToNext, courseChange.getCourseChangeInDegrees()); + result.add(maneuver); + previous = current; + current = next; + } while (approximationPointsIter.hasNext()); + } + } + return result; + } + + private Maneuver createManeuverFromGroupOfCourseChanges(Competitor competitor, + SpeedWithBearing speedWithBearingOnApproximationAtBeginning, GPSFixMoving currentFix, + SpeedWithBearing speedWithBearingOnApproximationAtEnd, double totalCourseChangeInDegrees) { + TimePoint maneuverTimePoint = currentFix.getTimePoint(); + Position maneuverPosition = currentFix.getPosition(); + final Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint); + Tack tackAfterManeuver = null; + try { + tackAfterManeuver = wind == null ? null + : trackedRace.getTack(maneuverPosition, maneuverTimePoint, + speedWithBearingOnApproximationAtEnd.getBearing()); + } catch (NoWindException e) { + } + ManeuverType maneuverType; + ManeuverCurveBoundaries maneuverCurve = new ManeuverCurveBoundariesImpl( + maneuverTimePoint.minus(getApproximateManeuverDuration().divide(2)), + maneuverTimePoint.plus(getApproximateManeuverDuration().times(3.0)), + speedWithBearingOnApproximationAtBeginning, speedWithBearingOnApproximationAtEnd, + totalCourseChangeInDegrees, + speedWithBearingOnApproximationAtBeginning.compareTo(speedWithBearingOnApproximationAtBeginning) < 0 + ? speedWithBearingOnApproximationAtBeginning : speedWithBearingOnApproximationAtEnd); + + if (wind != null) { + if (getNumberOfTacks(maneuverCurve, wind) > 0) { + maneuverType = ManeuverType.TACK; + } else if (getNumberOfJibes(maneuverCurve, wind) > 0) { + maneuverType = ManeuverType.JIBE; + } else { + // heading up or bearing away + Bearing windBearing = wind.getBearing(); + Bearing toWindBeforeManeuver = windBearing + .getDifferenceTo(speedWithBearingOnApproximationAtBeginning.getBearing()); + Bearing toWindAfterManeuver = windBearing + .getDifferenceTo(speedWithBearingOnApproximationAtEnd.getBearing()); + maneuverType = Math.abs(toWindBeforeManeuver.getDegrees()) < Math.abs(toWindAfterManeuver.getDegrees()) + ? ManeuverType.HEAD_UP : ManeuverType.BEAR_AWAY; + } + } else { + // no wind information; marking as UNKNOWN + maneuverType = ManeuverType.UNKNOWN; + } + Maneuver maneuver = new ManeuverWithCoarseGrainedBoundariesImpl(maneuverType, tackAfterManeuver, + maneuverPosition, maneuverTimePoint, maneuverCurve); + return maneuver; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java index 0a32506111d..a9ba715c9e4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorImpl.java @@ -5,10 +5,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; -import java.util.NavigableSet; import java.util.function.Predicate; import java.util.logging.Logger; -import java.util.stream.Collectors; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; @@ -21,16 +19,14 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.Wind; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; -import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; +import com.sap.sailing.domain.maneuverdetection.ApproximatedFixesCalculator; import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; -import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.ManeuverLoss; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.SpeedWithBearingStep; import com.sap.sailing.domain.tracking.SpeedWithBearingStepsIterable; @@ -56,7 +52,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; * @see ManeuverDetector * */ -public class ManeuverDetectorImpl implements ManeuverDetector { +public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl { private static final Logger logger = Logger.getLogger(ManeuverDetectorImpl.class.getName()); @@ -64,7 +60,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * Defines the maximal absolute course change velocity in degrees per second that shall be regarded as a stable * course. */ - private static final double MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS = 2; + private static final double MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS = 1; /** * Defines the absolute course change in degrees between bearing steps to ignore in order to shorten the @@ -72,35 +68,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector { */ private static final double MIN_ANGULAR_VELOCITY_FOR_MAIN_CURVE_BOUNDARIES_IN_DEGREES_PER_SECOND = 0.2; - /** - * Defines the course change limit toward opposite direction related to the direction of maneuver main curve. If - * speed maxima or stable bearing analysis produce a curve extension which exceeds this limit, the extension gets - * rejected. - */ - private static final double MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES = 15.0; - - /** - * Tracked race whose tracks are being processed for maneuver detection. - */ - protected final TrackedRace trackedRace; - - /** - * The competitor, whose maneuvers are being discovered - */ - protected final Competitor competitor; - - /** - * The track of competitor - */ - protected final GPSFixTrack track; - /** * Constructor for unit tests only. */ public ManeuverDetectorImpl() { - trackedRace = null; - competitor = null; - track = null; + super(null, null); } /** @@ -113,9 +85,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * The competitor, whose maneuvers shall be discovered */ public ManeuverDetectorImpl(TrackedRace trackedRace, Competitor competitor) { - this.trackedRace = trackedRace; - this.competitor = competitor; - this.track = trackedRace.getTrack(competitor); + super(trackedRace, competitor); } @Override @@ -123,355 +93,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return getAllManeuversFromManeuverSpots(detectManeuverSpots()); } - @Override - public List detectManeuvers(Iterable maneuverCurves) { - List maneuvers = new ArrayList<>(); - for (CompleteManeuverCurve maneuverCurve : maneuverCurves) { - TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); - Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); - Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint); - maneuvers.addAll(determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind, - maneuverCurve.getMarkPassing())); - } - return maneuvers; - } - - @Override - public List detectCompleteManeuverCurves() { - List maneuverSpots = detectManeuverSpots(); - return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null) - .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); - } - - @Override - public List getCompleteManeuverCurves(Iterable maneuvers) { - List result = new ArrayList<>(); - CompleteManeuverCurve curveToAdd = null; - boolean previousManeuverCouldBelongToSameCurve = false; - Maneuver previousManeuver = null; - for (Maneuver maneuver : maneuvers) { - boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE - || maneuver.isMarkPassing() - && (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE); - if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve - && previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter() - .equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()) - && previousManeuver.getToSide() == maneuver.getToSide()) { - curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver); - } else { - if (curveToAdd != null) { - result.add(curveToAdd); - } - curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver); - } - previousManeuver = maneuver; - previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve; - } - if (curveToAdd != null) { - result.add(curveToAdd); - } - return result; - } - - /** - * Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused - * for the resulting complete maneuver curve. - * - * @see CompleteManeuverCurve - * @see Maneuver - */ - private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) { - ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps( - maneuver.getMainCurveBoundaries().getTimePointBefore(), - maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(), - maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(), - maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(), - maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(), - maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(), - getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(), - maneuver.getMainCurveBoundaries().getTimePointAfter())); - return new CompleteManeuverCurveImpl(mainCurveBoundaries, - maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing()); - } - - /** - * Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with - * unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely - * from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly. - */ - private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve, - Maneuver maneuver) { - ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = computeManeuverMainCurveDetails( - maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), - maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide()); - if (mainCurveDetails == null) { - return maneuverCurve; - } - ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), - maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(), - maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees() - + maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed() - .compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0 - ? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed() - : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()); - return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries, - maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing()); - } - - @Override - public List getCompleteManeuverCurvesWithEstimationData( - Iterable maneuverCurves) { - List result = new ArrayList<>(); - - CompleteManeuverCurve previousManeuverCurve = null; - CompleteManeuverCurve currentManeuverCurve = null; - for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) { - if (currentManeuverCurve != null) { - CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData( - currentManeuverCurve, previousManeuverCurve, nextManeuverCurve); - result.add(maneuverCurveWithEstimationData); - } - previousManeuverCurve = currentManeuverCurve; - currentManeuverCurve = nextManeuverCurve; - } - if (currentManeuverCurve != null) { - CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData( - currentManeuverCurve, previousManeuverCurve, null); - result.add(maneuverCurveWithEstimationData); - } - return result; - } - - /** - * Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The - * computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as - * computationally-intensive. - */ - private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData( - CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve, - CompleteManeuverCurve nextManeuverCurve) { - Bearing courseAtMaxTurningRate = null; - SpeedWithBearingStep stepWithLowestSpeed = null; - SpeedWithBearingStep stepWithHighestSpeed = null; - SpeedWithBearingStep stepWithMaxTurningRate = null; - SpeedWithBearingStep previousStep = null; - for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) { - if (stepWithLowestSpeed == null - || stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) { - stepWithLowestSpeed = step; - } - if (stepWithHighestSpeed == null - || stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) { - stepWithHighestSpeed = step; - } - if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate - .getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) { - stepWithMaxTurningRate = step; - courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing() - .add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2)); - } - previousStep = step; - } - int gpsFixCountWithinMainCurve = 0; - int gpsFixCountWithinWholeCurve = 0; - int gpsFixesCountFromPreviousManeuver = 0; - int gpsFixesCountToNextManeuver = 0; - try { - track.lockForRead(); - boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve - .getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter() - .before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); - boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve - .getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore() - .after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); - for (GPSFixMoving fix : track.getFixes( - considerPreviousManeuver - ? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() - .getTimePointAfter() - : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), - !considerPreviousManeuver, - considerNextManeuver - ? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() - .getTimePointBefore() - : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), - !considerNextManeuver)) { - if (fix.getTimePoint().before( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) { - ++gpsFixesCountFromPreviousManeuver; - } else if (fix.getTimePoint().after( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) { - ++gpsFixesCountToNextManeuver; - } else { - if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore()) - && !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) { - ++gpsFixCountWithinMainCurve; - } - ++gpsFixCountWithinWholeCurve; - } - } - } finally { - track.unlockAfterRead(); - } - - ManeuverLoss projectedManeuverLoss = getManeuverLoss(maneuverCurve.getMainCurveBoundaries()); - Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore() - .travel(maneuverCurve.getMainCurveBoundaries().getDuration()); - Distance distanceSailedWithinManeuver = track.getDistanceTraveled( - maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), - maneuverCurve.getMainCurveBoundaries().getTimePointAfter()); - Duration longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes( - maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), - maneuverCurve.getMainCurveBoundaries().getTimePointAfter()); - ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl( - maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), - maneuverCurve.getMainCurveBoundaries().getTimePointAfter(), - maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(), - maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(), - maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(), - stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(), - stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(), - maneuverCurve.getMainCurveBoundaries().getTimePoint(), - maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate, - distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering, - projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), - Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees()) - / maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(), - gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes); - projectedManeuverLoss = getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()); - distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() - .getSpeedWithBearingBefore() - .travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration()); - distanceSailedWithinManeuver = track.getDistanceTraveled( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); - longestGpsFixIntervalBetweenTwoFixes = track.getLongestIntervalBetweenTwoFixes( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); - TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null ? getTrackTimeInfo() - : null; - Pair durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( - previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint() - : previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() - .getTimePointAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); - Pair durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), - nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint() - : nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); - Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL; - GPSFixMoving lastManeuverFix = track.getLastFixAtOrBefore( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); - if (lastManeuverFix != null) { - GPSFixMoving firstFixAfterLastManeuverFix = track.getFirstFixAfter(lastManeuverFix.getTimePoint()); - if (firstFixAfterLastManeuverFix != null) { - intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint() - .until(firstFixAfterLastManeuverFix.getTimePoint()); - } - } - Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL; - GPSFixMoving firstManeuverFix = track.getFirstFixAtOrAfter( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); - if (firstManeuverFix != null) { - GPSFixMoving lastFixBeforeFirstManeuverFix = track.getLastFixBefore(firstManeuverFix.getTimePoint()); - if (lastFixBeforeFirstManeuverFix != null) { - intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint() - .until(firstManeuverFix.getTimePoint()); - } - } - ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(), - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(), - durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(), - gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(), - durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver, - projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering, - projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve, - longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix, - intervalBetweenFirstFixOfCurveAndPreviousFix); - TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); - Position maneuverPosition = track.getEstimatedPosition(maneuverTimePoint, /* extrapolate */false); - Wind wind = trackedRace.getWind(maneuverPosition, maneuverTimePoint); - int numberOfJibes = getNumberOfJibes(mainCurve, wind); - int numberOfTacks = getNumberOfTacks(mainCurve, wind); - boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees() - - 180) * mainCurve.getDirectionChangeInDegrees() < 0; - Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve - .getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing()); - Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark( - maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve - .getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing()); - return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve, - curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes, - maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver, - relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing()); - } - - /** - * Calculates the duration and avg speed with avg course based on the competitor's track within the provided time - * range. - */ - private Pair calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from, - TimePoint to) { - Duration duration = from.until(to); - Position fromPosition = track.getEstimatedPosition(from, false); - Position toPosition = track.getEstimatedPosition(to, false); - Distance distance = fromPosition.getDistance(toPosition); - Bearing bearing = fromPosition.getBearingGreatCircle(toPosition); - Speed speed = distance.inTime(Math.abs(duration.asMillis())); - SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing); - return new Pair<>(duration, avgSpeedWithBearing); - } - - /** - * Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative - * bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course. - */ - private Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) { - Bearing result = null; - TrackedLegOfCompetitor legAfter = trackedRace.getTrackedLeg(competitor, timePoint); - if (legAfter != null && legAfter.getLeg().getTo() != null) { - Position nextMarkPosition = trackedRace.getApproximatePosition(legAfter.getLeg().getTo(), timePoint); - Position maneuverEndPosition = track.getEstimatedPosition(timePoint, false); - Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition); - result = absoluteBearing.getDifferenceTo(boatCourse); - } - return result; - } - - /** - * Gets the number of cases, when the boats bow was headed through the wind coming from behind. - */ - private int getNumberOfJibes(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) { - BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; - int numberOfJibes = wind == null ? 0 - : bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(), - maneuverBoundaries.getDirectionChangeInDegrees(), - maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getBearing()); - return numberOfJibes; - } - - /** - * Gets the number of cases, when the boats bow was headed through the wind coming from the front. - */ - private int getNumberOfTacks(ManeuverCurveBoundaries maneuverBoundaries, Wind wind) { - BearingChangeAnalyzer bearingChangeAnalyzer = BearingChangeAnalyzer.INSTANCE; - int numberOfTacks = wind == null ? 0 - : bearingChangeAnalyzer.didPass(maneuverBoundaries.getSpeedWithBearingBefore().getBearing(), - maneuverBoundaries.getDirectionChangeInDegrees(), - maneuverBoundaries.getSpeedWithBearingAfter().getBearing(), wind.getFrom()); - return numberOfTacks; - } - /** * Detects maneuver spots performed within a GPS-track of the competitor associated with this * {@link ManeuverDetector}-instance. See {@link ManeuverDetector} description for more info regarding the detection @@ -490,64 +111,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return Collections.emptyList(); } - /** - * Gets track's start time point, end time point and the time point of last raw fix. - * - * @return {@code null} when there are no appropriate fixes contained within the analyzed track - */ - public TrackTimeInfo getTrackTimeInfo() { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - TimePoint earliestTrackRecord = null; - TimePoint latestRawFixTimePoint = null; - MarkPassing crossedFinishLine = null; - // getLastWaypoint() will wait for a read lock on the course; do this outside the synchronized block to avoid - // deadlocks - final Waypoint lastWaypoint = trackedRace.getRace().getCourse().getLastWaypoint(); - if (lastWaypoint != null) { - trackedRace.lockForRead(markPassings); - try { - if (markPassings != null && !markPassings.isEmpty()) { - earliestTrackRecord = markPassings.iterator().next().getTimePoint(); - crossedFinishLine = trackedRace.getMarkPassing(competitor, lastWaypoint); - } - } finally { - trackedRace.unlockAfterRead(markPassings); - } - } - if (earliestTrackRecord == null) { - GPSFixMoving firstRawFix = track.getFirstRawFix(); - if (firstRawFix != null) { - earliestTrackRecord = firstRawFix.getTimePoint(); - } - } - if (earliestTrackRecord != null) { - TimePoint latestTrackRecord; - if (crossedFinishLine != null) { - latestTrackRecord = crossedFinishLine.getTimePoint(); - } else { - final GPSFixMoving lastRawFix = track.getLastRawFix(); - if (lastRawFix != null) { - latestTrackRecord = lastRawFix.getTimePoint(); - latestRawFixTimePoint = latestTrackRecord; - } else { - latestTrackRecord = null; - } - } - if (latestTrackRecord != null) { - if (latestRawFixTimePoint == null) { - final GPSFixMoving lastRawFix = track.getLastRawFix(); - if (lastRawFix != null) { - latestRawFixTimePoint = lastRawFix.getTimePoint(); - } - } - if (latestRawFixTimePoint != null) { - return new TrackTimeInfo(earliestTrackRecord, latestTrackRecord, latestRawFixTimePoint); - } - } - } - return null; - } - /** * Detects the maneuver spots with corresponding maneuvers within provided time frame. See step 1ff. in * {@link ManeuverDetector} description. @@ -566,13 +129,12 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * @return an empty list if no maneuver spots are detected for competitor between from and * to, or else the list of maneuver spots with corresponding maneuvers detected. */ - protected List detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) { - return detectManeuvers( - trackedRace.approximate(competitor, - trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass() - .getMaximumDistanceForCourseApproximation(), - earliestManeuverStart, latestManeuverEnd), - earliestManeuverStart, latestManeuverEnd); + public List detectManeuvers(TimePoint earliestManeuverStart, TimePoint latestManeuverEnd) { + ApproximatedFixesCalculator approximatedFixesCalculator = new ApproximatedFixesCalculatorImpl(trackedRace, + competitor); + Iterable approximatedFixes = approximatedFixesCalculator.approximate(earliestManeuverStart, + latestManeuverEnd); + return detectManeuvers(approximatedFixes, earliestManeuverStart, latestManeuverEnd); } /** @@ -622,13 +184,6 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } - /** - * Maps the provided {@code courseChangeInDegrees} from {@link Bearing} to {@link NauticalSide}. - */ - protected NauticalSide getDirectionOfCourseChange(double courseChangeInDegrees) { - return courseChangeInDegrees < 0 ? NauticalSide.PORT : NauticalSide.STARBOARD; - } - /** * Checks whether {@code currentFix} can be grouped together with the previous fixes in order to be regarded as a * single maneuver spot. For this, the {@code newCourseChangeDirection must match the direction of provided @@ -661,6 +216,16 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return true; } + protected List getAllManeuversFromManeuverSpots(List maneuverSpots) { + List maneuvers = new ArrayList<>(); + for (ManeuverSpot maneuverSpot : maneuverSpots) { + for (Maneuver maneuver : maneuverSpot.getManeuvers()) { + maneuvers.add(maneuver); + } + } + return maneuvers; + } + /** * Determines course change direction around the provided {@code fix} by means of * {@link #getSpeedWithBearingSteps(TimePoint, TimePoint)}. The course change analysis considers fixes within @@ -895,14 +460,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } else { Pair mainCurves = splitManeuverMainCurveByTimePoint( maneuverMainCurveDetails, firstPenaltyCircleCompletedAt); - if (mainCurves.getA() == null || mainCurves.getB() == null) { - // This should really not happen! - logger.warning( - "Maneuver detection has failed to process penalty circle maneuver correctly, because refinedPenaltyMainCurveDetails computation returned null. Race-Id: " - + trackedRace.getRace().getId() + ", Competitor: " + competitor.getName() - + ", Time point before maneuver: " - + maneuverUnstableCourseAndSpeedBoundaries.getTimePointBefore()); - } else { + if (mainCurves.getA() != null && mainCurves.getB() != null) { maneuversAlreadyAdded = true; Pair maneuverUnstableCourseAndSpeedBoundariesPair = splitManeuverCurveWithStableSpeedAndCourseByTimePoint( maneuverUnstableCourseAndSpeedBoundaries, mainCurves.getA(), mainCurves.getB(), @@ -990,19 +548,50 @@ public class ManeuverDetectorImpl implements ManeuverDetector { SpeedWithBearingStepsIterable speedWithBearingSteps, TimePoint timePoint) { List stepsBefore = new ArrayList<>(); List stepsAfter = new ArrayList<>(); + SpeedWithBearingStep lastEntry = null; for (SpeedWithBearingStep entry : speedWithBearingSteps) { if (!entry.getTimePoint().after(timePoint)) { - if (stepsBefore.isEmpty()) { - // First bearing step supposed to have 0 as course change as - // it does not have any previous steps with bearings to compute bearing difference. - // If the condition is not met, the existing code which uses ManeuverBearingStep class will break. - entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0); - } stepsBefore.add(entry); } if (!entry.getTimePoint().before(timePoint)) { if (stepsAfter.isEmpty()) { - entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, 0.0); + // First step supposed to have 0 as course change as it does not have any previous steps to compute + // bearing difference. If the condition is not met, the existing code which uses + // SpeedWithBearingStepsIterable class will break. + if (lastEntry != null && lastEntry.getTimePoint().before(timePoint)) { + // If there is not any step located at the splitting time point, we need to retrieve the + // interpolated speed with bearing at time point in order to produce boundary step for both step + // sets at time point. If we will not do it, the course change between the steps split by + // splitting time point will be lost. + SpeedWithBearing speedWithBearing = track.getEstimatedSpeed(timePoint); + if (speedWithBearing != null) { + double courseChangeAngleInDegrees = lastEntry.getSpeedWithBearing().getBearing() + .getDifferenceTo(speedWithBearing.getBearing(), + new DegreeBearingImpl(lastEntry.getCourseChangeInDegrees())) + .getDegrees(); + double turningRateInDegreesPerSecond = Math.abs( + courseChangeAngleInDegrees / lastEntry.getTimePoint().until(timePoint).asSeconds()); + SpeedWithBearingStep lastStepBefore = new SpeedWithBearingStepImpl(timePoint, + speedWithBearing, courseChangeAngleInDegrees, turningRateInDegreesPerSecond); + stepsBefore.add(lastStepBefore); + SpeedWithBearingStep firstStepAfter = new SpeedWithBearingStepImpl(timePoint, + speedWithBearing, 0.0, 0.0); + stepsAfter.add(firstStepAfter); + courseChangeAngleInDegrees = firstStepAfter.getSpeedWithBearing().getBearing() + .getDifferenceTo(speedWithBearing.getBearing(), + new DegreeBearingImpl(firstStepAfter.getCourseChangeInDegrees())) + .getDegrees(); + turningRateInDegreesPerSecond = Math.abs( + courseChangeAngleInDegrees / timePoint.until(entry.getTimePoint()).asSeconds()); + entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), + courseChangeAngleInDegrees, turningRateInDegreesPerSecond); + } + + } + if (stepsAfter.isEmpty()) { + entry = new SpeedWithBearingStepImpl(entry.getTimePoint(), entry.getSpeedWithBearing(), 0.0, + 0.0); + } } stepsAfter.add(entry); } @@ -1051,8 +640,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector { ManeuverCurveBoundaries maneuverUnstableCourseAndSpeedBoundaries, Wind wind, MarkPassing markPassing) { final Maneuver maneuver; final ManeuverType maneuverType; - Distance maneuverLoss = null; Tack tackAfterManeuver = null; + ManeuverLoss maneuverLoss = null; final Position maneuverPosition = track.getEstimatedPosition(maneuverMainCurveDetails.getTimePoint(), /* extrapolate */false); int numberOfJibes = getNumberOfJibes(maneuverMainCurveDetails, wind); @@ -1091,11 +680,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } else { tackAfterManeuver = null; } - maneuverLoss = getManeuverLoss(maneuverUnstableCourseAndSpeedBoundaries).getDistanceLost(); + maneuverLoss = getManeuverLoss(maneuverUnstableCourseAndSpeedBoundaries); maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver, - maneuverPosition, maneuverLoss, maneuverMainCurveDetails.getTimePoint(), + maneuverPosition, maneuverMainCurveDetails.getTimePoint(), maneuverMainCurveDetails.extractCurveBoundariesOnly(), maneuverUnstableCourseAndSpeedBoundaries, - maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing); + maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing, maneuverLoss); } else { // Logic for head-up and bear-away try { @@ -1109,10 +698,9 @@ public class ManeuverDetectorImpl implements ManeuverDetector { } catch (NoWindException e) { tackAfterManeuver = null; } - maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition, - maneuverLoss, maneuverMainCurveDetails.getTimePoint(), + maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverMainCurveDetails.getTimePoint(), maneuverMainCurveDetails.extractCurveBoundariesOnly(), maneuverUnstableCourseAndSpeedBoundaries, - maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing); + maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing, maneuverLoss); } return maneuver; } @@ -1125,16 +713,17 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * distance" is compared to the competitor's actual position at that time. This distance is returned as the result * of this method. */ - private ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) { + protected ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) { final GPSFixTrack track = trackedRace.getTrack(competitor); - SpeedWithBearing speedWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore(); - SpeedWithBearing speedAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter(); + SpeedWithBearing speedWithBearingWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore(); + SpeedWithBearing speedWithBearingAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter(); TimePoint timePointWhenSpeedStartedToDrop = maneuverBoundaries.getTimePointBefore(); TimePoint timePointWhenSpeedLevelledOffAfterManeuver = maneuverBoundaries.getTimePointAfter(); + Duration maneuverDuration = timePointWhenSpeedStartedToDrop.until(timePointWhenSpeedLevelledOffAfterManeuver); // For upwind/downwind legs, find the mean course between inbound and outbound course and project actual // and // extrapolated positions onto it: - Bearing middleManeuverAngle = speedWhenSpeedStartedToDrop.getBearing().middle(speedAfterManeuver.getBearing()); + Bearing middleManeuverAngle = speedWithBearingWhenSpeedStartedToDrop.getBearing().middle(speedWithBearingAfterManeuver.getBearing()); // extrapolate maximum speed before maneuver to time point of maximum speed after maneuver and project // resulting position // onto the average maneuver course; compare to the projected position actually reached at the time @@ -1142,7 +731,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { // maneuver: Position positionWhenSpeedStartedToDrop = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop, /* extrapolate */ false); - Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWhenSpeedStartedToDrop.travelTo( + Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWithBearingWhenSpeedStartedToDrop.travelTo( positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop, timePointWhenSpeedLevelledOffAfterManeuver); Position actualPositionAtTimePointOfMaxSpeedAfterManeuver = track @@ -1155,23 +744,16 @@ public class ManeuverDetectorImpl implements ManeuverDetector { .getDistance(projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver); Distance projectedDistanceSailedIfNotManeuvering = positionWhenSpeedStartedToDrop .getDistance(projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver); - return new ManeuverLoss(projectedDistanceSailed, projectedDistanceSailedIfNotManeuvering); + return new ManeuverLoss(projectedDistanceSailed, projectedDistanceSailedIfNotManeuvering, + positionWhenSpeedStartedToDrop, + actualPositionAtTimePointOfMaxSpeedAfterManeuver, maneuverDuration, speedWithBearingWhenSpeedStartedToDrop, + middleManeuverAngle); } protected Duration getDurationForDouglasPeuckerExtensionForMainCurveAnalysis(Duration approximateManeuverDuration) { return approximateManeuverDuration.divide(2); } - protected List getAllManeuversFromManeuverSpots(List maneuverSpots) { - List maneuvers = new ArrayList<>(); - for (ManeuverSpot maneuverSpot : maneuverSpots) { - for (Maneuver maneuver : maneuverSpot.getManeuvers()) { - maneuvers.add(maneuver); - } - } - return maneuvers; - } - /** * Starting at timePointBeforeManeuver, and assuming that the group of * approximatedFixesAndCourseChanges contains at least a tack and a jibe, finds the approximated fix's @@ -1241,8 +823,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * The target course change direction for the main curve to determine * @return The details of the maneuver main curve */ - private ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails(TimePoint timePointBeforeManeuver, - TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) { + protected ManeuverMainCurveDetailsWithBearingSteps computeManeuverMainCurveDetails( + TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver, NauticalSide maneuverDirection) { SpeedWithBearingStepsIterable stepsToAnalyze = getSpeedWithBearingSteps(timePointBeforeManeuver, timePointAfterManeuver); ManeuverMainCurveDetailsWithBearingSteps maneuverMainCurveDetails = computeManeuverMainCurve(stepsToAnalyze, @@ -1255,7 +837,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * the steps, performance costy call to {@link GPSFixTrack#getSpeedWithBearingSteps(TimePoint, TimePoint, Duration)} * is made. */ - private SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver, + protected SpeedWithBearingStepsIterable getSpeedWithBearingSteps(TimePoint timePointBeforeManeuver, TimePoint timePointAfterManeuver) { SpeedWithBearingStepsIterable stepsToAnalyze = track.getSpeedWithBearingSteps(timePointBeforeManeuver, timePointAfterManeuver); @@ -1272,10 +854,10 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * approximate the beginning time point of the maneuver, the speed maximum is determined throughout forward in time * iteration of speed steps starting from time point of main curve beginning. From the determined speed maximum, the * iteration continues until the point, when the bearing changes occur only with a maximum of - * {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is - * regarded as a stable course. The exiting time point of maneuver is approximated analogously by speed maximum - * determination throughout backward in time iteration of speed steps starting from time of main curve end, followed - * by a search for a point with stable course. + * {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as + * a stable course. The exiting time point of maneuver is approximated analogously by speed maximum determination + * throughout backward in time iteration of speed steps starting from time of main curve end, followed by a search + * for a point with stable course. * * @param maneuverMainCurveDetails * The details of the main curve, ideally computed by @@ -1324,8 +906,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * determined, the course changes get analyzed starting from {@code t'} until {@code (t -} * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration}{@code )} in order to * locate the point where the bearing starts to change with a rate of maximal - * {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is - * regarded as a stable course. + * {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as + * a stable course. * * @param maneuverMainCurveDetails * The details of the main curve, ideally computed by @@ -1356,25 +938,11 @@ public class ManeuverDetectorImpl implements ManeuverDetector { if (isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, maneuverStart)) { maneuverStart = null; } - TimePoint stableBearingAnalysisUntil = maneuverStart == null ? maneuverMainCurveDetails.getTimePointBefore() - : maneuverStart.getExtensionTimePoint(); + // Stable course analysis is considered as not necessary for preparation phase of maneuver because no + // oversteering is usually performed before maneuver Speed lowestSpeed = maneuverStart == null ? null : maneuverStart.getLowestSpeedWithinExtensionArea(); double courseChangeSinceManeuverMainCurveInDegrees = maneuverStart == null ? 0 : maneuverStart.getCourseChangeInDegreesWithinExtensionArea(); - stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, earliestTimePointForSpeedTrendAnalysis, - stableBearingAnalysisUntil); - ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed( - stepsToAnalyze, true, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS); - if (stableBearingExtension != null - && !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) { - maneuverStart = stableBearingExtension; - courseChangeSinceManeuverMainCurveInDegrees += stableBearingExtension - .getCourseChangeInDegreesWithinExtensionArea(); - if (lowestSpeed == null - || lowestSpeed.compareTo(stableBearingExtension.getLowestSpeedWithinExtensionArea()) > 0) { - lowestSpeed = stableBearingExtension.getLowestSpeedWithinExtensionArea(); - } - } return maneuverStart != null ? new ManeuverCurveBoundaryExtension(maneuverStart.getExtensionTimePoint(), maneuverStart.getSpeedWithBearingAtExtensionTimePoint(), @@ -1391,10 +959,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector { if (curveBoundaryExtension == null) { return false; } - return curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea() - * maneuverMainCurveDetails.getDirectionChangeInDegrees() < 0 - && Math.abs(curveBoundaryExtension - .getCourseChangeInDegreesWithinExtensionArea()) > MAX_COURSE_CHANGE_TOWARD_MANEUVER_OPPOSITE_DIRECTION_FOR_CURVE_EXTENSION_IN_DEGREES; + return Math.abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) > Math + .abs(curveBoundaryExtension.getCourseChangeInDegreesWithinExtensionArea()) / 3.0; } /** @@ -1409,8 +975,8 @@ public class ManeuverDetectorImpl implements ManeuverDetector { * determined, the course changes get analyzed starting from {@code t'} until {@code (t +} * {@link BoatClass#getApproximateManeuverDurationInMilliseconds() approx. maneuver duration} {@code * 3)} in order * to locate the point where the bearing starts to change with a rate of maximal - * {@value #MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS} degrees per second, which is - * regarded as a stable course. + * {@value #MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS} degrees per second, which is regarded as + * a stable course. * * @param maneuverMainCurveDetails * The details of the main curve, ideally computed by @@ -1450,7 +1016,7 @@ public class ManeuverDetectorImpl implements ManeuverDetector { stepsToAnalyze = getSpeedWithBearingStepsWithinTimeRange(stepsToAnalyze, stableBearingAnalysisFrom, latestTimePointForSpeedTrendAnalysis); ManeuverCurveBoundaryExtension stableBearingExtension = findStableBearingWithMaxAbsCourseChangeSpeed( - stepsToAnalyze, false, MAX_ABS_COURSE_CHANGE_IN_DEGREES_PER_SECOND_FOR_STABLE_BEARING_ANALYSIS); + stepsToAnalyze, false, MAX_TURNING_RATE_IN_DEG_PER_SECOND_FOR_STABLE_COURSE_ANALYSIS); if (stableBearingExtension != null && !isCourseChangeLimitExceededForCurveExtension(maneuverMainCurveDetails, stableBearingExtension)) { maneuverEnd = stableBearingExtension; @@ -1762,11 +1328,4 @@ public class ManeuverDetectorImpl implements ManeuverDetector { return new SpeedWithBearingStepsIterable(maneuverBearingSteps); } - /** - * Gets the approximated duration of the maneuver main curve considering the boat class of the competitor. - */ - protected Duration getApproximateManeuverDuration() { - return trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass().getApproximateManeuverDuration(); - } - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java new file mode 100644 index 00000000000..14d8b767aea --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverDetectorWithEstimationDataSupportDecoratorImpl.java @@ -0,0 +1,473 @@ +package com.sap.sailing.domain.maneuverdetection.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; +import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport; +import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData; +import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.domain.tracking.CompleteManeuverCurve; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.ManeuverLoss; +import com.sap.sailing.domain.tracking.SpeedWithBearingStep; +import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; +import com.sap.sailing.domain.tracking.impl.CompleteManeuverCurveImpl; +import com.sap.sailing.domain.tracking.impl.ManeuverCurveBoundariesImpl; +import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; +import com.sap.sse.common.Speed; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.DegreeBearingImpl; + +/** + * A decorator which adds support for management of estimation data for wind estimation to an existing maneuver detector + * implementation. + * + * @author Vladislav Chumak (D069712) + * @see ManeuverDetector + * + */ +public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl + implements ManeuverDetectorWithEstimationDataSupport { + + private final ManeuverDetectorImpl maneuverDetector; + private final PolarDataService polarDataService; + + public ManeuverDetectorWithEstimationDataSupportDecoratorImpl(ManeuverDetectorImpl maneuverDetector, + PolarDataService polarDataService) { + this.maneuverDetector = maneuverDetector; + this.polarDataService = polarDataService; + } + + @Override + public List detectManeuvers() { + return maneuverDetector.detectManeuvers(); + } + + @Override + public List detectManeuvers(Iterable maneuverCurves) { + List maneuvers = new ArrayList<>(); + for (CompleteManeuverCurve maneuverCurve : maneuverCurves) { + TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); + Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint, + /* extrapolate */false); + Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint); + maneuvers + .addAll(maneuverDetector.determineManeuversFromManeuverCurve(maneuverCurve.getMainCurveBoundaries(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries(), wind, + maneuverCurve.getMarkPassing())); + } + return maneuvers; + } + + @Override + public List detectCompleteManeuverCurves() { + List maneuverSpots = maneuverDetector.detectManeuverSpots(); + return maneuverSpots.stream().filter(maneuverSpot -> maneuverSpot.getManeuverCurve() != null) + .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); + } + + @Override + public List getCompleteManeuverCurves(Iterable maneuvers) { + List result = new ArrayList<>(); + CompleteManeuverCurve curveToAdd = null; + boolean previousManeuverCouldBelongToSameCurve = false; + Maneuver previousManeuver = null; + for (Maneuver maneuver : maneuvers) { + boolean maneuverCouldBelongToSameCurve = maneuver.getType() == ManeuverType.PENALTY_CIRCLE + || maneuver.isMarkPassing() + && (maneuver.getType() == ManeuverType.TACK || maneuver.getType() == ManeuverType.JIBE); + if (previousManeuverCouldBelongToSameCurve && maneuverCouldBelongToSameCurve + && previousManeuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter() + .equals(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()) + && previousManeuver.getToSide() == maneuver.getToSide()) { + curveToAdd = extendCompleteManeuverCurveWithManeuver(curveToAdd, maneuver); + } else { + if (curveToAdd != null) { + result.add(curveToAdd); + } + curveToAdd = convertManeuverToCompleteManeuverCurve(maneuver); + } + previousManeuver = maneuver; + previousManeuverCouldBelongToSameCurve = maneuverCouldBelongToSameCurve; + } + if (curveToAdd != null) { + result.add(curveToAdd); + } + return result; + } + + /** + * Converts the provided maneuver into {@link CompleteManeuverCurve}. The boundaries of provided maneuver are reused + * for the resulting complete maneuver curve. + * + * @see CompleteManeuverCurve + * @see Maneuver + */ + private CompleteManeuverCurve convertManeuverToCompleteManeuverCurve(Maneuver maneuver) { + ManeuverMainCurveDetailsWithBearingSteps mainCurveBoundaries = new ManeuverMainCurveDetailsWithBearingSteps( + maneuver.getMainCurveBoundaries().getTimePointBefore(), + maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getTimePoint(), + maneuver.getMainCurveBoundaries().getSpeedWithBearingBefore(), + maneuver.getMainCurveBoundaries().getSpeedWithBearingAfter(), + maneuver.getMainCurveBoundaries().getDirectionChangeInDegrees(), + maneuver.getMaxTurningRateInDegreesPerSecond(), maneuver.getMainCurveBoundaries().getLowestSpeed(), + maneuverDetector.getSpeedWithBearingSteps(maneuver.getMainCurveBoundaries().getTimePointBefore(), + maneuver.getMainCurveBoundaries().getTimePointAfter())); + return new CompleteManeuverCurveImpl(mainCurveBoundaries, + maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries(), maneuver.getMarkPassing()); + } + + /** + * Extends the end of provided maneuver curve with the end of provided maneuver. For this, the curve boundaries with + * unstable course and speed are merged by appending, whereas the maneuver main curve gets recalculated completely + * from scratch. The additional attributes such as, direction change and lowest speed get adjusted accordingly. + */ + private CompleteManeuverCurve extendCompleteManeuverCurveWithManeuver(CompleteManeuverCurve maneuverCurve, + Maneuver maneuver) { + ManeuverMainCurveDetailsWithBearingSteps mainCurveDetails = maneuverDetector.computeManeuverMainCurveDetails( + maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), + maneuver.getMainCurveBoundaries().getTimePointAfter(), maneuver.getToSide()); + if (mainCurveDetails == null) { + return maneuverCurve; + } + ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries = new ManeuverCurveBoundariesImpl( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), + maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(), + maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees() + + maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed() + .compareTo(maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()) > 0 + ? maneuver.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed() + : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed()); + return new CompleteManeuverCurveImpl(mainCurveDetails, maneuverCurveWithStableSpeedAndCourseBoundaries, + maneuverCurve.getMarkPassing() == null ? maneuver.getMarkPassing() : maneuverCurve.getMarkPassing()); + } + + @Override + public List getCompleteManeuverCurvesWithEstimationData( + Iterable maneuverCurves) { + List result = new ArrayList<>(); + + CompleteManeuverCurve previousManeuverCurve = null; + CompleteManeuverCurve currentManeuverCurve = null; + for (CompleteManeuverCurve nextManeuverCurve : maneuverCurves) { + if (currentManeuverCurve != null) { + CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData( + currentManeuverCurve, previousManeuverCurve, nextManeuverCurve); + result.add(maneuverCurveWithEstimationData); + } + previousManeuverCurve = currentManeuverCurve; + currentManeuverCurve = nextManeuverCurve; + } + if (currentManeuverCurve != null) { + CompleteManeuverCurveWithEstimationData maneuverCurveWithEstimationData = calculateCompleteManeuverCurveWithEstimationData( + currentManeuverCurve, previousManeuverCurve, null); + result.add(maneuverCurveWithEstimationData); + } + return result; + } + + /** + * Calculates a {@link CompleteManeuverCurveWithEstimationData}-instance for the provided {@code maneuverCurve}. The + * computation of additional information required by {@link CompleteManeuverCurveWithEstimationData} is regarded as + * computationally-intensive. + */ + private CompleteManeuverCurveWithEstimationData calculateCompleteManeuverCurveWithEstimationData( + CompleteManeuverCurve maneuverCurve, CompleteManeuverCurve previousManeuverCurve, + CompleteManeuverCurve nextManeuverCurve) { + Bearing courseAtMaxTurningRate = null; + SpeedWithBearingStep stepWithLowestSpeed = null; + SpeedWithBearingStep stepWithHighestSpeed = null; + SpeedWithBearingStep stepWithMaxTurningRate = null; + SpeedWithBearingStep previousStep = null; + for (SpeedWithBearingStep step : maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingSteps()) { + if (stepWithLowestSpeed == null + || stepWithLowestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) > 0) { + stepWithLowestSpeed = step; + } + if (stepWithHighestSpeed == null + || stepWithHighestSpeed.getSpeedWithBearing().compareTo(step.getSpeedWithBearing()) < 0) { + stepWithHighestSpeed = step; + } + if (previousStep != null && (stepWithMaxTurningRate == null || stepWithMaxTurningRate + .getTurningRateInDegreesPerSecond() < step.getTurningRateInDegreesPerSecond())) { + stepWithMaxTurningRate = step; + courseAtMaxTurningRate = previousStep.getSpeedWithBearing().getBearing() + .add(new DegreeBearingImpl(step.getCourseChangeInDegrees() / 2)); + } + previousStep = step; + } + int gpsFixCountWithinMainCurve = 0; + int gpsFixCountWithinWholeCurve = 0; + int gpsFixesCountFromPreviousManeuver = 0; + int gpsFixesCountToNextManeuver = 0; + try { + maneuverDetector.track.lockForRead(); + boolean considerPreviousManeuver = previousManeuverCurve != null && previousManeuverCurve + .getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter() + .before(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); + boolean considerNextManeuver = nextManeuverCurve != null && nextManeuverCurve + .getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore() + .after(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); + for (GPSFixMoving fix : maneuverDetector.track.getFixes( + considerPreviousManeuver + ? previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() + .getTimePointAfter() + : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), + !considerPreviousManeuver, + considerNextManeuver + ? nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() + .getTimePointBefore() + : maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), + !considerNextManeuver)) { + if (fix.getTimePoint().before( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore())) { + ++gpsFixesCountFromPreviousManeuver; + } else if (fix.getTimePoint().after( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter())) { + ++gpsFixesCountToNextManeuver; + } else { + if (!fix.getTimePoint().before(maneuverCurve.getMainCurveBoundaries().getTimePointBefore()) + && !fix.getTimePoint().after(maneuverCurve.getMainCurveBoundaries().getTimePointAfter())) { + ++gpsFixCountWithinMainCurve; + } + ++gpsFixCountWithinWholeCurve; + } + } + } finally { + maneuverDetector.track.unlockAfterRead(); + } + + ManeuverLoss projectedManeuverLoss = maneuverDetector.getManeuverLoss(maneuverCurve.getMainCurveBoundaries()); + Distance distanceSailedIfNotManeuvering = maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore() + .travel(maneuverCurve.getMainCurveBoundaries().getDuration()); + Distance distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled( + maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), + maneuverCurve.getMainCurveBoundaries().getTimePointAfter()); + Duration longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes( + maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), + maneuverCurve.getMainCurveBoundaries().getTimePointAfter()); + ManeuverMainCurveWithEstimationData mainCurve = new ManeuverMainCurveWithEstimationDataImpl( + maneuverCurve.getMainCurveBoundaries().getTimePointBefore(), + maneuverCurve.getMainCurveBoundaries().getTimePointAfter(), + maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingBefore(), + maneuverCurve.getMainCurveBoundaries().getSpeedWithBearingAfter(), + maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees(), + stepWithLowestSpeed.getSpeedWithBearing(), stepWithLowestSpeed.getTimePoint(), + stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(), + maneuverCurve.getMainCurveBoundaries().getTimePoint(), + maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate, + distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering, + projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), + Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees()) + / maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(), + gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes); + projectedManeuverLoss = maneuverDetector + .getManeuverLoss(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries()); + distanceSailedIfNotManeuvering = maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() + .getSpeedWithBearingBefore() + .travel(maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDuration()); + distanceSailedWithinManeuver = maneuverDetector.track.getDistanceTraveled( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); + longestGpsFixIntervalBetweenTwoFixes = maneuverDetector.track.getLongestIntervalBetweenTwoFixes( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); + TrackTimeInfo trackTimeInfo = previousManeuverCurve == null || nextManeuverCurve == null + ? maneuverDetector.getTrackTimeInfo() : null; + Pair durationAndAvgSpeedWithBearingBefore = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( + previousManeuverCurve == null ? trackTimeInfo.getTrackStartTimePoint() + : previousManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries() + .getTimePointAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); + Pair durationAndAvgSpeedWithBearingAfter = calculateDurationAndAvgSpeedWithBearingBetweenTimePoints( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), + nextManeuverCurve == null ? trackTimeInfo.getTrackEndTimePoint() + : nextManeuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); + Duration intervalBetweenLastFixOfCurveAndNextFix = Duration.NULL; + GPSFixMoving lastManeuverFix = maneuverDetector.track.getLastFixAtOrBefore( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter()); + if (lastManeuverFix != null) { + GPSFixMoving firstFixAfterLastManeuverFix = maneuverDetector.track + .getFirstFixAfter(lastManeuverFix.getTimePoint()); + if (firstFixAfterLastManeuverFix != null) { + intervalBetweenLastFixOfCurveAndNextFix = lastManeuverFix.getTimePoint() + .until(firstFixAfterLastManeuverFix.getTimePoint()); + } + } + Duration intervalBetweenFirstFixOfCurveAndPreviousFix = Duration.NULL; + GPSFixMoving firstManeuverFix = maneuverDetector.track.getFirstFixAtOrAfter( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore()); + if (firstManeuverFix != null) { + GPSFixMoving lastFixBeforeFirstManeuverFix = maneuverDetector.track + .getLastFixBefore(firstManeuverFix.getTimePoint()); + if (lastFixBeforeFirstManeuverFix != null) { + intervalBetweenFirstFixOfCurveAndPreviousFix = lastFixBeforeFirstManeuverFix.getTimePoint() + .until(firstManeuverFix.getTimePoint()); + } + } + ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData curveWithUnstableCourseAndSpeed = new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataImpl( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getDirectionChangeInDegrees(), + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getLowestSpeed(), + durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(), + gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(), + durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver, + projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering, + projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), gpsFixCountWithinWholeCurve, + longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix, + intervalBetweenFirstFixOfCurveAndPreviousFix); + TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint(); + Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(maneuverTimePoint, + /* extrapolate */false); + Wind wind = maneuverDetector.trackedRace.getWind(maneuverPosition, maneuverTimePoint); + int numberOfJibes = maneuverDetector.getNumberOfJibes(mainCurve, wind); + int numberOfTacks = maneuverDetector.getNumberOfTacks(mainCurve, wind); + boolean maneuverStartsByRunningAwayFromWind = (mainCurve.getSpeedWithBearingBefore().getBearing().getDegrees() + - 180) * mainCurve.getDirectionChangeInDegrees() < 0; + Bearing relativeBearingToNextMarkPassingBeforeManeuver = getRelativeBearingToNextMark( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointBefore(), maneuverCurve + .getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingBefore().getBearing()); + Bearing relativeBearingToNextMarkPassingAfterManeuver = getRelativeBearingToNextMark( + maneuverCurve.getManeuverCurveWithStableSpeedAndCourseBoundaries().getTimePointAfter(), maneuverCurve + .getManeuverCurveWithStableSpeedAndCourseBoundaries().getSpeedWithBearingAfter().getBearing()); + BoatClass boatClass = maneuverDetector.trackedRace.getRace().getBoatOfCompetitor(maneuverDetector.competitor) + .getBoatClass(); + Double deviationFromTackAngle = null; + Double deviationFromJibeAngle = null; + Speed boatSpeed = curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore() + .compareTo(curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter()) < 0 + ? curveWithUnstableCourseAndSpeed.getSpeedWithBearingBefore() + : curveWithUnstableCourseAndSpeed.getSpeedWithBearingAfter(); + if (polarDataService.getAllBoatClassesWithPolarSheetsAvailable().contains(boatClass)) { + SpeedWithBearingWithConfidence closestTackTwa = polarDataService.getClosestTwaTws(ManeuverType.TACK, + boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass); + SpeedWithBearingWithConfidence closestJibeTwa = polarDataService.getClosestTwaTws(ManeuverType.JIBE, + boatSpeed, curveWithUnstableCourseAndSpeed.getDirectionChangeInDegrees(), boatClass); + if (closestTackTwa != null) { + deviationFromTackAngle = polarDataService.getManeuverAngleInDegreesFromTwa( + closestTackTwa.getObject().getBearing().getDegrees(), ManeuverType.TACK); + } + if (closestJibeTwa != null) { + deviationFromJibeAngle = polarDataService.getManeuverAngleInDegreesFromTwa( + closestJibeTwa.getObject().getBearing().getDegrees(), ManeuverType.JIBE); + } + } + Distance closestDistanceToMark = getClosestDistanceToMark(mainCurve.getTimePointOfMaxTurningRate()); + + return new CompleteManeuverCurveWithEstimationDataImpl(maneuverPosition, mainCurve, + curveWithUnstableCourseAndSpeed, wind, numberOfTacks, numberOfJibes, + maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkPassingBeforeManeuver, + relativeBearingToNextMarkPassingAfterManeuver, maneuverCurve.isMarkPassing(), closestDistanceToMark, + deviationFromTackAngle, deviationFromJibeAngle); + } + + public Distance getClosestDistanceToMark(TimePoint timePoint) { + NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache( + maneuverDetector.trackedRace, timePoint); + Distance result = null; + TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor, + timePoint); + if (legAfter != null) { + Position maneuverPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false); + if (legAfter.getLeg().getTo() != null) { + result = getClosestDistanceToMarkInternal(markPositionAtTimePointCache, legAfter.getLeg().getTo(), + maneuverPosition); + } + if (legAfter.getLeg().getFrom() != null) { + Distance distance = getClosestDistanceToMarkInternal(markPositionAtTimePointCache, + legAfter.getLeg().getFrom(), maneuverPosition); + if (result == null || distance != null && distance.compareTo(result) < 0) { + result = distance; + } + } + } + return result; + } + + private Distance getClosestDistanceToMarkInternal( + NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache, Waypoint waypoint, + Position maneuverPosition) { + Distance result = null; + for (Mark mark : waypoint.getMarks()) { + Position markPosition = markPositionAtTimePointCache.getEstimatedPosition(mark); + Distance distance = markPosition.getDistance(maneuverPosition); + if (result == null || distance.compareTo(result) < 0) { + result = distance; + } + } + return result; + } + + /** + * Calculates the duration and avg speed with avg course based on the competitor's track within the provided time + * range. + */ + private Pair calculateDurationAndAvgSpeedWithBearingBetweenTimePoints(TimePoint from, + TimePoint to) { + Duration duration = from.until(to); + Position fromPosition = maneuverDetector.track.getEstimatedPosition(from, false); + Position toPosition = maneuverDetector.track.getEstimatedPosition(to, false); + Distance distance = fromPosition.getDistance(toPosition); + Bearing bearing = fromPosition.getBearingGreatCircle(toPosition); + Speed speed = distance.inTime(Math.abs(duration.asMillis())); + SpeedWithBearing avgSpeedWithBearing = new KnotSpeedWithBearingImpl(speed.getKnots(), bearing); + return new Pair<>(duration, avgSpeedWithBearing); + } + + /** + * Gets the relative bearing of the next mark from the boat's position and course at {@code timePoint}. The relative + * bearing is calculated by absolute bearing of next mark from the boat's position minus the boat's course. + */ + public Bearing getRelativeBearingToNextMark(TimePoint timePoint, Bearing boatCourse) { + NonCachingMarkPositionAtTimePointCache markPositionAtTimePointCache = new NonCachingMarkPositionAtTimePointCache( + maneuverDetector.trackedRace, timePoint); + Bearing result = null; + TrackedLegOfCompetitor legAfter = maneuverDetector.trackedRace.getTrackedLeg(maneuverDetector.competitor, + timePoint); + if (legAfter != null && legAfter.getLeg().getTo() != null) { + Position maneuverEndPosition = maneuverDetector.track.getEstimatedPosition(timePoint, false); + Waypoint nextWaypoint = legAfter.getLeg().getTo(); + for (Mark mark : nextWaypoint.getMarks()) { + Position nextMarkPosition = markPositionAtTimePointCache.getEstimatedPosition(mark); + Bearing absoluteBearing = maneuverEndPosition.getBearingGreatCircle(nextMarkPosition); + Bearing resultCandidate = absoluteBearing.getDifferenceTo(boatCourse); + if (result == null) { + result = resultCandidate; + } else if (Math.signum(result.getDegrees()) != Math.signum(resultCandidate.getDegrees())) { + result = new DegreeBearingImpl(0); + break; + } else if (Math.abs(result.getDegrees()) > Math.abs(resultCandidate.getDegrees())) { + result = resultCandidate; + } + + } + } + return result; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverLoss.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverLoss.java deleted file mode 100644 index e05bf9bb81c..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/maneuverdetection/impl/ManeuverLoss.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.sap.sailing.domain.maneuverdetection.impl; - -import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sse.common.Distance; - -/** - * - * @author Vladislav Chumak (D069712) - * - */ -public class ManeuverLoss { - - private final Distance distanceSailed; - private final Distance distanceSailedIfNotManeuvering; - - public ManeuverLoss(Distance distanceSailed, Distance distanceSailedIfNotManeuvering) { - this.distanceSailed = distanceSailed; - this.distanceSailedIfNotManeuvering = distanceSailedIfNotManeuvering; - } - - public Distance getDistanceSailed() { - return distanceSailed; - } - - public Distance getDistanceSailedIfNotManeuvering() { - return distanceSailedIfNotManeuvering; - } - - public Distance getDistanceLost() { - return new MeterDistance(distanceSailedIfNotManeuvering.getMeters() - distanceSailed.getMeters()); - } - - /** - * Gets the ratio between {@link #getDistanceSailed()} and - * {@link #getDistanceSailedIfNotManeuvering()}. - */ - public double getRatioBetweenDistanceSailedWithAndWithoutManeuver() { - return getDistanceSailed().getMeters() / getDistanceSailedIfNotManeuvering().getMeters(); - } - -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java index 0e1c1ce4e70..231fd9b68ce 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/polars/PolarDataService.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.polars; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -181,4 +182,11 @@ public interface PolarDataService { * with this service, then lets the {@code consumer} accept that domain factory. */ void runWithDomainFactory(Consumer consumer) throws InterruptedException; + + Map getFixCountPerBoatClass(); + + SpeedWithBearingWithConfidence getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart, + double courseChangeDeg, BoatClass boatClass); + + double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java deleted file mode 100755 index c16c669fe70..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.sap.sailing.domain.racelog.impl; - -import java.util.List; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorMappingFinder; -import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.DeviceIdentifier; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; -import com.sap.sailing.domain.racelog.tracking.SensorFixStore; -import com.sap.sailing.domain.racelogtracking.DeviceMapping; -import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; - -/** - * At the moment, the timerange covered by the fixes for a device, and the number of fixes for a device are stored in a - * metadata collection. Should be changed, see bug 1982. - * - * @author Fredrik Teschke - */ -public class GPSFixStoreImpl implements GPSFixStore { - private final SensorFixStore sensorFixStore; - - public GPSFixStoreImpl(SensorFixStore sensorFixStore) { - this.sensorFixStore = sensorFixStore; - } - - private void loadTrack(DynamicGPSFixTrack track, DeviceIdentifier device, - TimePoint from, TimePoint to, boolean toIsInclusive) - throws NoCorrespondingServiceRegisteredException, TransformationException { - loadTrack(track, device, from, to, toIsInclusive, () -> { - return false; - }, (c) -> { - }); - } - - private void loadTrack(DynamicGPSFixTrack track, DeviceIdentifier device, - TimePoint from, TimePoint to, boolean toIsInclusive, BooleanSupplier isPreemptiveStopped, - Consumer progress) - throws NoCorrespondingServiceRegisteredException, TransformationException { - sensorFixStore. loadFixes(fix -> track.add(fix, /* replace */ true), device, from, to, toIsInclusive, - isPreemptiveStopped, progress); - } - - @Override - public void loadCompetitorTrack(DynamicGPSFixTrack track, RegattaLog log, - Competitor competitor) throws NoCorrespondingServiceRegisteredException, TransformationException { - List> mappings = new RegattaLogDeviceCompetitorMappingFinder(log) - .analyze().get(competitor); - if (mappings != null) { - for (DeviceMapping mapping : mappings) { - loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), - false /* toIsInclusive */); - } - } - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, RegattaLog log, Mark mark) - throws NoCorrespondingServiceRegisteredException, TransformationException { - List> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze() - .get(mark); - if (mappings != null) { - for (DeviceMapping mapping : mappings) { - loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), - false /* toIsInclusive */); - } - } - } - - @Override - public void storeFix(DeviceIdentifier device, GPSFix fix) { - sensorFixStore.storeFix(device, fix); - } - - @Override - public void loadCompetitorTrack(DynamicGPSFixTrack track, - DeviceMapping mapping, TimePoint start, TimePoint end, BooleanSupplier isPreemptiveStopped, - Consumer progressReporter) - throws TransformationException, NoCorrespondingServiceRegisteredException { - - // loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true - // /*inclusive*/); - final TimePoint from = Util.getLatestOfTimePoints(start, mapping.getTimeRange().from()); - final TimePoint to = Util.getEarliestOfTimePoints(end, mapping.getTimeRange().to()); - loadTrack(track, mapping.getDevice(), from, to, true /* inclusive */, isPreemptiveStopped, progressReporter); - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, DeviceMapping mapping, TimePoint start, - TimePoint end, BooleanSupplier isPreemptiveStopped, Consumer progressReport) - throws TransformationException, NoCorrespondingServiceRegisteredException { - final TimePoint from = Util.getLatestOfTimePoints(start, mapping.getTimeRange().from()); - final TimePoint to = Util.getEarliestOfTimePoints(end, mapping.getTimeRange().to()); - loadTrack(track, mapping.getDevice(), from, to, true /* inclusive */, isPreemptiveStopped, progressReport); - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, RegattaLog log, Mark mark, TimePoint start, - TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException { - List> mappings = new RegattaLogDeviceMarkMappingFinder(log).analyze() - .get(mark); - if (mappings != null) { - for (DeviceMapping mapping : mappings) { - final TimePoint from = Util.getLatestOfTimePoints(start, mapping.getTimeRange().from()); - final TimePoint to = Util.getEarliestOfTimePoints(end, mapping.getTimeRange().to()); - loadTrack(track, mapping.getDevice(), from, to, false /* toIsInclusive */); - } - } - } -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java deleted file mode 100644 index 58a500bbcb1..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sap.sailing.domain.racelog.tracking; - -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.DeviceIdentifier; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.racelogtracking.DeviceMapping; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; - -public enum EmptyGPSFixStore implements GPSFixStore { - INSTANCE; - - @Override - public void storeFix(DeviceIdentifier device, GPSFix fix) { - } - - @Override - public void loadCompetitorTrack(DynamicGPSFixTrack track, - RegattaLog log, Competitor competitor) { - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, - RegattaLog log, Mark mark) { - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, RegattaLog log, Mark mark, - TimePoint start, TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException { - } - - @Override - public void loadCompetitorTrack(DynamicGPSFixTrack track, - DeviceMapping mapping, TimePoint start, TimePoint end, BooleanSupplier isPreemptiveStopped, - Consumer progressReporter) - throws TransformationException, NoCorrespondingServiceRegisteredException { - - } - - @Override - public void loadMarkTrack(DynamicGPSFixTrack track, DeviceMapping mapping, TimePoint start, - TimePoint end, BooleanSupplier isPreemptiveStopped, Consumer progressReporter) - throws TransformationException, NoCorrespondingServiceRegisteredException { - - } -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java deleted file mode 100644 index b83eabef87f..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.sap.sailing.domain.racelog.tracking; - -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; - -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.DeviceIdentifier; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.racelogtracking.DeviceMapping; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; - - -public interface GPSFixStore { - /** - * Load all fixes that correspond to the {@link RegattaLogDeviceCompetitorMappingEvent}s found in the {@code raceLog}. - */ - void loadCompetitorTrack(DynamicGPSFixTrack track, RegattaLog log, - Competitor competitor) throws TransformationException; - - /** - * Load all fixes that correspond to the {@link RegattaLogDeviceMarkMappingEvent}s found in the {@code raceLog}. - */ - void loadMarkTrack(DynamicGPSFixTrack track, RegattaLog log, Mark mark) throws TransformationException, - NoCorrespondingServiceRegisteredException; - - /** - * Load all fixes within the start and end time point (inclusive) that correspond to the - * {@link RegattaLogDeviceMarkMappingEvent}s found in the {@code raceLog}. - * - * @param start - * if null, the start of the time range for which to load fixes is only constrained by the - * device mapping intervals - * @param end - * if null, the end of the time range for which to load fixes is only constrained by the - * device mapping intervals - */ - void loadMarkTrack(DynamicGPSFixTrack track, RegattaLog log, Mark mark, TimePoint start, - TimePoint end) throws TransformationException, NoCorrespondingServiceRegisteredException; - - void loadCompetitorTrack(DynamicGPSFixTrack track, DeviceMapping mapping, - TimePoint start, TimePoint end, BooleanSupplier isPreemptiveStopped, Consumer progressReporter) - throws TransformationException, NoCorrespondingServiceRegisteredException; - - /** - * Load all fixes that correspond to the {@code mapping}. - */ - void loadMarkTrack(DynamicGPSFixTrack track, DeviceMapping mapping, TimePoint start, - TimePoint end, BooleanSupplier isPreemptiveStopped, Consumer progressReport) - throws TransformationException, - NoCorrespondingServiceRegisteredException; - - void storeFix(DeviceIdentifier device, GPSFix fix) throws TransformationException, NoCorrespondingServiceRegisteredException; -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/sharding/ShardingContext.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/sharding/ShardingContext.java new file mode 100644 index 00000000000..e98f0873c65 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/sharding/ShardingContext.java @@ -0,0 +1,120 @@ +package com.sap.sailing.domain.sharding; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.common.sharding.ShardingType; +import com.sap.sailing.domain.leaderboard.caching.LiveLeaderboardUpdater; + +/** + * The sharding context is used to store and verify contraints for a thread. + * + * This is currently used to verify if a given thread, that is explicitly expected to only + * trigger the calculation of a given leaderboard, does trigger the calculation of further/ other + * leaderboards. + * + * It is also used to log warnings if a leaderboard is calculated without predefining the thread constraint. + * + * The enum {@link ShardingType} lists the known sharding types. Each sharding type defines a prefix that will + * be used to identify the sharding type from the request, as implemented in {@link #identifyAndSetShardingConstraint(String)}. + * + * By calling {@link #setShardingConstraint(ShardingType, String)} it is possible to directly set a thread constraint. + * This is useful in background computing code, as in {@link LiveLeaderboardUpdater} + * + */ +public class ShardingContext { + private static final Logger logger = Logger.getLogger(ShardingContext.class.getName()); + + private static final ConcurrentMap> shardingMap = new ConcurrentHashMap<>(); + + /** + * Sets the sharding context. The type of sharding will be identified by the prefix. + * + * @param shardingInfo + */ + public static ShardingType identifyAndSetShardingConstraint(String shardingInfo) { + if (shardingInfo == null || shardingInfo.isEmpty()) { + return null; + } + ThreadLocal identifiedShardingHolder = null; + for (ShardingType shardingType : ShardingType.values()) { + if (shardingInfo.startsWith(shardingType.getPrefix())) { + identifiedShardingHolder = shardingMap.computeIfAbsent(shardingType, t -> new ThreadLocal<>()); + checkAndSetShardingInfo(shardingType, shardingInfo, identifiedShardingHolder); + return shardingType; + } + } + if (identifiedShardingHolder == null) { + logger.warning("Could not identify sharding type for: " + shardingInfo); + } + return null; + } + + /** + * Provide sharding constraint for given sharding type. + * + * @param shardingType + * @param shardingInfo + */ + public static void setShardingConstraint(ShardingType shardingType, String shardingInfo) { + final String encodedShardingInfo = shardingType.encodeIfNeeded(shardingInfo); + ThreadLocal shardingHolder = shardingMap.computeIfAbsent(shardingType, t -> new ThreadLocal<>()); + checkAndSetShardingInfo(shardingType, encodedShardingInfo, shardingHolder); + } + + /** + * Check if provided sharding information name equals shard constraint. + * + * If the information is null or empty, we log a warning. + * If shard does not have a constraint, we log a warning. If + * the information provided does not match information previously stored in shard, + * we make a severe log entry. + * + * @param shardingInfo + */ + public static void checkConstraint(final ShardingType type, final String shardingInfo) { + if (shardingInfo == null || shardingInfo.isEmpty()) { + logger.warning("Empty sharding constraint"); + return; + } + final ThreadLocal shardingHolder = shardingMap.get(type); + if (shardingHolder == null) { + logger.log(Level.WARNING, "No current sharding context set for " + type.name(), new RuntimeException()); + return; + } + String currentShardingInfo = shardingHolder.get(); + if (currentShardingInfo == null || currentShardingInfo.isEmpty()) { + logger.warning("No current sharding constraint for " + type.name()); + return; + } + final String encodedShardingInfo = type.encodeIfNeeded(shardingInfo); + if (!encodedShardingInfo.equals(currentShardingInfo)) { + logger.log(Level.SEVERE, "Current sharding constraint vialation for " + type.name() + ". Got " + + shardingInfo + ", shard requires " + currentShardingInfo, new RuntimeException()); + return; + } + } + + public static void clearShardingConstraint(ShardingType type) { + ThreadLocal shardingHolder = shardingMap.get(type); + if (shardingHolder != null) { + shardingHolder.remove(); + } + } + + private static void checkAndSetShardingInfo(ShardingType shardingType, String encodedShardingInfo, + ThreadLocal shardingHolder) { + if (encodedShardingInfo == null || encodedShardingInfo.isEmpty()) { + logger.log(Level.SEVERE, "Cannot set empty sharding information for " + shardingType.name(), + new RuntimeException()); + } + String currentShardingInfo = shardingHolder.get(); + if (currentShardingInfo == null) { + shardingHolder.set(encodedShardingInfo); + } else if (!encodedShardingInfo.equals(currentShardingInfo)) { + logger.log(Level.SEVERE, "Switching shard constraint for " + shardingType.name()+". Got <<" + encodedShardingInfo +">>, expeted <<"+ currentShardingInfo+">>", new RuntimeException()); + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java index a91c230a693..4e2674fd8ac 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/GPSFixTrack.java @@ -175,11 +175,13 @@ public interface GPSFixTrack extends MappedTra void resumeValidityCaching(); /** - * Gets a list of bearings between the provided time range (inclusive the boundaries). The bearings are retrieved by - * means of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The first and last bearing steps will be always - * sampled at provided {@code fromTimePoint} and {@code toTimePoint}, whereas the steps between are sampled at time - * points of non-raw GPS fixes. The idea of this concept is to produce at least two bearing steps as result, in - * order to provide the caller at least a {@code totalCourseChangeAngleInDegrees > 0} between the given time range. + * Gets a list of speed with bearing steps considering the provided time range. The bearings are retrieved by means + * of {@link GPSFixTrack#getEstimatedSpeed(TimePoint)}. The steps are sampled at time points of non-raw GPS fixes. + * When there is no non-raw fix contained at {@code fromTimePoint}, the time point of the first step will be the + * time point of the first non-raw fix before {@code fromTimePoint}. Analogously, when there is no non-raw fix + * contained at {@code toTimePoint}, the last step's time point will be the first non-raw fix after + * {@code toTimePoint}. The idea of this concept is to produce at least two steps as part of the result, in order to + * provide the caller at least a {@code |totalCourseChangeAngleInDegrees > 0|} between the given time range. * * @param fromTimePoint * The from time point (inclusive) for resulting bearing steps diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java index 7011612e984..0a9a591d1d7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/Maneuver.java @@ -5,7 +5,7 @@ import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sse.common.Distance; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; import com.sap.sse.common.Duration; import com.sap.sse.common.Speed; import com.sap.sse.common.TimePoint; @@ -61,8 +61,14 @@ public interface Maneuver extends GPSFix { * and exiting the maneuver that the boat lost compared to not having maneuvered. The maneuver loss is calculated * considering the maneuver curve, which was performed between {@link #getTimePointBefore()} and * {@link #getTimePointAfter()}. +// */ +// Distance getManeuverLossDistanceLost(); + + /** + * Gets the ManeuverLoss Object of a maneuver, that is calculated in {@link ManeuverDetectorImpl#getManeuverLoss} + * */ - Distance getManeuverLoss(); + ManeuverLoss getManeuverLoss(); /** * Gets the time point of the corresponding maneuver. The time point refers to a point within the main curve of @@ -174,5 +180,5 @@ public interface Maneuver extends GPSFix { */ @Statistic(messageKey = "AvgTurningRateInDegreesPerSecond", resultDecimals = 4) double getAvgTurningRateInDegreesPerSecond(); - + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/ManeuverLoss.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/ManeuverLoss.java new file mode 100644 index 00000000000..8bdaa808bc9 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/ManeuverLoss.java @@ -0,0 +1,74 @@ +package com.sap.sailing.domain.tracking; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; + +/** + * Updated ManeuverLoss class to use it to visualize the correct maneuver loss on the race map. To draw the ManeuverLoss + * needs the maneuverStartPosition, maneuverEndPosition, middleManeuverAngle, speedWithBearingBefore and + * maneuverDuration. The ManeuverLoss is calculated by {@link ManeuverDetectorImpl#getManeuverLoss()}. + * + * @author Vladislav Chumak (D069712) + */ +public class ManeuverLoss { + private final Distance distanceSailedProjectedOnMiddleManeuverAngle; + private final Distance distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle; + private final Position maneuverStartPosition; + private final Position maneuverEndPosition; + private final Duration maneuverDuration; + private final SpeedWithBearing speedWithBearingBefore; + private final Bearing middleManeuverAngle; + + public ManeuverLoss(Distance distanceSailedProjectedOnMiddleManeuverAngle, + Distance distanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle, Position maneuverStartPosition, + Position maneuverEndPosition, Duration maneuverDuration, SpeedWithBearing speedWithBearingBefore, + Bearing middleManeuverAngle) { + this.distanceSailedProjectedOnMiddleManeuverAngle = distanceSailedProjectedOnMiddleManeuverAngle; + this.distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle = distanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle; + this.maneuverStartPosition = maneuverStartPosition; + this.maneuverEndPosition = maneuverEndPosition; + this.maneuverDuration = maneuverDuration; + this.speedWithBearingBefore = speedWithBearingBefore; + this.middleManeuverAngle = middleManeuverAngle; + } + + public Distance getDistanceSailedProjectedOnMiddleManeuverAngle() { + return distanceSailedProjectedOnMiddleManeuverAngle; + } + + public Distance getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle() { + return distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle; + } + + public Distance getProjectedDistanceLost() { + return distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle.add(distanceSailedProjectedOnMiddleManeuverAngle.scale(-1)); + } + + /** + * Gets the ratio between {@link #getDistanceSailedProjectedOnMiddleManeuverAngle()} and + * {@link #getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle()}. + */ + public double getRatioBetweenDistanceSailedWithAndWithoutManeuver() { + return getDistanceSailedProjectedOnMiddleManeuverAngle().getMeters() / getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle().getMeters(); + } + + public Position getManeuverStartPosition() { + return maneuverStartPosition; + } + public Position getManeuverEndPosition() { + return maneuverEndPosition; + } + public Duration getManeuverDuration() { + return maneuverDuration; + } + public SpeedWithBearing getSpeedWithBearingBefore() { + return speedWithBearingBefore; + } + public Bearing getMiddleManeuverAngle() { + return middleManeuverAngle; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 6120df5646a..c598d23b01d 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -49,7 +49,7 @@ import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuse import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.ranking.RankingMetric; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache; @@ -773,7 +773,7 @@ public interface TrackedRace extends Serializable, IsManagedByCache extends S } } - private TimeRangeCache createTimeRangeCache(ItemType trackedItem, final String cacheName) { + /** + * This method is protected in order to let test classes use other TimeRangeCache specializations + * that provide specific test support. + */ + protected TimeRangeCache createTimeRangeCache(ItemType trackedItem, final String cacheName) { return new TimeRangeCache<>(cacheName+" for "+trackedItem); } 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 b50674511a1..f4266d5e422 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 @@ -1171,20 +1171,13 @@ public abstract class GPSFixTrackImpl extends Bearing lastCourse = null; TimePoint lastTimePoint = null; double lastCourseChangeAngleInDegrees = 0; - TimePoint fixTimePointAfterToTimePoint = null; try { lockForRead(); - TimePoint timePoint = fromTimePoint; - for (Iterator iterator = getFixesIterator(fromTimePoint, false); iterator - .hasNext(); timePoint = iterator.next().getTimePoint()) { - if (timePoint == null) { - continue; - } - if (timePoint.after(toTimePoint)) { - fixTimePointAfterToTimePoint = timePoint; - timePoint = toTimePoint; - } - SpeedWithBearing estimatedSpeed = getEstimatedSpeed(timePoint); + FixType firstFix = getLastFixAtOrBefore(fromTimePoint); + TimePoint currentTimePoint = firstFix == null ? fromTimePoint : firstFix.getTimePoint(); + for (Iterator iterator = getFixesIterator(currentTimePoint, false); iterator + .hasNext(); currentTimePoint = iterator.next().getTimePoint()) { + SpeedWithBearing estimatedSpeed = getEstimatedSpeed(currentTimePoint); if (estimatedSpeed != null) { Bearing course = estimatedSpeed.getBearing(); /* @@ -1195,44 +1188,20 @@ public abstract class GPSFixTrackImpl extends double courseChangeAngleInDegrees = lastCourse == null ? 0 : lastCourse.getDifferenceTo(course, new DegreeBearingImpl(lastCourseChangeAngleInDegrees)) .getDegrees(); + double turningRateInDegreesPerSecond = lastTimePoint == null ? 0 + : Math.abs(courseChangeAngleInDegrees + / lastTimePoint.until(currentTimePoint).asSeconds()); - // Fix distorted turning rate due to inappropriate interpolation of getEstimatedSpeed() at first - // and last step - double courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees; - Duration durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null - : lastTimePoint.until(timePoint); - if (fromTimePoint.equals(lastTimePoint)) { - FixType firstFix = getLastFixAtOrBefore(fromTimePoint); - if (firstFix != null && !firstFix.getTimePoint().equals(fromTimePoint)) { - SpeedWithBearing firstFixEstimatedSpeed = getEstimatedSpeed(firstFix.getTimePoint()); - if (firstFixEstimatedSpeed != null) { - durationBetweenStepsForTurningRateCalculation = firstFix.getTimePoint() - .until(timePoint); - courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees - + firstFixEstimatedSpeed.getBearing().getDifferenceTo(lastCourse).getDegrees(); - } - } - } else if (fixTimePointAfterToTimePoint != null && lastCourse != null) { - SpeedWithBearing lastFixEstimatedSpeed = getEstimatedSpeed(fixTimePointAfterToTimePoint); - if (lastFixEstimatedSpeed != null) { - durationBetweenStepsForTurningRateCalculation = lastTimePoint == null ? null - : lastTimePoint.until(fixTimePointAfterToTimePoint); - courseChangeInDegreesForTurningRateCalculation = courseChangeAngleInDegrees + estimatedSpeed - .getBearing().getDifferenceTo(lastFixEstimatedSpeed.getBearing()).getDegrees(); - } - } - - double turningRateInDegreesPerSecond = durationBetweenStepsForTurningRateCalculation == null ? 0 - : Math.abs(courseChangeInDegreesForTurningRateCalculation - / durationBetweenStepsForTurningRateCalculation.asSeconds()); - - speedWithBearingSteps.add(new SpeedWithBearingStepImpl(timePoint, estimatedSpeed, + speedWithBearingSteps.add(new SpeedWithBearingStepImpl(currentTimePoint, estimatedSpeed, courseChangeAngleInDegrees, turningRateInDegreesPerSecond)); + if (currentTimePoint.after(toTimePoint)) { + break; + } lastCourse = course; lastCourseChangeAngleInDegrees = courseChangeAngleInDegrees; - lastTimePoint = timePoint; + lastTimePoint = currentTimePoint; } - if (!timePoint.before(toTimePoint)) { + if (!currentTimePoint.before(toTimePoint)) { break; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverImpl.java index 7adc0b7a105..06c4810e935 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverImpl.java @@ -8,8 +8,8 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.tracking.impl.AbstractGPSFixImpl; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.ManeuverLoss; import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sse.common.Distance; import com.sap.sse.common.Duration; import com.sap.sse.common.Speed; import com.sap.sse.common.TimePoint; @@ -24,25 +24,25 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve private final Tack newTack; private final Position position; private final TimePoint timePoint; - private final Distance maneuverLoss; private final double maxTurningRateInDegreesPerSecond; private final ManeuverCurveBoundaries mainCurveBoundaries; private final ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries; private final MarkPassing markPassing; + private final ManeuverLoss maneuverLoss; - public ManeuverImpl(ManeuverType type, Tack newTack, Position position, Distance maneuverLoss, TimePoint timePoint, + public ManeuverImpl(ManeuverType type, Tack newTack, Position position, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries, ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries, - double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) { + double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) { this.type = type; this.newTack = newTack; this.position = position; - this.maneuverLoss = maneuverLoss; this.timePoint = timePoint; this.mainCurveBoundaries = mainCurveBoundaries; this.maneuverCurveWithStableSpeedAndCourseBoundaries = maneuverCurveWithStableSpeedAndCourseBoundaries; this.maxTurningRateInDegreesPerSecond = maxTurningRateInDegreesPerSecond; this.markPassing = markPassing; + this.maneuverLoss = maneuverLoss; } @Override @@ -75,11 +75,6 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve return newTack; } - @Override - public Distance getManeuverLoss() { - return maneuverLoss; - } - @Override public double getDirectionChangeInDegrees() { return getManeuverBoundaries().getDirectionChangeInDegrees(); @@ -105,7 +100,7 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve return super.toString() + " " + type + " on new tack " + newTack + " on position " + position + " at time point " + timePoint + ", " + getManeuverBoundaries() + ", max. turning rate: " + maxTurningRateInDegreesPerSecond - + (getManeuverLoss() == null ? "" : ", Lost approximately " + getManeuverLoss()) + ", Mark passing: " + + (getManeuverLoss() == null ? "" : ", Lost approximately " + getManeuverLoss().getProjectedDistanceLost()) + ", Mark passing: " + markPassing; } @@ -139,5 +134,11 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve return Math.abs(getMainCurveBoundaries().getDirectionChangeInDegrees()) / getMainCurveBoundaries().getDuration().asSeconds(); } + + @Override + public ManeuverLoss getManeuverLoss() { + return maneuverLoss; + } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java new file mode 100644 index 00000000000..ae79943505c --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithCoarseGrainedBoundariesImpl.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.tracking.impl; + +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sse.common.TimePoint; + +/** + * Maneuver implementation which were detected on tracks with extremely low GPS-sampling rate. The implementation + * suggests to ignore the following attributes: + *
        + *
      • Maneuver loss
      • + *
      • Max. and Avg. Turning rate
      • + *
      • Time point before and after of all maneuver boundaries
      • + *
      • Lowest speed within all maneuver boundaries
      • + *
      + * However, to provide capability with existing code, the attributes are filled with values. + * + * @author Vladislav Chumak (D069712) + * + */ +public class ManeuverWithCoarseGrainedBoundariesImpl extends ManeuverImpl { + + private static final long serialVersionUID = -381990329349665889L; + + public ManeuverWithCoarseGrainedBoundariesImpl(ManeuverType type, Tack newTack, Position position, + TimePoint timePoint, ManeuverCurveBoundaries maneuverBoundaries) { + super(type, newTack, position, timePoint, maneuverBoundaries, maneuverBoundaries, + Math.abs(maneuverBoundaries.getDirectionChangeInDegrees()), /* mark passing */ null, + /* maneuver loss */ null); + } + + @Override + public ManeuverCurveBoundaries getManeuverBoundaries() { + return getMainCurveBoundaries(); + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithMainCurveBoundariesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithMainCurveBoundariesImpl.java index 739d33b9162..9f8e240a1da 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithMainCurveBoundariesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithMainCurveBoundariesImpl.java @@ -5,8 +5,8 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.ManeuverLoss; import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; /** @@ -20,11 +20,11 @@ public class ManeuverWithMainCurveBoundariesImpl extends ManeuverImpl { private static final long serialVersionUID = 5831188137884083419L; public ManeuverWithMainCurveBoundariesImpl(ManeuverType type, Tack newTack, Position position, - Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries, + TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries, ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries, - double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) { - super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries, - maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing); + double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) { + super(type, newTack, position, timePoint, mainCurveBoundaries, + maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing, maneuverLoss); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithStableSpeedAndCourseBoundariesImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithStableSpeedAndCourseBoundariesImpl.java index 15f1f606e6a..914f18ebb99 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithStableSpeedAndCourseBoundariesImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/ManeuverWithStableSpeedAndCourseBoundariesImpl.java @@ -5,8 +5,8 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries; +import com.sap.sailing.domain.tracking.ManeuverLoss; import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sse.common.Distance; import com.sap.sse.common.TimePoint; /** @@ -21,11 +21,11 @@ public class ManeuverWithStableSpeedAndCourseBoundariesImpl extends ManeuverImpl private static final long serialVersionUID = 5831188137884083419L; public ManeuverWithStableSpeedAndCourseBoundariesImpl(ManeuverType type, Tack newTack, Position position, - Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries, + TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries, ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries, - double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) { - super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries, - maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing); + double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) { + super(type, newTack, position, timePoint, mainCurveBoundaries, + maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing, maneuverLoss); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index d80b0c45e6a..2760bf877ca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -113,6 +113,7 @@ import com.sap.sailing.domain.maneuverdetection.IncrementalManeuverDetector; import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; import com.sap.sailing.domain.maneuverdetection.ShortTimeAfterLastHitCache; import com.sap.sailing.domain.maneuverdetection.impl.IncrementalManeuverDetectorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.LowGPSSamplingRateManeuverDetectorImpl; import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; @@ -701,10 +702,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public List computeCacheUpdate(Competitor competitor, EmptyUpdateInterval updateInterval) throws NoWindException { - IncrementalManeuverDetector maneuverDetector = maneuverDetectorPerCompetitorCache - .getValue(competitor); - List maneuvers = computeManeuvers(competitor, maneuverDetector); - return maneuvers; + Duration averageIntervalBetweenRawFixes = getTrack(competitor) + .getAverageIntervalBetweenRawFixes(); + if (averageIntervalBetweenRawFixes != null) { + ManeuverDetector maneuverDetector; + if (averageIntervalBetweenRawFixes.asSeconds() >= 30) { + maneuverDetector = new LowGPSSamplingRateManeuverDetectorImpl(TrackedRaceImpl.this, + competitor); + } else { + maneuverDetector = maneuverDetectorPerCompetitorCache.getValue(competitor); + } + List maneuvers = computeManeuvers(competitor, maneuverDetector); + return maneuvers; + } else { + return Collections.emptyList(); + } } }, /* nameForLocks */"Maneuver cache for race " + getRace().getName()); } diff --git a/java/com.sap.sailing.gwt.ui/DataMining.css b/java/com.sap.sailing.gwt.ui/DataMining.css index a7adc9d4486..92ca1290f69 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.css +++ b/java/com.sap.sailing.gwt.ui/DataMining.css @@ -29,7 +29,7 @@ html { body { - background: url(images/spectatorview-bg.png) no-repeat left top #ebebeb; + background: #eee; font-family: 'Open Sans', Arial, Verdana, sans-serif; line-height: 1; font-size: 14px; @@ -150,11 +150,6 @@ input.magnifier { z-index: 2000; } -.mainPanel { - width: 100%; - margin-left: 13px; -} - .errorLabel { color: #FF0000; } @@ -221,15 +216,32 @@ input.magnifier { top: 9px; } +.groupingProvider { + padding: 3px 0px; +} + .groupingProviderElement { margin: 2px; + display: inline-block; +} + +.statisticProvider { + padding: 5px 0px 0px 0px; } .statisticProviderElement { - margin: 10px 2px 2px 2px; + margin: 2px; display: inline-block; } +.statisticSuggestBoxPopup { + z-index: 2; +} +.statisticSuggestBoxPopup .suggestPopupContent { + max-height: 700px; + overflow-y: scroll; +} + .definitionProviderControls { margin: 2px; } @@ -238,12 +250,89 @@ input.magnifier { margin: 2px; } +.queryProviderElementLabel { + margin-left: 0px; +} + .queryDefinitionViewerContent { background-color: white; padding: 5px; } -.statisticSuggestBoxPopup .suggestPopupContent { - max-height: 700px; - overflow-y: scroll; +.filterDimensionsTitleLabel { + padding-top: 5px; } + +.filterFilterDimensionsPanel { + background: #eee; +} + +.dimensionFilterSelectionTablesContainer { + overflow-x: auto; +} + +.dimensionFilterSelectionHeader { + background: #eee; + padding-top: 5px; +} + +.dimensionFilterSelectionHeader .gwt-Label { + color: #4b4a4a; + text-shadow: #ddf 1px 1px 0; +} + +.dataMiningBorderTop { + border-top: 2px solid #6f7277; +} +.dataMiningBorderLeft { + border-left: 2px solid #6f7277; +} +.dataMiningBorderBottom { + border-border: 2px solid #6f7277; +} +.dataMiningBorderRight { + border-right: 2px solid #6f7277; +} + +.gwt-SplitLayoutPanel-VDragger { + background: #fff url(images/thumb_horz.png) center center no-repeat; + width: 100% !important; + height: 100% !important; + left: 0 !important; + top: 0 !important; + border: none; + border-radius: 0px; + opacity: 1; +} + +.gwt-SplitLayoutPanel-HDragger { + background: #fff url(images/thumb_vertical.png) center center no-repeat; + width: 100% !important; + height: 100% !important; + left: 0 !important; + top: 0 !important; + border: none; + border-radius: 0px; + margin: 0px; + opacity: 1; +} + +.dataMiningMarginBase { + margin: 0px 5px; +} +.dataMiningMarginLeft { + margin-left: 5px; +} +.dataMiningMarginRight { + margin-right: 5px; +} + +.resultsPresenterTabHeader * { + display: inline-block; + margin: 0px 3px; + vertical-align: bottom; +} + +.emphasizedLabel { + font-weight: 600; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/Debug DataMining.launch b/java/com.sap.sailing.gwt.ui/Debug DataMining.launch new file mode 100644 index 00000000000..60255a75d65 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/Debug DataMining.launch @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + 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 ed917794a7e..6287e654bdf 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -57,8 +57,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.expeditionconnector, com.sap.sailing.expeditionconnector.common, org.mp4parser.isoparser;bundle-version="1.9.31", - com.sap.sailing.domain.windfinderadapter, - com.sap.sse.datamining.ui;bundle-version="1.0.0" + com.sap.sse.datamining.ui Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: javax.servlet;version="3.1.0", @@ -77,6 +76,7 @@ TrackLifecycle: true Export-Package: com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.confidence.impl, com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.impl, com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.tracking.impl, + com.sap.sailing.gwt.common.communication.routing, com.sap.sailing.gwt.home.communication, com.sap.sailing.gwt.home.server, com.sap.sailing.gwt.server, diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 8538c7b0804..7f9c9641021 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -70,6 +70,7 @@ SailingService /service/sailing + /service/sailing/* @@ -90,6 +91,7 @@ sailingDispatch /service/dispatch + /service/dispatch/* diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedWithBearingImpl_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedWithBearingImpl_CustomFieldSerializer.java new file mode 100755 index 00000000000..69926ff376c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedWithBearingImpl_CustomFieldSerializer.java @@ -0,0 +1,50 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.impl; + +import com.google.gwt.user.client.rpc.CustomFieldSerializer; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.client.rpc.SerializationStreamReader; +import com.google.gwt.user.client.rpc.SerializationStreamWriter; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sse.common.impl.DegreeBearingImpl; + +public class KnotSpeedWithBearingImpl_CustomFieldSerializer extends CustomFieldSerializer { + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, KnotSpeedWithBearingImpl instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, KnotSpeedWithBearingImpl instance) + throws SerializationException { + streamWriter.writeDouble(instance.getKnots()); + streamWriter.writeDouble(instance.getBearing().getDegrees()); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public KnotSpeedWithBearingImpl instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static KnotSpeedWithBearingImpl instantiate(SerializationStreamReader streamReader) + throws SerializationException { + return new KnotSpeedWithBearingImpl(streamReader.readDouble(), new DegreeBearingImpl(streamReader.readDouble())); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, KnotSpeedWithBearingImpl instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, KnotSpeedWithBearingImpl instance) { + // Done by instantiateInstance + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java index e84ad7f3faf..066d5800090 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/PreciseCompactGPSFixMovingImpl.java @@ -5,7 +5,10 @@ import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.SerializationStreamReader; import com.google.gwt.user.client.rpc.SerializationStreamWriter; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition; +import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactSpeedWithBearing; +import com.sap.sse.common.impl.DegreeBearingImpl; public final class PreciseCompactGPSFixMovingImpl { public static final class PreciseCompactPosition_CustomFieldSerializer extends CustomFieldSerializer { @@ -49,4 +52,46 @@ public final class PreciseCompactGPSFixMovingImpl { streamWriter.writeDouble(instance.getLngDeg()); } } + + public static final class PreciseCompactSpeedWithBearing_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public PreciseCompactSpeedWithBearing instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static PreciseCompactSpeedWithBearing instantiate(SerializationStreamReader streamReader) throws SerializationException { + final double speedInKnots = streamReader.readDouble(); + final double bearingDeg = streamReader.readDouble(); + return (PreciseCompactSpeedWithBearing) new com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl( + /* dummy position */ new DegreePosition(0, 0), /* timePoint */null, new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(bearingDeg))).getSpeed(); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, PreciseCompactSpeedWithBearing instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, PreciseCompactSpeedWithBearing instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, PreciseCompactSpeedWithBearing instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, PreciseCompactSpeedWithBearing instance) + throws SerializationException { + streamWriter.writeDouble(instance.getKnots()); + streamWriter.writeDouble(instance.getBearing().getDegrees()); + } + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java index f4d26da9263..355eec4bcad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/tracking/impl/VeryCompactGPSFixMovingImpl.java @@ -8,8 +8,12 @@ import com.google.gwt.user.client.rpc.SerializationException; import com.google.gwt.user.client.rpc.SerializationStreamReader; import com.google.gwt.user.client.rpc.SerializationStreamWriter; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleException; +import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactEstimatedSpeed; import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactPosition; +import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactSpeedWithBearing; +import com.sap.sse.common.impl.DegreeBearingImpl; public final class VeryCompactGPSFixMovingImpl { private static final Logger logger = Logger.getLogger(VeryCompactGPSFixMovingImpl.class.getName()); @@ -61,4 +65,103 @@ public final class VeryCompactGPSFixMovingImpl { streamWriter.writeDouble(instance.getLngDeg()); } } + + public static final class VeryCompactSpeedWithBearing_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public VeryCompactSpeedWithBearing instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static VeryCompactSpeedWithBearing instantiate(SerializationStreamReader streamReader) throws SerializationException { + final double knotSpeed = streamReader.readDouble(); + final double bearingDeg = streamReader.readDouble(); + try { + return (VeryCompactSpeedWithBearing) new com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl( + /* position */ null, /* timePoint */null, new KnotSpeedWithBearingImpl(knotSpeed, new DegreeBearingImpl(bearingDeg))).getSpeed(); + } catch (CompactionNotPossibleException e) { + logger.log(Level.SEVERE, "Internal error: an object that was a very compact position and was serialized "+ + "couldn't be de-serialized again as such an object", e); + throw new RuntimeException(e); + } + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, VeryCompactSpeedWithBearing instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, VeryCompactSpeedWithBearing instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, VeryCompactSpeedWithBearing instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, VeryCompactSpeedWithBearing instance) + throws SerializationException { + streamWriter.writeDouble(instance.getKnots()); + streamWriter.writeDouble(instance.getBearing().getDegrees()); + } + } + + public static final class VeryCompactEstimatedSpeed_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public VeryCompactEstimatedSpeed instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static VeryCompactEstimatedSpeed instantiate(SerializationStreamReader streamReader) throws SerializationException { + final double knotSpeed = streamReader.readDouble(); + final double bearingDeg = streamReader.readDouble(); + try { + final KnotSpeedWithBearingImpl speed = new KnotSpeedWithBearingImpl(knotSpeed, new DegreeBearingImpl(bearingDeg)); + com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl fix = new com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl( + /* dummy position */ new DegreePosition(0, 0), /* timePoint */null, speed); + fix.cacheEstimatedSpeed(speed); + return (VeryCompactEstimatedSpeed) fix.getCachedEstimatedSpeed(); + } catch (CompactionNotPossibleException e) { + logger.log(Level.SEVERE, "Internal error: an object that was a very compact position and was serialized "+ + "couldn't be de-serialized again as such an object", e); + throw new RuntimeException(e); + } + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, VeryCompactEstimatedSpeed instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, VeryCompactEstimatedSpeed instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, VeryCompactEstimatedSpeed instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, VeryCompactEstimatedSpeed instance) + throws SerializationException { + streamWriter.writeDouble(instance.getKnots()); + streamWriter.writeDouble(instance.getBearing().getDegrees()); + } + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayClientFactoryBase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayClientFactoryBase.java index 0c865358e6e..242ea7d5c6a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayClientFactoryBase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayClientFactoryBase.java @@ -1,16 +1,20 @@ package com.sap.sailing.gwt.autoplay.client.app; +import java.util.HashMap; +import java.util.Map; + import com.google.gwt.core.client.GWT; import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceController; import com.google.gwt.user.client.rpc.ServiceDefTarget; import com.google.web.bindery.event.shared.EventBus; import com.sap.sailing.gwt.autoplay.client.places.autoplaystart.AutoPlayStartPlace; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.ui.client.MediaService; import com.sap.sailing.gwt.ui.client.MediaServiceAsync; import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; -import com.sap.sailing.gwt.ui.client.SailingService; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SailingServiceHelper; import com.sap.sse.gwt.client.EntryPointHelper; import com.sap.sse.security.ui.client.SecureClientFactoryImpl; @@ -20,11 +24,13 @@ public abstract class AutoPlayClientFactoryBase private final MediaServiceAsync mediaService; private final AutoPlayPlaceNavigator navigator; + private final Map services = new HashMap<>(); + public AutoPlayClientFactoryBase(ApplicationTopLevelView root, EventBus eventBus, PlaceController placeController, AutoPlayPlaceNavigator navigator) { super(root, eventBus, placeController); this.navigator = navigator; - sailingService = GWT.create(SailingService.class); + sailingService = SailingServiceHelper.createSailingServiceInstance(); mediaService = GWT.create(MediaService.class); EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); @@ -39,8 +45,25 @@ public abstract class AutoPlayClientFactoryBase @Override public SailingServiceAsync getSailingService() { + if (isConfigured()) { + return getSailingService(() -> getAutoPlayCtxSignalError().getContextDefinition().getLeaderboardName()); + } return sailingService; } + + @Override + public SailingServiceAsync getSailingService(ProvidesLeaderboardRouting routingProvider) { + if (routingProvider == null) { + return sailingService; + } else { + SailingServiceAsync sailingServiceAsync = services.get(routingProvider.routingSuffixPath()); + if (sailingServiceAsync == null) { + sailingServiceAsync = SailingServiceHelper.createSailingServiceInstance(routingProvider); + services.put(routingProvider.routingSuffixPath(), sailingServiceAsync); + } + return sailingServiceAsync; + } + } @Override public MediaServiceAsync getMediaService() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java index e3c631638db..349d9ba9ac7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/AutoPlayConfiguration.java @@ -1,10 +1,13 @@ package com.sap.sailing.gwt.autoplay.client.configs; +import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.user.client.Window; import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; +import com.sap.sse.gwt.settings.SettingsToUrlSerializer; +import com.sap.sse.gwt.settings.UrlBuilderUtil; import com.sap.sse.security.ui.client.UserService; public abstract class AutoPlayConfiguration { @@ -19,20 +22,28 @@ public abstract class AutoPlayConfiguration { OnSettingsCallback holder, PerspectiveCompositeSettings settings, AutoPlayContextDefinition apcd, UserService userService) { Window.alert("This configuration does not have settings"); - } /** * This method should be pure/standalone, it is not allowed to make any guesses on fields written by startRootNode, * as it can be called before that */ - public void loadSettingsDefault(EventDTO selectedEvent, StrippedLeaderboardDTO leaderboard, UserService userService, - OnSettingsCallback holder) { - holder.newSettings(null); + public void loadSettingsDefault(EventDTO selectedEvent, AutoPlayContextDefinition apcd, + StrippedLeaderboardDTO leaderboard, UserService userService, OnSettingsCallback holder) { + holder.newSettings(null, getUrlWithSettings(apcd, null)); + } + + private String getUrlWithSettings(AutoPlayContextDefinition apcd, PerspectiveCompositeSettings settings) { + UrlBuilder urlBuilder = UrlBuilderUtil.createUrlBuilderFromCurrentLocationWithCleanParameters(); + SettingsToUrlSerializer urlSerializer = new SettingsToUrlSerializer(); + if (settings != null) { + urlSerializer.serializeSettingsMapToUrlBuilder(settings, urlBuilder); + } + urlSerializer.serializeToUrlBuilder(apcd, urlBuilder); + return urlBuilder.buildString(); } public interface OnSettingsCallback { - void newSettings(PerspectiveCompositeSettings newSettings); + void newSettings(PerspectiveCompositeSettings newSettings, String urlWithSettings); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/impl/AutoPlayClassicConfiguration.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/impl/AutoPlayClassicConfiguration.java index bc1cd20e127..ec9dade106a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/impl/AutoPlayClassicConfiguration.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/configs/impl/AutoPlayClassicConfiguration.java @@ -26,58 +26,81 @@ import com.sap.sse.security.ui.client.UserService; public class AutoPlayClassicConfiguration extends AutoPlayConfiguration { private static final Logger logger = Logger.getLogger(AutoPlayClassicConfiguration.class.getName()); - + @Override public void startRootNode(AutoPlayClientFactory cf, AutoPlayContextDefinition context, PerspectiveCompositeSettings settings, EventDTO initialEventData) { - cf.getSailingService().getAvailableDetailTypesForLeaderboard(context.getLeaderboardName(), new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - logger.log(Level.WARNING, "Could not load detailtypes for leaderboard", caught); - } + cf.getSailingService().getAvailableDetailTypesForLeaderboard(context.getLeaderboardName(), + null, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + logger.log(Level.WARNING, "Could not load detailtypes for leaderboard", caught); + } - @SuppressWarnings("unchecked") - @Override - public void onSuccess(Iterable result) { - StrippedLeaderboardDTO leaderBoardDTO = AutoplayHelper.getSelectedLeaderboard(initialEventData, - context.getLeaderboardName()); - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderBoardDTO, cf.getUserService(), result); - cf.setAutoPlayContext(new AutoPlayContextImpl(autoplayLifecycle, - (PerspectiveCompositeSettings) settings, - AutoPlayClassicConfiguration.this, context, initialEventData)); - // start sixty inch slide loop nodes... - RootNodeClassic root = new RootNodeClassic(cf); - root.start(cf.getEventBus()); - } - }); + @SuppressWarnings("unchecked") + @Override + public void onSuccess(Iterable result) { + StrippedLeaderboardDTO leaderBoardDTO = AutoplayHelper.getSelectedLeaderboard(initialEventData, + context.getLeaderboardName()); + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle( + leaderBoardDTO, cf.getUserService(), result); + cf.setAutoPlayContext(new AutoPlayContextImpl(autoplayLifecycle, + (PerspectiveCompositeSettings) settings, + AutoPlayClassicConfiguration.this, context, initialEventData)); + // start sixty inch slide loop nodes... + RootNodeClassic root = new RootNodeClassic(cf); + root.start(cf.getEventBus()); + } + }); } @Override public void openSettingsDialog(EventDTO selectedEvent, StrippedLeaderboardDTO leaderboard, - OnSettingsCallback settingsCallback, PerspectiveCompositeSettings settings, AutoPlayContextDefinition apcd, UserService userService) { + OnSettingsCallback settingsCallback, PerspectiveCompositeSettings settings, + AutoPlayContextDefinition apcd, UserService userService) { DialogCallback> callback = new DialogCallback>() { @Override public void ok(PerspectiveCompositeSettings editedObject) { - settingsCallback.newSettings(editedObject); + settingsCallback.newSettings(editedObject, + getUrlWithSettings(apcd, editedObject, leaderboard, userService)); } @Override public void cancel() { } }; - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, userService, Arrays.asList(DetailType.values())); - PerspectiveCompositeSettings autoplayPerspectiveSettings = autoplayLifecycle - .createDefaultSettings(); - LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>(Window.Location.getPath(), autoplayLifecycle::createDefaultSettings, apcd); - new SettingsDialogForLinkSharing>(settingsGenerator, - autoplayLifecycle, autoplayPerspectiveSettings, StringMessages.INSTANCE, true, callback).show(); + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, userService, + Arrays.asList(DetailType.values())); + @SuppressWarnings("unchecked") + PerspectiveCompositeSettings autoplayPerspectiveSettings = settings != null + ? (PerspectiveCompositeSettings) settings + : autoplayLifecycle.createDefaultSettings(); + LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>( + Window.Location.getPath(), autoplayLifecycle::createDefaultSettings, apcd); + new SettingsDialogForLinkSharing>( + settingsGenerator, autoplayLifecycle, autoplayPerspectiveSettings, StringMessages.INSTANCE, true, + callback).show(); } @Override - public void loadSettingsDefault(EventDTO selectedEvent, StrippedLeaderboardDTO leaderboard, - UserService userService, OnSettingsCallback settingsCallback) { - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, userService, Arrays.asList(DetailType.values())); - settingsCallback.newSettings(autoplayLifecycle.createDefaultSettings()); + public void loadSettingsDefault(EventDTO selectedEvent, AutoPlayContextDefinition apcd, + StrippedLeaderboardDTO leaderboard, UserService userService, OnSettingsCallback settingsCallback) { + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, userService, + Arrays.asList(DetailType.values())); + PerspectiveCompositeSettings defaultSettings = autoplayLifecycle + .createDefaultSettings(); + settingsCallback.newSettings(defaultSettings, + getUrlWithSettings(apcd, defaultSettings, leaderboard, userService)); } + private String getUrlWithSettings(AutoPlayContextDefinition apcd, + PerspectiveCompositeSettings settings, StrippedLeaderboardDTO leaderboard, + UserService userService) { + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, userService, + Arrays.asList(DetailType.values())); + LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>( + Window.Location.getPath(), autoplayLifecycle::createDefaultSettings, apcd); + + return settingsGenerator.createUrl(settings); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java index 0ea917d7fd1..f73c2f82b74 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java @@ -17,6 +17,7 @@ import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; @@ -35,6 +36,7 @@ public class IdleRaceLeaderboard extends FiresPlaceNode { } public void onStart() { + leaderboardTimer.setTime(MillisecondsTimePoint.now().asMillis()); leaderboardTimer.play(); PerspectiveCompositeSettings settings = cf.getAutoPlayCtxSignalError() .getAutoplaySettings(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleSixtyInchLeaderboardNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleSixtyInchLeaderboardNode.java index 2b2231c106a..ee1361b1c53 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleSixtyInchLeaderboardNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleSixtyInchLeaderboardNode.java @@ -93,11 +93,11 @@ public class IdleSixtyInchLeaderboardNode extends FiresPlaceNode { private void startWithLeaderbaord(final MultiRaceLeaderboardSettings leaderboardSettings, CompetitorSelectionProvider provider, String leaderboardName) { - MultiRaceLeaderboardPanel leaderboardPanel = new MultiRaceLeaderboardPanel(null, null, cf.getSailingService(), - new AsyncActionsExecutor(), leaderboardSettings, false, provider, timer, null, leaderboardName, - cf.getErrorReporter(), StringMessages.INSTANCE, false, null, false, null, false, true, false, false, - false, new SixtyInchLeaderboardStyle(true), FlagImageResolverImpl.get(), - Arrays.asList(DetailType.values())); + MultiRaceLeaderboardPanel leaderboardPanel = new MultiRaceLeaderboardPanel(null, null, + cf.getSailingService(() -> leaderboardName), new AsyncActionsExecutor(), leaderboardSettings, false, + provider, timer, null, leaderboardName, cf.getErrorReporter(), StringMessages.INSTANCE, false, null, + false, null, false, true, false, false, false, new SixtyInchLeaderboardStyle(true), + FlagImageResolverImpl.get(), Arrays.asList(DetailType.values())); IdleSixtyInchLeaderboardPlace place = new IdleSixtyInchLeaderboardPlace(leaderboardPanel, provider, durationConsumer); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java index c7e5493dc6b..b5bd0f557bd 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java @@ -24,6 +24,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.raceboard.RaceBoardPanel; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.RaceboardDataDTO; +import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; @@ -54,6 +55,8 @@ public class LiveRaceBoardNode extends FiresPlaceNode implements RaceTimesInfoPr } public void onStart() { + raceboardTimer.setTime(MillisecondsTimePoint.now().asMillis()); + raceboardTimer.play(); raceTimesInfoProvider.addRaceIdentifier(cf.getAutoPlayCtxSignalError().getLifeOrPreLiveRace(), true); PerspectiveCompositeSettings settings = cf.getAutoPlayCtxSignalError().getAutoplaySettings(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartPresenterImpl.java index 69d1a6a2e36..879c748387c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartPresenterImpl.java @@ -55,10 +55,10 @@ public class AutoPlayStartPresenterImpl extends AbstractActivity implements Auto StrippedLeaderboardDTO leaderBoard = AutoplayHelper.getSelectedLeaderboard(event, apcd.getLeaderboardName()); - apcd.getType().getConfig().loadSettingsDefault(event, leaderBoard, clientFactory.getUserService(), new OnSettingsCallback() { + apcd.getType().getConfig().loadSettingsDefault(event, apcd, leaderBoard, clientFactory.getUserService(), new OnSettingsCallback() { @Override - public void newSettings(PerspectiveCompositeSettings newSettings) { + public void newSettings(PerspectiveCompositeSettings newSettings, String urlWithSettings) { if (newSettings != null) { serializer.deserializeSettingsMapFromCurrentLocation(newSettings); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartViewImpl.java index ce8f0b88f03..2e21e7d8da2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/autoplaystart/AutoPlayStartViewImpl.java @@ -8,7 +8,6 @@ import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -30,8 +29,6 @@ import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.GWTLocaleUtil; import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings; -import com.sap.sse.gwt.settings.SettingsToUrlSerializer; -import com.sap.sse.gwt.settings.UrlBuilderUtil; public class AutoPlayStartViewImpl extends Composite implements AutoPlayStartView { private static StartPageViewUiBinder uiBinder = GWT.create(StartPageViewUiBinder.class); @@ -151,9 +148,9 @@ public class AutoPlayStartViewImpl extends Composite implements AutoPlayStartVie selectedAutoPlayType.getConfig().openSettingsDialog(selectedEvent, selectedLeaderboard, new OnSettingsCallback() { @Override - public void newSettings(PerspectiveCompositeSettings newSettings) { + public void newSettings(PerspectiveCompositeSettings newSettings, String urlWithSettings) { settings = newSettings; - updateURL(); + updateURL(urlWithSettings); } }, settings, apcd, currentPresenter.getUserService()); } @@ -197,12 +194,12 @@ public class AutoPlayStartViewImpl extends Composite implements AutoPlayStartVie settingsButton.removeStyleName(SharedResources.INSTANCE.mainCss().buttoninactive()); startAutoPlayButton.removeStyleName(SharedResources.INSTANCE.mainCss().buttoninactive()); apcd = new AutoPlayContextDefinitionImpl(selectedAutoPlayType, selectedEvent.id, selectedLeaderboardName); - apcd.getType().getConfig().loadSettingsDefault(selectedEvent, selectedLeaderboard, currentPresenter.getUserService(), + apcd.getType().getConfig().loadSettingsDefault(selectedEvent, apcd, selectedLeaderboard, currentPresenter.getUserService(), new OnSettingsCallback() { @Override - public void newSettings(PerspectiveCompositeSettings newSettings) { + public void newSettings(PerspectiveCompositeSettings newSettings, String urlWithSettings) { settings = newSettings; - updateURL(); + updateURL(urlWithSettings); } }); } @@ -210,16 +207,10 @@ public class AutoPlayStartViewImpl extends Composite implements AutoPlayStartVie return readyToGo; } - private void updateURL() { - UrlBuilder urlBuilder = UrlBuilderUtil.createUrlBuilderFromCurrentLocationWithCleanParameters(); - SettingsToUrlSerializer urlSerializer = new SettingsToUrlSerializer(); - if (settings != null) { - urlSerializer.serializeSettingsMapToUrlBuilder(settings, urlBuilder); - } - urlSerializer.serializeToUrlBuilder(apcd, urlBuilder); - configuratedUrl = urlBuilder.buildString(); - configStarter.setText(configuratedUrl); - configStarter.setHref(configuratedUrl); + private void updateURL(String urlWithSettings) { + configuratedUrl = urlWithSettings; + configStarter.setText(urlWithSettings); + configStarter.setHref(urlWithSettings); } @UiHandler("localeSelectionBox") diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java index bd37fc96661..b03b79dc71f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/utils/AutoplayHelper.java @@ -284,7 +284,7 @@ public class AutoplayHelper { RaceMapZoomSettings autoFollowRace = new RaceMapZoomSettings(typesToConsiderOnZoom, true); RaceMapSettings settings = new RaceMapSettings(autoFollowRace, new RaceMapHelpLinesSettings(), false, 15, 100000l, false, RaceMapSettings.DEFAULT_BUOY_ZONE_RADIUS, false, true, false, false, false, false, - RaceMapSettings.getDefaultManeuvers(), false, false, /* startCountDownFontSizeScaling */ 1.5); + RaceMapSettings.getDefaultManeuvers(), false, false, /* startCountDownFontSizeScaling */ 1.5, /* showManeuverLossVisualization */ false); RaceMapLifecycle raceMapLifecycle = new RaceMapLifecycle(StringMessages.INSTANCE); final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(currentLiveRace, competitorsAndTheirBoats); RaceCompetitorSelectionModel competitorSelectionProvider = new RaceCompetitorSelectionModel( diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SourceCommon.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SourceCommon.gwt.xml index a20e68ee3dd..f1e0f650ecf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SourceCommon.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SourceCommon.gwt.xml @@ -6,6 +6,8 @@ + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java index c4f83ff28d1..4d755860ec3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java @@ -45,6 +45,7 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata.CANOE_TAIFUN.getDisplayName(), imageResources.CanoeTaifunIcon()); boatClassIconsMap.put(BoatClassMasterdata.CONTENDER.getDisplayName(), imageResources.ContenderIcon()); boatClassIconsMap.put(BoatClassMasterdata.CC_30.getDisplayName(), imageResources.CC30Icon()); + boatClassIconsMap.put(BoatClassMasterdata.CLUB_SWAN_50.getDisplayName(), imageResources.CLUBSWAN50Icon()); boatClassIconsMap.put(BoatClassMasterdata.DELPHIA_24.getDisplayName(), imageResources.Delphia24Icon()); boatClassIconsMap.put(BoatClassMasterdata.DRAGON_INT.getDisplayName(), imageResources.DragonIcon()); boatClassIconsMap.put(BoatClassMasterdata.DYAS.getDisplayName(), imageResources.DyasIcon()); @@ -58,6 +59,7 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata.FARR_280.getDisplayName(), imageResources.Farr280Icon()); boatClassIconsMap.put(BoatClassMasterdata.FINN.getDisplayName(), imageResources.FinnIcon()); boatClassIconsMap.put(BoatClassMasterdata.FLYING_DUTCHMAN.getDisplayName(), imageResources.FlyingDutchmanIcon()); + boatClassIconsMap.put(BoatClassMasterdata.FLYING_JUNIOR.getDisplayName(), imageResources.FlyingJuniorIcon()); boatClassIconsMap.put(BoatClassMasterdata.FOLKBOAT.getDisplayName(), imageResources.FolkBoatIcon()); boatClassIconsMap.put(BoatClassMasterdata.FUN.getDisplayName(), imageResources.FUNIcon()); boatClassIconsMap.put(BoatClassMasterdata.GC_32.getDisplayName(), imageResources.GC32Icon()); @@ -126,6 +128,8 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata.TOM_28_MAX.getDisplayName(), imageResources.Tom28MaxIcon()); boatClassIconsMap.put(BoatClassMasterdata.TP52.getDisplayName(), imageResources.TP52Icon()); boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon()); + boatClassIconsMap.put(BoatClassMasterdata.VARIANTA.getDisplayName(), imageResources.VariantaIcon()); + boatClassIconsMap.put(BoatClassMasterdata.VAURIEN.getDisplayName(), imageResources.VaurienIcon()); boatClassIconsMap.put(BoatClassMasterdata.VENT_D_OUEST.getDisplayName(), imageResources.VentdOuestIcon()); boatClassIconsMap.put(BoatClassMasterdata.VIPER_640.getDisplayName(), imageResources.Viper640Icon()); boatClassIconsMap.put(BoatClassMasterdata.VX_ONE.getDisplayName(), imageResources.VXOneIcon()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java index 2855dbbf8db..027523c1035 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java @@ -132,6 +132,10 @@ public interface BoatClassImageResources extends ClientBundle { @ImageOptions(preventInlining = true) ImageResource FinnIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_JUNIOR.png") + @ImageOptions(preventInlining = true) + ImageResource FlyingJuniorIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_DUTCHMAN.png") @ImageOptions(preventInlining = true) ImageResource FlyingDutchmanIcon(); @@ -396,6 +400,10 @@ public interface BoatClassImageResources extends ClientBundle { @ImageOptions(preventInlining = true) ImageResource CC30Icon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/CLUBSWAN50.png") + @ImageOptions(preventInlining = true) + ImageResource CLUBSWAN50Icon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/GP_26.png") @ImageOptions(preventInlining = true) ImageResource GP26Icon(); @@ -431,4 +439,12 @@ public interface BoatClassImageResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RUNNING.png") @ImageOptions(preventInlining = true) ImageResource RunningIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/VAURIEN.png") + @ImageOptions(preventInlining = true) + ImageResource VaurienIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/VARIANTA.png") + @ImageOptions(preventInlining = true) + ImageResource VariantaIcon(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml index 15379e2d27b..89523c3b333 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml @@ -25,6 +25,7 @@ .tabExtension { float: right; + margin-top: .5em; } .tabExtension > * { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/communication/routing/ProvidesLeaderboardRouting.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/communication/routing/ProvidesLeaderboardRouting.java new file mode 100644 index 00000000000..3c224cd0998 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/communication/routing/ProvidesLeaderboardRouting.java @@ -0,0 +1,16 @@ +package com.sap.sailing.gwt.common.communication.routing; + +import com.sap.sailing.domain.common.sharding.ShardingType; +import com.sap.sse.gwt.client.ServiceRoutingProvider; + +/** + * Interface used to provide leaderboard specific routing, see {@link ServiceRoutingProvider} + */ +public interface ProvidesLeaderboardRouting extends ServiceRoutingProvider { + + String getLeaderboardName(); + + default String routingSuffixPath() { + return ShardingType.LEADERBOARDNAME.encodeIfNeeded(getLeaderboardName()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetCompetitionFormatRacesAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetCompetitionFormatRacesAction.java index 7479b838571..479278ed257 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetCompetitionFormatRacesAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetCompetitionFormatRacesAction.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.communication.event; import java.util.UUID; import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.home.communication.SailingAction; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.server.EventActionUtil; @@ -26,7 +27,7 @@ import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException; *

      */ public class GetCompetitionFormatRacesAction implements - SailingAction>>, IsClientCacheable { + SailingAction>>, IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String regattaId; @@ -58,4 +59,9 @@ public class GetCompetitionFormatRacesAction implements public void cacheInstanceKey(StringBuilder key) { key.append(eventId).append("_").append(regattaId); } + + @Override + public String getLeaderboardName() { + return regattaId; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetFinishedRacesAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetFinishedRacesAction.java index f634c0ec9b4..36103a132d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetFinishedRacesAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetFinishedRacesAction.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.communication.event; import java.util.UUID; import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.home.communication.SailingAction; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.server.EventActionUtil; @@ -25,7 +26,7 @@ import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL; *

      */ public class GetFinishedRacesAction implements SailingAction>>, - IsClientCacheable { + IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String regattaId; @@ -59,4 +60,9 @@ public class GetFinishedRacesAction implements SailingAction */ public class GetLiveRacesForRegattaAction implements SailingAction>>, - IsClientCacheable { + IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String regattaName; @@ -66,4 +67,10 @@ public class GetLiveRacesForRegattaAction implements SailingAction5 minutes. *

      */ -public class GetRegattaStatisticsAction implements SailingAction>, IsClientCacheable { +public class GetRegattaStatisticsAction implements SailingAction>, IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String regattaId; @@ -53,4 +54,9 @@ public class GetRegattaStatisticsAction implements SailingAction5 minutes for currently running events. *

      */ -public class GetRegattaWithProgressAction implements SailingAction>, IsClientCacheable { +public class GetRegattaWithProgressAction implements SailingAction>, IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String regattaId; @@ -55,4 +56,9 @@ public class GetRegattaWithProgressAction implements SailingAction */ -public class GetMiniLeaderbordAction implements SailingAction>, IsClientCacheable { +public class GetMiniLeaderbordAction implements SailingAction>, IsClientCacheable, ProvidesLeaderboardRouting { private UUID eventId; private String leaderboardName; private int limit = 0; @@ -74,4 +75,9 @@ public class GetMiniLeaderbordAction implements SailingAction */ -public class GetMiniOverallLeaderbordAction implements SailingAction>, IsClientCacheable { +public class GetMiniOverallLeaderbordAction implements SailingAction>, IsClientCacheable, ProvidesLeaderboardRouting { private UUID seriesId; + private String leaderboardName; private int limit = 0; @SuppressWarnings("unused") @@ -35,8 +37,8 @@ public class GetMiniOverallLeaderbordAction implements SailingAction */ -public class GetSixtyInchStatisticAction implements SailingAction { +public class GetSixtyInchStatisticAction implements SailingAction, ProvidesLeaderboardRouting { private static final Logger LOGGER = Logger.getLogger(GetSixtyInchStatisticAction.class.getName()); // transfer as string, to avoid transfering incompatible types private String racename; @@ -95,4 +96,9 @@ public class GetSixtyInchStatisticAction implements SailingAction seriesNavigation, EventListEventSeriesDTO eventSeries) { eventImage.appendChild(new EventTeaserSeriesInfoCorner(seriesNavigation, eventSeries).getElement()); + eventName.setInnerSafeHtml(LongNamesUtil.breakLongName(eventSeries.getDisplayName())); + venue.setInnerText(StringMessages.INSTANCE.lastEvent(event.getLocationOrVenue())); + eventImage.setTitle(StringMessages.INSTANCE.teaserOverallLinkToolTip()); } public void hideImage(boolean hide) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sailorinfo/SailorInfo.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sailorinfo/SailorInfo.java index e8f8256c259..734bbc937e8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sailorinfo/SailorInfo.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sailorinfo/SailorInfo.java @@ -20,12 +20,12 @@ public class SailorInfo extends Composite { FlowPanel panel = new FlowPanel(); Style panelStyle = panel.getElement().getStyle(); panelStyle.setDisplay(Display.INLINE_BLOCK); + panelStyle.setVerticalAlign(VerticalAlign.BOTTOM); Label sailorInfoLabel = new Label(); sailorInfoLabel.getElement().setInnerHTML(i18n.sailorInfoLongText().replace("\n", "
      ")); Style labelStyle = sailorInfoLabel.getElement().getStyle(); labelStyle.setDisplay(Display.INLINE_BLOCK); - sailorInfoLabel.getElement().getStyle().setMarginTop(0.5, Unit.EM); sailorInfoLabel.getElement().getStyle().setMarginRight(0.5, Unit.EM); panel.add(sailorInfoLabel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java index aa8ca601d0f..eaeb6c02a20 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java @@ -37,7 +37,6 @@ import com.sap.sailing.gwt.settings.client.raceboard.RaceBoardPerspectiveOwnSett import com.sap.sailing.gwt.settings.client.raceboard.RaceboardContextDefinition; import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaOverviewContextDefinition; import com.sap.sailing.gwt.settings.client.regattaoverview.RegattaRaceStatesSettings; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapLifecycle; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings; @@ -83,10 +82,6 @@ public abstract class AbstractEventActivity ex this.timerForClientServerOffset = new Timer(PlayModes.Replay); this.clientFactory = clientFactory; } - - public SailingServiceAsync getSailingService() { - return clientFactory.getSailingService(); - } public SailingDispatchSystem getDispatch() { return clientFactory.getDispatch(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventView.java index 67df7061b24..130a9419a8a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventView.java @@ -21,7 +21,6 @@ import com.sap.sailing.gwt.home.shared.places.event.EventDefaultPlace; import com.sap.sailing.gwt.home.shared.places.events.EventsPlace; import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace; import com.sap.sailing.gwt.home.shared.places.start.StartPlace; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.refresh.ErrorAndBusyClientFactory; import com.sap.sse.gwt.client.player.Timer; @@ -37,7 +36,6 @@ public interface EventView> asyncCallback) { + clientFactory.getSailingService(()-> leaderboardName).getAvailableDetailTypesForLeaderboard(leaderboardName, raceOrNull, asyncCallback); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaView.java index 3ba25f59503..4b6ba6e5061 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaView.java @@ -1,5 +1,8 @@ package com.sap.sailing.gwt.home.desktop.places.event.regatta; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.home.desktop.places.event.EventView; import com.sap.sailing.gwt.home.desktop.places.event.regatta.overviewtab.RegattaOverviewPlace; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; @@ -15,5 +18,8 @@ public interface EventRegattaView extends EventView> asyncCallback); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/RegattaAnalyticsDataManager.java index cd2674c4483..c68b0705aba 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/RegattaAnalyticsDataManager.java @@ -6,6 +6,7 @@ import com.sap.sailing.gwt.settings.client.leaderboard.MultiRaceLeaderboardSetti import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.SailingClientFactory; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChart; @@ -31,16 +32,16 @@ public class RegattaAnalyticsDataManager { private final CompetitorSelectionModel competitorSelectionProvider; private final AsyncActionsExecutor asyncActionsExecutor; private final ErrorReporter errorReporter; - private final SailingServiceAsync sailingService; + private final SailingClientFactory sailingCF; private final Timer timer; private final FlagImageResolver flagImageResolver; - public RegattaAnalyticsDataManager(final SailingServiceAsync sailingService, + public RegattaAnalyticsDataManager(final SailingClientFactory sailingCF, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, FlagImageResolver flagImageResolver) { this.flagImageResolver = flagImageResolver; this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); - this.sailingService = sailingService; + this.sailingCF = sailingCF; this.asyncActionsExecutor = asyncActionsExecutor; this.timer = timer; this.errorReporter = errorReporter; @@ -53,6 +54,7 @@ public class RegattaAnalyticsDataManager { final String leaderboardGroupName, String leaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn, Iterable availableDetailTypes) { if (leaderboardPanel == null) { + SailingServiceAsync sailingService = sailingCF.getSailingService(()-> leaderboardName); leaderboardPanel = new MultiRaceLeaderboardPanel(parent, context, sailingService, asyncActionsExecutor, leaderboardSettings, @@ -68,6 +70,7 @@ public class RegattaAnalyticsDataManager { public MultiCompetitorLeaderboardChart createMultiCompetitorChart(String leaderboardName, DetailType chartDetailType) { if(multiCompetitorChart == null) { + SailingServiceAsync sailingService = sailingCF.getSailingService(()-> leaderboardName); multiCompetitorChart = new MultiCompetitorLeaderboardChart(null, null, sailingService, asyncActionsExecutor, leaderboardName, chartDetailType, competitorSelectionProvider, timer, StringMessages.INSTANCE, false, errorReporter); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java index 2e738b85c8d..3a01b27d22c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.home.desktop.places.event.regatta; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.TextTransform; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; @@ -65,7 +66,12 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event racesTabUi = new RegattaRacesTabView(flagImageResolver); regattaOverviewTabUi = new RegattaOverviewTabView(flagImageResolver); initWidget(uiBinder.createAndBindUi(this)); - + + String sailorsInfoURL = currentPresenter.getEventDTO().getSailorsInfoWebsiteURL(); + if (sailorsInfoURL != null && !sailorsInfoURL.isEmpty()) { + tabPanelUi.addTabExtension(new SailorInfo(sailorsInfoURL)); + } + if(currentPresenter.getEventDTO().getType() == EventType.SERIES) { final PlaceNavigation currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation(); Anchor seriesAnchor = new Anchor(i18n.overallLeaderboardSelection()); @@ -82,14 +88,10 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event seriesAnchor.setStyleName(SharedResources.INSTANCE.mainCss().button()); seriesAnchor.addStyleName(SharedResources.INSTANCE.mainCss().buttonprimary()); Style style = seriesAnchor.getElement().getStyle(); - style.setFontSize(16, Unit.PX); - style.setPadding(0.75, Unit.EM); + style.setPaddingLeft(0.75, Unit.EM); + style.setPaddingRight(0.75, Unit.EM); + style.setTextTransform(TextTransform.UPPERCASE); tabPanelUi.addTabExtension(seriesAnchor); - } else { - String sailorsInfoURL = currentPresenter.getEventDTO().getSailorsInfoWebsiteURL(); - if(sailorsInfoURL != null && ! sailorsInfoURL.isEmpty()) { - tabPanelUi.addTabExtension(new SailorInfo(sailorsInfoURL)); - } } final Iterable spots = currentPresenter.getEventDTO().getAllWindFinderSpotIdsUsedByEvent(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java index 327906f426a..54b514a9f7c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/analyticstab/RegattaCompetitorAnalyticsTabView.java @@ -99,8 +99,8 @@ public class RegattaCompetitorAnalyticsTabView extends SharedLeaderboardRegattaT } }; if (regattaAnalyticsManager.getLeaderboardPanel() == null) { - currentPresenter.getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + currentPresenter.getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onSuccess(Iterable result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java index 2b3d471ba91..588316451ce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java @@ -147,8 +147,8 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView> callback) { - currentPresenter.getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + currentPresenter.getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onSuccess(Iterable result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/EventSeriesAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/EventSeriesAnalyticsDataManager.java index d2356ebb303..0ffae739c78 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/EventSeriesAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/EventSeriesAnalyticsDataManager.java @@ -3,8 +3,11 @@ package com.sap.sailing.gwt.home.desktop.places.fakeseries; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.function.Function; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.settings.client.leaderboard.MultiCompetitorLeaderboardChartSettings; import com.sap.sailing.gwt.settings.client.leaderboard.MultiRaceLeaderboardSettings; @@ -12,6 +15,7 @@ import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.DebugIdHelper; import com.sap.sailing.gwt.ui.client.FlagImageResolver; +import com.sap.sailing.gwt.ui.client.SailingClientFactory; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorLeaderboardChart; @@ -42,33 +46,32 @@ public class EventSeriesAnalyticsDataManager { private final CompetitorSelectionModel competitorSelectionProvider; private final AsyncActionsExecutor asyncActionsExecutor; private final ErrorReporter errorReporter; - private final SailingServiceAsync sailingService; + private final SailingClientFactory sailingCF; private final Timer timer; private final int MAX_COMPETITORS_IN_CHART = 30; private final FlagImageResolver flagImageResolver; - public EventSeriesAnalyticsDataManager(final SailingServiceAsync sailingService, + public EventSeriesAnalyticsDataManager(final SailingClientFactory sailingCF, AsyncActionsExecutor asyncActionsExecutor, Timer timer, ErrorReporter errorReporter, FlagImageResolver flagImageResolver) { this.flagImageResolver = flagImageResolver; this.competitorSelectionProvider = new CompetitorSelectionModel(/* hasMultiSelection */true); - this.sailingService = sailingService; + this.sailingCF = sailingCF; this.asyncActionsExecutor = asyncActionsExecutor; this.timer = timer; this.errorReporter = errorReporter; this.overallLeaderboardPanel = null; this.multiCompetitorChart = null; } - - public SailingServiceAsync getSailingService() { - return sailingService; - } public MultiRaceLeaderboardPanel createMultiRaceOverallLeaderboardPanel(Component parent, ComponentContext context, final MultiRaceLeaderboardSettings leaderboardSettings, final String leaderboardGroupName, String leaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn, Iterable availableDetailTypes) { + + if(overallLeaderboardPanel == null) { + SailingServiceAsync sailingService = sailingCF.getSailingService(()-> leaderboardName); overallLeaderboardPanel = new MultiRaceLeaderboardPanel(parent, context, sailingService, asyncActionsExecutor, leaderboardSettings, true, competitorSelectionProvider, timer, leaderboardGroupName, leaderboardName, errorReporter, @@ -84,7 +87,10 @@ public class EventSeriesAnalyticsDataManager { public MultiCompetitorLeaderboardChart createMultiCompetitorChart(Component parent, ComponentContext context, String leaderboardName, DetailType chartDetailType) { + if(multiCompetitorChart == null) { + SailingServiceAsync sailingService = sailingCF.getSailingService(()-> leaderboardName); + multiCompetitorChart = new MultiCompetitorLeaderboardChart(parent, context, sailingService, asyncActionsExecutor, leaderboardName, chartDetailType, @@ -98,8 +104,10 @@ public class EventSeriesAnalyticsDataManager { MultiRaceLeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, String leaderboardGroupName, String metaLeaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn, Iterable availableDetailTypes) { - if(multiLeaderboardPanel == null) { - multiLeaderboardPanel = new MultiLeaderboardProxyPanel(parent, context, sailingService, metaLeaderboardName, + if (multiLeaderboardPanel == null) { + final Function sailingServiceFactory = leaderBoardName -> sailingCF + .getSailingService(() -> leaderBoardName); + multiLeaderboardPanel = new MultiLeaderboardProxyPanel(parent, context, sailingServiceFactory, metaLeaderboardName, asyncActionsExecutor, timer, true /* isEmbedded */, preselectedLeaderboardName, errorReporter, StringMessages.INSTANCE, showRaceDetails, autoExpandLastRaceColumn, leaderboardSettings, flagImageResolver, availableDetailTypes); @@ -179,4 +187,10 @@ public class EventSeriesAnalyticsDataManager { public Timer getTimer() { return timer; } + + public void getAvailableDetailTypesForLeaderboard(String leaderboardName, RegattaAndRaceIdentifier raceOrNull, + AsyncCallback> asyncCallback) { + SailingServiceAsync sailingService = sailingCF.getSailingService(()-> leaderboardName); + sailingService.getAvailableDetailTypesForLeaderboard(leaderboardName, raceOrNull, asyncCallback); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/SeriesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/SeriesActivity.java index 3e615dad84c..260c47542b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/SeriesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/SeriesActivity.java @@ -59,7 +59,7 @@ public class SeriesActivity extends AbstractActivity implements SeriesView.Prese if (this.ctx.getAnalyticsManager() == null) { ctx.withAnalyticsManager(new EventSeriesAnalyticsDataManager( // - clientFactory.getSailingService(), // + clientFactory, // asyncActionsExecutor, // new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds), // clientFactory.getErrorReporter(), flagImageResolver)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java index a2c71c335ec..e838a90f357 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/analyticstab/EventSeriesCompetitorAnalyticsTabView.java @@ -78,8 +78,8 @@ public class EventSeriesCompetitorAnalyticsTabView extends SharedLeaderboardEven } }; if (eventSeriesAnalyticsManager.getLeaderboardPanel() == null) { - eventSeriesAnalyticsManager.getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + eventSeriesAnalyticsManager.getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onSuccess(Iterable result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/leaderboardstab/EventSeriesLeaderboardsTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/leaderboardstab/EventSeriesLeaderboardsTabView.java index 3a6df7f843b..0ad163958f8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/leaderboardstab/EventSeriesLeaderboardsTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/leaderboardstab/EventSeriesLeaderboardsTabView.java @@ -77,8 +77,8 @@ public class EventSeriesLeaderboardsTabView extends Composite implements SeriesT final EventSeriesAnalyticsDataManager regattaAnalyticsManager = currentPresenter.getCtx() .getAnalyticsManager(); - regattaAnalyticsManager.getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + regattaAnalyticsManager.getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onSuccess(Iterable result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/overallleaderboardtab/EventSeriesOverallLeaderboardTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/overallleaderboardtab/EventSeriesOverallLeaderboardTabView.java index 5f924bdb3c3..b34f29b1291 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/overallleaderboardtab/EventSeriesOverallLeaderboardTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/fakeseries/overallleaderboardtab/EventSeriesOverallLeaderboardTabView.java @@ -64,7 +64,7 @@ public class EventSeriesOverallLeaderboardTabView extends SharedLeaderboardEvent contentArea.setWidget(currentPresenter.getErrorAndBusyClientFactory().createBusyView()); String leaderboardName = currentPresenter.getSeriesDTO().getLeaderboardId(); if (leaderboardName != null && !leaderboardName.isEmpty()) { - currentPresenter.getCtx().getAnalyticsManager().getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, new AsyncCallback>() { + currentPresenter.getCtx().getAnalyticsManager().getAvailableDetailTypesForLeaderboard(leaderboardName, null, new AsyncCallback>() { @Override public void onSuccess(Iterable result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html index bc21c9b9ca2..f1d5075a2e4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html @@ -4,6 +4,14 @@

      What's New - SAP Sailing Race Manager

      +
      February 2018
      +
        +
      • The protest time is now configurable in the backend. An empty value means, the RCApp + will take 90 minutes as default. If no time is set in the backend, the RCApp will save a + custom duration as new default. A server side duration (device or regatta specific) will always + be the default.
      • +
      +
      August 2017
      • The result list can now be edited without a finish time.
      • @@ -22,6 +30,11 @@ for the competitor is cleared, the rank is set to 0 and the penalty is set to "NONE" then the competitor will remain in the list, and this "empty" result will be sent to the server, clearing any previous score + corrections that were sent to the server. This is the only way to + "reset" an erroneous score correction entirely from the app, because + swiping and thus removing the competitor from the list would leave the + previous score correction unchanged on the server upon the next result + publication.
      • When entering and publishing partial results, updates will only be applied for those competitors for which a result has been entered. In particular, @@ -30,12 +43,6 @@ partial results entered on one device will not unnecessarily remove changes applied on another device.
      • - corrections that were sent to the server. This is the only way to - "reset" an erroneous score correction entirely from the app, because - swiping and thus removing the competitor from the list would leave the - previous score correction unchanged on the server upon the next result - publication. -
      April 2017
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index 10bb3916aee..fa28a560f40 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -4,11 +4,31 @@

      What's New - SAP Sailing Analytics

      +
      July 2018
      +
        +
      • In RaceBoard.html there is a new feature for maneuver loss visualization. There is a setting in the map settings and in the maneuver pop up. +
      • In RaceBoard.html when zooming into a specific time range, the time slider will provide a "reset zoom" button in expanded mode.
      • +
      +
      June 2018
      • In RaceBoard.html the availability of media is now visualized under the time slider via a bar overlay. When hovering the bar, the title of the video is shown.
      • In RaceBoard.html video players are now ensured to be on top of all video players when clicking/touching them. This means you can bring a specific video window in the foreground in case there are multiple ones that overlap each other.
      • +
      • In RaceBoard.html the various areas (e.g. leaderboard and charts) now keep their size on expansion/collapsion.
      • +
      • The filter selection in DataMining.html has been overhauled. +
          +
        • All available filter dimensions are now listed where the different data retriever levels have been listed previously. + The different dimensions are divided by the corresponding data retriever level and can be filtered with the input + field above the list. Both the dimension and data retriever level names can be used to filter the dimension list. +
        • +
        • Selecting a filter dimension adds a table containing the available values of the selected dimension. The order of + the value tables is equal to the order of the dimensions list. +
        • +
        • Each filter value table now has its own scroll-bar.
        • +
        +
      • +
      • The grouping dimensions are now ordered alphabetically.
      April 2018
      @@ -30,7 +50,7 @@
    1. The data source and key figure selection of the DataMining.html have been simplified into a single input field widget.
    2. -
    3. The key figure selection can now be searched and filtered with keywords by typing in the new selection widget. +
    4. The key figure selection can now be searched and filtered with keywords by typing in the new selection widget.
    5. Zooming in on a competitor chart in the race viewer will now adjust the time resolution to that selected in the settings if before zooming the resolution had to be reduced to remain under the limit of data points that diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java index 9f0d24cda4a..acec846051f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java @@ -6,6 +6,7 @@ import java.util.Set; import java.util.UUID; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -39,9 +40,11 @@ public abstract class AbstractEventView

      exten static class AbstractEventViewLayout { @UiField(provided = true) EventHeader eventHeaderUi; @UiField Quickfinder quickFinderUi; - @UiField SimpleInfoBlock simpleInfoUi; + @UiField SimpleInfoBlock sailorInfoUi; + @UiField SimpleInfoBlock seriesNavigationoUi; + @UiField DivElement windfinderWrapperUi; @UiField(provided = true) WindfinderControl windfinderUi; - @UiField SimplePanel viewContentUi;; + @UiField SimplePanel viewContentUi; private AbstractEventViewLayout(EventViewDTO event, String regattaName, PlaceNavigation logoNavigation) { this.eventHeaderUi = new EventHeader(event, regattaName, logoNavigation); @@ -125,18 +128,19 @@ public abstract class AbstractEventView

      exten @Override public void setSailorInfos(String description, String buttonLabel, String url) { - layout.simpleInfoUi.setDescription(SafeHtmlUtils.fromString(description.replace("\n", " "))); - layout.simpleInfoUi.setAction(buttonLabel, url); + layout.sailorInfoUi.setDescription(SafeHtmlUtils.fromString(description.replace("\n", " "))); + layout.sailorInfoUi.setAction(buttonLabel, url); } @Override public void setWindfinderNavigations(Iterable spotData) { + layout.windfinderWrapperUi.getStyle().clearDisplay(); layout.windfinderUi.setSpotData(spotData); } @Override public void setSeriesNavigation(String buttonLabel, PlaceNavigation placeNavigation) { - layout.simpleInfoUi.setAction(buttonLabel, placeNavigation); + layout.seriesNavigationoUi.setAction(buttonLabel, placeNavigation); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml index 2258c94f492..bb367cc4c7d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml @@ -7,9 +7,12 @@ .headerWrapper { background-color: #f2f2f2; } + .windfinderWrapper { + padding-bottom: 10px; + margin: 0 10px; + } .windfinder { - display: block; - margin: 0 10px 10px; + width: 100%; border-radius: .384615384615385em; text-align: center; } @@ -18,8 +21,11 @@

      - - + + +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaActivity.java index 5b08d12688d..35ca51d380f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/regatta/RegattaActivity.java @@ -30,11 +30,11 @@ public class RegattaActivity extends AbstractEventActivity @Override protected EventViewBase initView() { final AbstractEventOverview view = new RegattaOverviewImpl(this, flagImageResolver); + initSailorInfo(view); initSeriesNavigation(view); initQuickfinder(view, true); initWindfinderNavigations(view); if (!isMultiRegattaEvent()) { - // initSailorInfo(view); initMedia(view); } return view; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/SeriesViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/SeriesViewImpl.java index 400bafadfcb..16be21bb162 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/SeriesViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/SeriesViewImpl.java @@ -73,7 +73,7 @@ public class SeriesViewImpl extends Composite implements SeriesView { private void setupListContent(EventSeriesViewDTO event) { leaderboardUi.setAction(MSG.showAll(), currentPresenter.getMiniOverallLeaderboardNavigation()); - refreshManager.add(leaderboardUi, new GetMiniOverallLeaderbordAction(event.getId(), 3)); + refreshManager.add(leaderboardUi, new GetMiniOverallLeaderbordAction(event.getId(), event.getLeaderboardId(), 3)); } private void setupStatisticsBox(EventSeriesViewDTO series) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/minileaderboard/SeriesMiniOverallLeaderboardViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/minileaderboard/SeriesMiniOverallLeaderboardViewImpl.java index 71dd3b0716d..2cd43df7ac1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/minileaderboard/SeriesMiniOverallLeaderboardViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/series/minileaderboard/SeriesMiniOverallLeaderboardViewImpl.java @@ -41,7 +41,8 @@ public class SeriesMiniOverallLeaderboardViewImpl extends Composite implements S initWidget(uiBinder.createAndBindUi(this)); RefreshManager refreshManager = new LifecycleRefreshManager(this, currentPresenter.getDispatch()); SeriesContext ctx = presenter.getCtx(); - refreshManager.add(minileaderboardUi, new GetMiniOverallLeaderbordAction(UUID.fromString(ctx.getSeriesId()))); + refreshManager.add(minileaderboardUi, new GetMiniOverallLeaderbordAction(UUID.fromString(ctx.getSeriesId()), + presenter.getSeriesDTO().getLeaderboardId())); minileaderboardUi.setAction(MSG.details(), presenter.getOverallLeaderboardNavigation()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java index 323139e954c..14a5070e01e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java @@ -1,8 +1,17 @@ package com.sap.sailing.gwt.home.server.servlets; +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; +import com.sap.sailing.domain.common.sharding.ShardingType; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.server.SailingDispatchContextImpl; @@ -47,4 +56,19 @@ public class SailingDispatchServlet extends AbstractDispatchServlet getMailVerifiedConfirmationNavigation() { - return createLocalPlaceNavigation(new ConfirmationPlace(Action.MAIL_VERIFIED)); + return createLocalPlaceNavigation(new ConfirmationPlace(Action.MAIL_VERIFIED, null, null)); } public PlaceNavigation getPasswordResettedConfirmationNavigation(String username) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.java index 2218004f08c..52e9c7fddb0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.java @@ -26,8 +26,6 @@ public interface AnniversaryLegalMessages extends Messages{ String anniversaryLegalNoticeSectionContestParagraph(int anniversary); String anniversaryLegalNoticeSectionCommunicationParagraphTitle(); String anniversaryLegalNoticeSectionCommunicationParagraph(); - String anniversaryLegalNoticeSectionCharitablyPurposeParagraphTitle(); - String anniversaryLegalNoticeSectionCharitablyPurposeParagraph(); String anniversaryLegalNoticeSectionPrizesParagraphTitle(); String anniversaryLegalNoticeSectionPrizesParagraph(int anniversary); String anniversaryLegalNoticeSectionPersonalDataAndRightsToImagesParagraphTitle(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.properties index 2f61292ba8b..39e8f81bd47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalMessages.properties @@ -7,18 +7,16 @@ anniversaryLegalNoticeSectionOrganizationAndParticipationParagraph3=The organize anniversaryLegalNoticeSectionRequirementsHeadline=Prerequisites anniversaryLegalNoticeSectionRequirementsListItem1=SAP Sailing Analytics must be responsible for the live data analysis of each race. anniversaryLegalNoticeSectionRequirementsListItem2=To win the prize, a race must enter into the regatta evaluation and be finished. -anniversaryLegalNoticeSectionRequirementsListItem3=The \u20AC10,000 are to be used solely for charitable purposes. +anniversaryLegalNoticeSectionRequirementsListItem3=The prize money is used solely for the purpose of paying catering (food, beverage, service). anniversaryLegalNoticeSectionContestParagraphTitle=Contest anniversaryLegalNoticeSectionContestParagraph=The contest will run until the {0,number,#,###}th race has begun. anniversaryLegalNoticeSectionCommunicationParagraphTitle=Notification anniversaryLegalNoticeSectionCommunicationParagraph=The winner will be automatically announced at www.sapsailing.com. -anniversaryLegalNoticeSectionCharitablyPurposeParagraphTitle=Charitable purposes -anniversaryLegalNoticeSectionCharitablyPurposeParagraph=The charitable purpose for which the prize is to be used must fulfill certain requirements. The winner must invest the prize in promoting sports, education, literature, the public good, health and safety, the fight against poverty, or similar aims without attempting to gain any private benefit, profit, or political influence. anniversaryLegalNoticeSectionPrizesParagraphTitle=Prizes anniversaryLegalNoticeSectionPrizesParagraph=The organizer of the {0,number,#,###}th race will receive the prize money. anniversaryLegalNoticeSectionPersonalDataAndRightsToImagesParagraphTitle=Personal data and image rights anniversaryLegalNoticeSectionPersonalDataAndRightsToImagesParagraph1=By participating in the contest, each organizer grants permission (and warrants to have received corresponding consent from its members) to the organizer, its subsidiaries and affiliated businesses, respective licensees, successors, and assignees to use the contest winner''s name, likeness, and entry for purposes of advertising and trade without further notice, consent, or compensation with regard to the contest and the event at which the contest took place, unless prohibited by law. This includes printing, publishing, broadcasting, distributing, and using the contest winner''s name, portrait, picture, voice, likeness, or image (or statements from associated members about the contest) for promotional purposes in any media now known or hereafter arising anywhere in the world. -anniversaryLegalNoticeSectionPersonalDataAndRightsToImagesParagraph2=SAP SE has set the total sum at \u20AC10,000. Event organizers must propose a charitable purpose for which they plan to use the prize to SAP SE. SAP SE reserves the right to approve these proposals or reject them should they not be deemed charitable. +anniversaryLegalNoticeSectionPersonalDataAndRightsToImagesParagraph2=SAP SE has set the total sum at \u20AC3,000. Event organizers must propose a catering plan to SAP SE. SAP SE reserves the right to approve the proposal or reject it. anniversaryLegalNoticeSectionDisclaimerSectionParagraphTitle=Disclaimer anniversaryLegalNoticeSectionDisclaimerSectionParagraph=SAP will assume no responsibility or liability for incomplete, inaccurate, or misdirected entries. anniversaryLegalNoticeSectionOtherParagraphTitle=Additional information diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalNoticeBubbleContent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalNoticeBubbleContent.ui.xml index 24b2d97ea69..ff67e8f1055 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalNoticeBubbleContent.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/anniversary/AnniversaryLegalNoticeBubbleContent.ui.xml @@ -41,10 +41,6 @@

      -

      - - -

      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/error/ErrorMessage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/error/ErrorMessage.java index 46afd7d5c0f..029138da8be 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/error/ErrorMessage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/error/ErrorMessage.java @@ -12,6 +12,8 @@ import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; public class ErrorMessage extends Composite { @@ -42,7 +44,7 @@ public class ErrorMessage extends Composite { } this.errorMessageDetail.setInnerText(errorMessageDetail); - Window.setStatus(errorMessageDetail); + Notification.notify(errorMessageDetail, NotificationType.WARNING); } public void addReloadPageButtonStyleNames(String styleNames) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss index 842e9380598..faf7d6a365e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss @@ -7,7 +7,7 @@ text-decoration: none; vertical-align: bottom; padding-left: 1em; - padding-right: 0.5em + padding-right: 0.5em; } .windfindercontrol_text, .windfindercontrol_icon { display: inline-block; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/confirmation/ConfirmationPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/confirmation/ConfirmationPlace.java index 46ff3a4505d..6e975a960b8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/confirmation/ConfirmationPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/user/confirmation/ConfirmationPlace.java @@ -86,7 +86,22 @@ public class ConfirmationPlace extends AbstractBasePlace implements HasMobileVer @Override public String getToken(ConfirmationPlace place) { - return place.getAction().name()+"?u="+URL.encodeQueryString(place.getName())+"&v="+URL.encodeQueryString(place.getValidationSecret()); + final StringBuilder result = new StringBuilder(place.getAction().name()); + if (place.getName() != null || place.getValidationSecret() != null) { + result.append('?'); + if (place.getName() != null) { + result.append("u="); + result.append(URL.encodeQueryString(place.getName())); + } + if (place.getValidationSecret() != null) { + if (place.getName() != null) { + result.append('&'); + } + result.append("v="); + result.append(URL.encodeQueryString(place.getValidationSecret())); + } + } + return result.toString(); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java index 99a59b307cc..de81d943467 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaOverviewEntryPoint.java @@ -64,6 +64,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint { return; } + createAndAddDetailPanel(); createAndAddRegattaPanel(regattaOverviewContextDefinition); toggleDetailPanel(false); @@ -88,7 +89,7 @@ public class RegattaOverviewEntryPoint extends AbstractSailingEntryPoint { } private void createAndAddRegattaPanel(RegattaOverviewContextDefinition regattaOverviewContextDefinition) { - regattaPanel = new RegattaOverviewPanel(sailingService, getUserService(), this, getStringMessages(), + regattaPanel = new RegattaOverviewPanel(getSailingService(), getUserService(), this, getStringMessages(), regattaOverviewContextDefinition); regattaPanel.addHandler(new EventDTOLoadedEvent.Handler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java index cecfa375774..285bfba3f93 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/settings/client/leaderboard/LeaderboardSettingsDialogComponent.java @@ -165,7 +165,8 @@ public abstract class LeaderboardSettingsDialogComponent validator, DialogCallback callback) { 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 ddc95b91416..fc16637f3a0 100755 --- 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 @@ -85,7 +85,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements private Widget createAdminConsolePanel() { AdminConsolePanel panel = new AdminConsolePanel(getUserService(), - sailingService, getStringMessages().releaseNotes(), "/release_notes_admin.html", /* error reporter */ this, + getSailingService(), getStringMessages().releaseNotes(), "/release_notes_admin.html", / error reporter */ this, SeurityStylesheetResources.INSTANCE.css(), getStringMessages(), Permission.getAdminConsolePermissions())); SecurityStylesheetResources.INSTANCE.css(), getStringMessages(), Permission.getAdminConsolePermissions()); panel.addStyleName("adminConsolePanel"); @@ -93,7 +93,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements leaderboardsDisplayers = new HashSet<>(); leaderboardGroupsDisplayers = new HashSet<>(); - final EventManagementPanel eventManagementPanel = new EventManagementPanel(sailingService, getUserService(), this, this, getStringMessages(), panel); + final EventManagementPanel eventManagementPanel = new EventManagementPanel(getSailingService(), getUserService(), this, this, getStringMessages(), panel); eventManagementPanel.ensureDebugId("EventManagement"); panel.addToVerticalTabPanel(new DefaultRefreshableAdminConsolePanel(eventManagementPanel) { @Override @@ -107,7 +107,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* REGATTAS */ RegattaManagementPanel regattaManagementPanel = new RegattaManagementPanel( - sailingService, this, getStringMessages(), this, eventManagementPanel); + getSailingService(), this, getStringMessages(), this, eventManagementPanel); regattaManagementPanel.ensureDebugId("RegattaStructureManagement"); panel.addToVerticalTabPanel(new DefaultRefreshableAdminConsolePanel(regattaManagementPanel) { @Override @@ -120,7 +120,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* LEADERBOARDS */ final HorizontalTabLayoutPanel leaderboardTabPanel = panel.addVerticalTab(getStringMessages().leaderboards(), "LeaderboardPanel"); - final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(sailingService, this, this, + final LeaderboardConfigPanel leaderboardConfigPanel = new LeaderboardConfigPanel(getSailingService(), this, this, getStringMessages(), /* showRaceDetails */true, this); leaderboardConfigPanel.ensureDebugId("LeaderboardConfiguration"); panel.addToTabPanel(leaderboardTabPanel, new DefaultRefreshableAdminConsolePanel(leaderboardConfigPanel) { @@ -132,7 +132,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements regattasDisplayers.add(leaderboardConfigPanel); leaderboardsDisplayers.add(leaderboardConfigPanel); - final LeaderboardGroupConfigPanel leaderboardGroupConfigPanel = new LeaderboardGroupConfigPanel(sailingService, + final LeaderboardGroupConfigPanel leaderboardGroupConfigPanel = new LeaderboardGroupConfigPanel(getSailingService(), this, this, this, this, getStringMessages()); leaderboardGroupConfigPanel.ensureDebugId("LeaderboardGroupConfiguration"); panel.addToTabPanel(leaderboardTabPanel, new DefaultRefreshableAdminConsolePanel(leaderboardGroupConfigPanel) { @@ -157,7 +157,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements final HorizontalTabLayoutPanel racesTabPanel = panel.addVerticalTab(getStringMessages().trackedRaces(), "RacesPanel"); racesTabPanel.ensureDebugId("RacesPanel"); - final TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(sailingService, this, + final TrackedRacesManagementPanel trackedRacesManagementPanel = new TrackedRacesManagementPanel(getSailingService(), this, this, getStringMessages()); trackedRacesManagementPanel.ensureDebugId("TrackedRacesManagement"); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(trackedRacesManagementPanel) { @@ -168,7 +168,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements }, getStringMessages().trackedRaces(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(trackedRacesManagementPanel); - final CompetitorPanel competitorPanel = new CompetitorPanel(sailingService, getStringMessages(), this); + final CompetitorPanel competitorPanel = new CompetitorPanel(getSailingService(), getStringMessages(), this); competitorPanel.ensureDebugId("CompetitorPanel"); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(competitorPanel) { @Override @@ -177,7 +177,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } }, getStringMessages().competitors(), Permission.MANAGE_ALL_COMPETITORS); - final BoatPanel boatPanel = new BoatPanel(sailingService, getStringMessages(), this); + final BoatPanel boatPanel = new BoatPanel(getSailingService(), getStringMessages(), this); boatPanel.ensureDebugId("BoatPanel"); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(boatPanel) { @Override @@ -186,18 +186,18 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } }, getStringMessages().boats(), Permission.MANAGE_ALL_BOATS); - RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(sailingService, this, this, getStringMessages()); + RaceCourseManagementPanel raceCourseManagementPanel = new RaceCourseManagementPanel(getSailingService(), this, this, getStringMessages()); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(raceCourseManagementPanel), getStringMessages().courseLayout(), Permission.MANAGE_COURSE_LAYOUT); regattasDisplayers.add(raceCourseManagementPanel); final AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); - WindPanel windPanel = new WindPanel(sailingService, asyncActionsExecutor, this, this, getStringMessages()); + WindPanel windPanel = new WindPanel(getSailingService(), asyncActionsExecutor, this, this, getStringMessages()); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(windPanel), getStringMessages().wind(), Permission.MANAGE_WIND); regattasDisplayers.add(windPanel); - final MediaPanel mediaPanel = new MediaPanel(regattasDisplayers, sailingService, this, mediaService, this, getStringMessages()); + final MediaPanel mediaPanel = new MediaPanel(regattasDisplayers, getSailingService(), this, mediaService, this, getStringMessages()); panel.addToTabPanel(racesTabPanel, new DefaultRefreshableAdminConsolePanel(mediaPanel) { @Override public void refreshAfterBecomingVisible() { @@ -208,7 +208,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* RACE COMMITTEE APP */ final HorizontalTabLayoutPanel raceCommitteeTabPanel = panel.addVerticalTab(getStringMessages().raceCommitteeApp(), "RaceCommiteeAppPanel"); - final DeviceConfigurationUserPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(sailingService, + final DeviceConfigurationUserPanel deviceConfigurationUserPanel = new DeviceConfigurationUserPanel(getSailingService(), getUserService(), getStringMessages(), this); panel.addToTabPanel(raceCommitteeTabPanel, new DefaultRefreshableAdminConsolePanel(deviceConfigurationUserPanel), getStringMessages().deviceConfiguration(), Permission.MANAGE_DEVICE_CONFIGURATION); @@ -216,7 +216,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements /* CONNECTORS */ final HorizontalTabLayoutPanel connectorsTabPanel = panel.addVerticalTab(getStringMessages().connectors(), "TrackingProviderPanel"); - TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(sailingService, + TracTracEventManagementPanel tractracEventManagementPanel = new TracTracEventManagementPanel(getSailingService(), this, this, getStringMessages()); tractracEventManagementPanel.ensureDebugId("TracTracEventManagement"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(tractracEventManagementPanel), @@ -224,19 +224,19 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements regattasDisplayers.add(tractracEventManagementPanel); SwissTimingReplayConnectorPanel swissTimingReplayConnectorPanel = new SwissTimingReplayConnectorPanel( - sailingService, this, this, getStringMessages()); + getSailingService(), this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swissTimingReplayConnectorPanel), getStringMessages().swissTimingArchiveConnector(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swissTimingReplayConnectorPanel); SwissTimingEventManagementPanel swisstimingEventManagementPanel = new SwissTimingEventManagementPanel( - sailingService, this, this, getStringMessages()); + getSailingService(), this, this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(swisstimingEventManagementPanel), getStringMessages().swissTimingEvents(), Permission.MANAGE_TRACKED_RACES); regattasDisplayers.add(swisstimingEventManagementPanel); final SmartphoneTrackingEventManagementPanel raceLogTrackingEventManagementPanel = new SmartphoneTrackingEventManagementPanel( - sailingService, this, this, this, getStringMessages()); + getSailingService(), this, this, this, getStringMessages()); raceLogTrackingEventManagementPanel.ensureDebugId("SmartphoneTrackingPanel"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(raceLogTrackingEventManagementPanel) { @Override @@ -247,12 +247,12 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements regattasDisplayers.add(raceLogTrackingEventManagementPanel); leaderboardsDisplayers.add(raceLogTrackingEventManagementPanel); - IgtimiAccountsPanel igtimiAccountsPanel = new IgtimiAccountsPanel(sailingService, this, getStringMessages()); + IgtimiAccountsPanel igtimiAccountsPanel = new IgtimiAccountsPanel(getSailingService(), this, getStringMessages()); igtimiAccountsPanel.ensureDebugId("IgtimiAccounts"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(igtimiAccountsPanel), getStringMessages().igtimiAccounts(), Permission.MANAGE_IGTIMI_ACCOUNTS); - ExpeditionDeviceConfigurationsPanel expeditionDeviceConfigurationsPanel = new ExpeditionDeviceConfigurationsPanel(sailingService, this, getStringMessages()); + ExpeditionDeviceConfigurationsPanel expeditionDeviceConfigurationsPanel = new ExpeditionDeviceConfigurationsPanel(getSailingService(), this, getStringMessages()); expeditionDeviceConfigurationsPanel.ensureDebugId("ExpeditionDeviceConfigurations"); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(expeditionDeviceConfigurationsPanel) { @Override @@ -261,11 +261,11 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } }, getStringMessages().expeditionDeviceConfigurations(), Permission.MANAGE_EXPEDITION_DEVICE_CONFIGURATIONS); - ResultImportUrlsManagementPanel resultImportUrlsManagementPanel = new ResultImportUrlsManagementPanel(sailingService, this, getStringMessages()); + ResultImportUrlsManagementPanel resultImportUrlsManagementPanel = new ResultImportUrlsManagementPanel(getSailingService(), this, getStringMessages()); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(resultImportUrlsManagementPanel), getStringMessages().resultImportUrls(), Permission.MANAGE_RESULT_IMPORT_URLS); - StructureImportManagementPanel structureImportUrlsManagementPanel = new StructureImportManagementPanel(sailingService, this, getStringMessages(), this, eventManagementPanel); + StructureImportManagementPanel structureImportUrlsManagementPanel = new StructureImportManagementPanel(getSailingService(), this, getStringMessages(), this, eventManagementPanel); panel.addToTabPanel(connectorsTabPanel, new DefaultRefreshableAdminConsolePanel(structureImportUrlsManagementPanel), getStringMessages().manage2Sail() + " " + getStringMessages().regattaStructureImport(), Permission.MANAGE_STRUCTURE_IMPORT_URLS); @@ -273,7 +273,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements final HorizontalTabLayoutPanel advancedTabPanel = panel.addVerticalTab(getStringMessages().advanced(), "AdvancedPanel"); advancedTabPanel.ensureDebugId("AdvancedTab"); - final ReplicationPanel replicationPanel = new ReplicationPanel(sailingService, this, getStringMessages()); + final ReplicationPanel replicationPanel = new ReplicationPanel(getSailingService(), this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(replicationPanel) { @Override public void refreshAfterBecomingVisible() { @@ -281,17 +281,17 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } }, getStringMessages().replication(), Permission.MANAGE_REPLICATION); - final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(getStringMessages(), sailingService, + final MasterDataImportPanel masterDataImportPanel = new MasterDataImportPanel(getStringMessages(), getSailingService(), this, eventManagementPanel, this, this, mediaPanel); masterDataImportPanel.ensureDebugId("MasterDataImport"); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(masterDataImportPanel), getStringMessages().masterDataImportPanel(), Permission.MANAGE_MASTERDATA_IMPORT); - RemoteServerInstancesManagementPanel remoteServerInstancesManagementPanel = new RemoteServerInstancesManagementPanel(sailingService, this, getStringMessages()); + RemoteServerInstancesManagementPanel remoteServerInstancesManagementPanel = new RemoteServerInstancesManagementPanel(getSailingService(), this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(remoteServerInstancesManagementPanel), getStringMessages().remoteServerInstances(), Permission.MANAGE_SAILING_SERVER_INSTANCES); - LocalServerManagementPanel localServerInstancesManagementPanel = new LocalServerManagementPanel(sailingService, this, getStringMessages()); + LocalServerManagementPanel localServerInstancesManagementPanel = new LocalServerManagementPanel(getSailingService(), this, getStringMessages()); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(localServerInstancesManagementPanel), getStringMessages().localServer(), Permission.MANAGE_LOCAL_SERVER_INSTANCE); @@ -324,7 +324,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements } }, getStringMessages().userGroupManagement(), Permission.MANAGE_USERS); - final FileStoragePanel fileStoragePanel = new FileStoragePanel(sailingService, this); + final FileStoragePanel fileStoragePanel = new FileStoragePanel(getSailingService(), this); panel.addToTabPanel(advancedTabPanel, new DefaultRefreshableAdminConsolePanel(fileStoragePanel), getStringMessages().fileStorage(), Permission.MANAGE_FILE_STORAGE); @@ -338,7 +338,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements @Override public void fillLeaderboards() { - sailingService.getLeaderboards(new MarkedAsyncCallback>( + getSailingService().getLeaderboards(new MarkedAsyncCallback>( new AsyncCallback>() { @Override public void onSuccess(List leaderboards) { @@ -365,7 +365,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements @Override public void fillLeaderboardGroups() { - sailingService.getLeaderboardGroups(false /*withGeoLocationData*/, + getSailingService().getLeaderboardGroups(false /*withGeoLocationData*/, new MarkedAsyncCallback>( new AsyncCallback>() { @Override @@ -393,7 +393,7 @@ public class AdminConsoleEntryPoint extends AbstractSailingEntryPoint implements @Override public void fillRegattas() { - sailingService.getRegattas(new MarkedAsyncCallback>( + getSailingService().getRegattas(new MarkedAsyncCallback>( new AsyncCallback>() { @Override public void onSuccess(List result) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DiscardThresholdBoxes.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DiscardThresholdBoxes.java index 2a3d5c20c4b..d1c393ccde5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DiscardThresholdBoxes.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DiscardThresholdBoxes.java @@ -3,8 +3,7 @@ package com.sap.sailing.gwt.ui.adminconsole; import java.util.ArrayList; import java.util.List; -import com.google.gwt.user.client.ui.HasVerticalAlignment; -import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.VerticalPanel; @@ -23,10 +22,11 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog; * */ public class DiscardThresholdBoxes { - private static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 4; + private static final int MAX_NUMBER_OF_DISCARDED_RESULTS = 15; + + private static final int NUMBER_OF_BOXES_PER_LINE = 5; private final LongBox[] discardThresholdBoxes; - private final DataEntryDialog parent; /** * The widget used to represent the UI @@ -38,7 +38,6 @@ public class DiscardThresholdBoxes { } public DiscardThresholdBoxes(DataEntryDialog parent, int[] initialDiscardThresholds, StringMessages stringMessages) { - this.parent = parent; discardThresholdBoxes = new LongBox[MAX_NUMBER_OF_DISCARDED_RESULTS]; for (int i = 0; i < discardThresholdBoxes.length; i++) { if (initialDiscardThresholds != null && i < initialDiscardThresholds.length) { @@ -82,16 +81,18 @@ public class DiscardThresholdBoxes { private Widget createDiscardThresholdBoxesPanel(StringMessages stringMessages) { assert discardThresholdBoxes != null && discardThresholdBoxes.length == MAX_NUMBER_OF_DISCARDED_RESULTS; - VerticalPanel vp = new VerticalPanel(); + final VerticalPanel vp = new VerticalPanel(); vp.add(new Label(stringMessages.discardRacesFromHowManyStartedRacesOn())); - HorizontalPanel hp = new HorizontalPanel(); - vp.add(hp); - hp.setSpacing(3); + final Grid grid = new Grid(0, 2*NUMBER_OF_BOXES_PER_LINE); + grid.setCellSpacing(3); + vp.add(grid); for (int i = 0; i < discardThresholdBoxes.length; i++) { - hp.add(new Label("" + (i + 1) + ".")); - hp.add(discardThresholdBoxes[i]); + if (i%NUMBER_OF_BOXES_PER_LINE == 0) { + grid.resizeRows(i/NUMBER_OF_BOXES_PER_LINE + 1); + } + grid.setWidget(i/NUMBER_OF_BOXES_PER_LINE, 2*(i%NUMBER_OF_BOXES_PER_LINE), new Label("" + (i + 1) + ".")); + grid.setWidget(i/NUMBER_OF_BOXES_PER_LINE, 2*(i%NUMBER_OF_BOXES_PER_LINE)+1, discardThresholdBoxes[i]); } - parent.alignAllPanelWidgetsVertically(hp, HasVerticalAlignment.ALIGN_MIDDLE); return vp; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java index 9c663ec8e86..c2ca47a4b13 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java @@ -40,22 +40,20 @@ public class EventDetailsComposite extends Composite { private final SimpleAnchorListComposite imageURLList; private final SimpleAnchorListComposite videoURLList; private final SimpleStringListComposite leaderboardGroupList; + private final SimpleStringListComposite windfinderSpotCollectionsList; private final CaptionPanel mainPanel; public EventDetailsComposite(final SailingServiceAsync sailingService, final ErrorReporter errorReporter, final StringMessages stringMessages) { super(); this.stringMessages = stringMessages; - event = null; mainPanel = new CaptionPanel(stringMessages.regatta()); VerticalPanel vPanel = new VerticalPanel(); mainPanel.add(vPanel); - int rows = 17; Grid grid = new Grid(rows, 2); vPanel.add(grid); - int currentRow = 0; eventId = createLabelAndValueWidget(grid, currentRow++, stringMessages.id(), "IdLabel"); eventName = createLabelAndValueWidget(grid, currentRow++, stringMessages.eventName(), "NameLabel"); @@ -72,12 +70,11 @@ public class EventDetailsComposite extends Composite { imageURLList = createLabelAndAnchorListWidget(grid, currentRow++, stringMessages.images(), "ImageURLValueList"); videoURLList = createLabelAndAnchorListWidget(grid, currentRow++, stringMessages.videos(), "VideoURLValueList"); leaderboardGroupList = createLabelAndValueListWidget(grid, currentRow++, stringMessages.leaderboardGroups(), "LeaderboardGroupValueList"); - - for(int i=0; i < rows; i++) { + windfinderSpotCollectionsList = createLabelAndValueListWidget(grid, currentRow++, stringMessages.windFinderSpotCollectionsList(), "WindFinderSpotCollectionsList"); + for (int i = 0; i < rows; i++) { grid.getCellFormatter().setVerticalAlignment(i, 0, HasVerticalAlignment.ALIGN_TOP); grid.getCellFormatter().setVerticalAlignment(i, 1, HasVerticalAlignment.ALIGN_TOP); } - initWidget(mainPanel); } @@ -142,7 +139,6 @@ public class EventDetailsComposite extends Composite { .createRegattaOverviewLink(new RegattaOverviewContextDefinition(event.id)); eventOverviewURL.setText(regattaOverviewLink); eventOverviewURL.setHref(UriUtils.fromString(regattaOverviewLink)); - List courseAreaNames = new ArrayList<>(); if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) { for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) { @@ -150,7 +146,6 @@ public class EventDetailsComposite extends Composite { } } courseAreaNamesList.setValues(courseAreaNames); - List imageURLStringsAsList = new ArrayList<>(); for(ImageDTO image: event.getImages()) { imageURLStringsAsList.add(image.getSourceRef()); @@ -166,6 +161,11 @@ public class EventDetailsComposite extends Composite { leaderboardGroupNamesAsList.add(leaderboardGroupDTO.getName()); } leaderboardGroupList.setValues(leaderboardGroupNamesAsList); + List windfinderSpotCollectionsNamesAsList = new ArrayList<>(); + for (String windfinderSpotCollection : event.getWindFinderReviewedSpotsCollectionIds()) { + windfinderSpotCollectionsNamesAsList.add(windfinderSpotCollection); + } + windfinderSpotCollectionsList.setValues(windfinderSpotCollectionsNamesAsList); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index ece46747371..cbf77d1c367 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -22,6 +22,7 @@ import com.google.gwt.user.client.ui.TextArea; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.windfinder.AvailableWindFinderSpotCollections; import com.sap.sailing.gwt.ui.client.DataEntryDialogWithDateTimeBox; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -32,6 +33,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput; +import com.sap.sse.gwt.client.controls.listedit.GenericStringListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite; @@ -123,10 +125,10 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox availableLeaderboardGroups, + public EventDialog(EventParameterValidator validator, SailingServiceAsync sailingService, + StringMessages stringMessages, List availableLeaderboardGroups, Iterable leaderboardGroupsOfEvent, DialogCallback callback) { - super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator, - callback); + super(stringMessages.event(), null, stringMessages.ok(), stringMessages.cancel(), validator, callback); this.stringMessages = stringMessages; this.availableLeaderboardGroupsByName = new HashMap<>(); for (final LeaderboardGroupDTO lgDTO : availableLeaderboardGroups) { @@ -145,7 +147,6 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox emptyList(), new GenericStringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50)); @@ -158,13 +159,17 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox suggestedWindFinderSpotCollections = AvailableWindFinderSpotCollections + .getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder() == null ? Collections.emptyList() + : AvailableWindFinderSpotCollections + .getAllAvailableWindFinderSpotCollectionsInAlphabeticalOrder(); windFinderSpotCollectionIdsComposite = new StringListInlineEditorComposite(Collections. emptyList(), - new GenericStringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ - Collections.emptyList(), stringMessages.enterIdOfWindfinderReviewedSpotCollection(), 80)); + new GenericStringListEditorComposite.ExpandedUi(stringMessages, + IconResources.INSTANCE.removeIcon(), /* suggestValues */ + suggestedWindFinderSpotCollections, stringMessages.enterIdOfWindFinderReviewedSpotCollection(), 35)); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java index 5cfef105737..21174efb5d6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventListComposite.java @@ -774,6 +774,7 @@ public class EventListComposite extends Composite implements EventsRefresher, Le allEvents.clear(); allEvents.addAll(events); filterTextbox.updateAll(allEvents); + eventTable.redraw(); } }); } 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 91e607d1718..f6b08449715 100644 --- 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 @@ -1,6 +1,6 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.ArrayList; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -336,7 +336,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { }); editCompetitorsDialog.show(); } else if (LeaderboardConfigImagesBarCell.ACTION_CONFIGURE_URL.equals(value)) { - sailingService.getAvailableDetailTypesForLeaderboard(leaderboardDTO.name, new AsyncCallback>() { + sailingService.getAvailableDetailTypesForLeaderboard(leaderboardDTO.name, null, new AsyncCallback>() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java index 82ef7f13ac5..ce032b46a75 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceCourseManagementPanel.java @@ -4,7 +4,6 @@ import java.util.Date; 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.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; @@ -14,6 +13,8 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; /** * A panel that has a race selection (inherited from {@link AbstractRaceManagementPanel}) and which adds a table @@ -41,7 +42,7 @@ public class RaceCourseManagementPanel extends AbstractRaceManagementPanel { @Override public void onSuccess(Void result) { - Window.setStatus(stringMessages.successfullyUpdatedCourse()); + Notification.notify(stringMessages.successfullyUpdatedCourse(), NotificationType.INFO); refreshSelectedRaceData(); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java index 30e77991424..4c832da3c12 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaConfigurationDialog.java @@ -23,12 +23,13 @@ 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; +import com.sap.sse.common.Duration; +import com.sap.sse.gwt.client.controls.IntegerBox; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class RegattaConfigurationDialog extends DataEntryDialog { private final AdminConsoleResources resources = GWT.create(AdminConsoleResources.class); - private final StringMessages stringMessages; private final DeviceConfigurationDTO.RegattaConfigurationDTO originalConfiguration; @@ -36,6 +37,7 @@ public class RegattaConfigurationDialog extends DataEntryDialog { private final StringMessages stringMessages; - + private DateAndTimeInput timeBox; public SetStartTimeReceivedDialog(StringMessages stringMessages, DataEntryDialog.DialogCallback callback) { - super(stringMessages.setStartTimeReceived(), stringMessages.setStartTimeReceivedDescription(), stringMessages.ok(), stringMessages.cancel(), new ReceivedStartTimeDialog(stringMessages), callback); + super(stringMessages.setStartTimeReceived(), stringMessages.setStartTimeReceivedDescription(), + stringMessages.ok(), stringMessages.cancel(), valueToValidate -> null, callback); this.stringMessages = stringMessages; } - + @Override protected Widget getAdditionalWidget() { - Grid content = new Grid(1, 2); - - Label timeBoxLabel = new Label(stringMessages.startTime() + ":"); + final FlowPanel panel = new FlowPanel(); + final Label noticeLabel = new Label(stringMessages.setStartTimeReceivedNotice()); + noticeLabel.getElement().getStyle().setFontWeight(FontWeight.BOLD); + noticeLabel.getElement().getStyle().setFontStyle(FontStyle.ITALIC); + panel.add(noticeLabel); + final Grid content = new Grid(1, 3); + final Label timeBoxLabel = new Label(stringMessages.startTime() + ":"); content.setWidget(0, 0, timeBoxLabel); - timeBox = createDateTimeBox(new Date(), Accuracy.SECONDS); + timeBox = createDateTimeBox(null, Accuracy.SECONDS); content.setWidget(0, 1, timeBox); - - return content; + final Button setNowButton = new Button(stringMessages.now()); + setNowButton.addClickHandler(event -> timeBox.setValue(new Date(), true)); + content.setWidget(0, 2, setNowButton); + panel.add(content); + return panel; } @Override protected Date getResult() { return timeBox.getValue(); } - - private static class ReceivedStartTimeDialog implements Validator { - - private StringMessages stringMessages; - - public ReceivedStartTimeDialog(StringMessages stringMessages) { - this.stringMessages = stringMessages; - } - - @Override - public String getErrorMessage(Date valueToValidate) { - return valueToValidate == null ? stringMessages.pleaseEnterAValue() : null; - } - - } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 3664af6be37..cdbfa9b781b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -5,14 +5,12 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; - import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Style.Unit; 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.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; @@ -35,6 +33,8 @@ import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; +import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingEventRecordDTO; @@ -42,9 +42,7 @@ import com.sap.sailing.gwt.ui.shared.SwissTimingRaceRecordDTO; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; -import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; /** @@ -56,15 +54,22 @@ import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; * */ public class SwissTimingEventManagementPanel extends AbstractEventManagementPanel { + private static final AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); + private final LabeledAbstractFilterablePanel filterablePanelEvents; private final ListDataProvider raceList; - private final CellTable raceTable; + private final FlushableCellTable raceTable; private final Map previousConfigurations; private final ListBox previousConfigurationsComboBox; + private final TextBox eventIdBox; private final TextBox jsonUrlBox; private final TextBox hostnameTextbox; private final IntegerBox portIntegerbox; private final List availableSwissTimingRaces = new ArrayList(); + private final String manage2sailBaseAPIUrl = "http://manage2sail.com/api/public/links/event/"; + private final String manage2sailAPIaccessToken = "?accesstoken=bDAv8CwsTM94ujZ"; + private final String manage2sailUrlAppendix = "&mediaType=json&includeRaces=true"; + private final String eventIdPattern = "[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}"; public SwissTimingEventManagementPanel(final SailingServiceAsync sailingService, ErrorReporter errorReporter, @@ -84,7 +89,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane captionPanelConnections.setContentWidget(verticalPanel); captionPanelConnections.setStyleName("bold"); - Grid connectionsGrid = new Grid(5, 2); + Grid connectionsGrid = new Grid(6, 2); verticalPanel.add(connectionsGrid); previousConfigurations = new HashMap(); @@ -103,89 +108,50 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane } }); fillConfigurations(); - + jsonUrlBox = new TextBox(); jsonUrlBox.getElement().getStyle().setWidth(50, Unit.EM); connectionsGrid.setWidget(0, 0, new Label(stringMessages.swissTimingEvents() + ":")); connectionsGrid.setWidget(0, 1, previousConfigurationsComboBox); - connectionsGrid.setWidget(1, 0, new Label("Manage2Sail Event-URL (json):")); - connectionsGrid.setWidget(1, 1, jsonUrlBox); + + eventIdBox = new TextBox(); + eventIdBox.getElement().getStyle().setWidth(30, Unit.EM); + eventIdBox.setTitle(stringMessages.manage2SailEventIdBoxTooltip()); + connectionsGrid.setWidget(1, 0, new Label(stringMessages.manage2SailEventIdBox() + ":")); + connectionsGrid.setWidget(1, 1, eventIdBox); + eventIdBox.addChangeHandler(event -> { + if (eventIdBox.getValue() != "") { + updateUrlFromEventId(eventIdBox.getValue()); + } + }); + + connectionsGrid.setWidget(2, 0, new Label(stringMessages.manage2SailEventURLBox() + ":")); + connectionsGrid.setWidget(2, 1, jsonUrlBox); + jsonUrlBox.addChangeHandler(event -> { + if (jsonUrlBox.getValue() != "") { + updateEventIdFromUrl(jsonUrlBox.getValue()); + } + }); hostnameTextbox = new TextBox(); portIntegerbox = new IntegerBox(); - connectionsGrid.setWidget(2, 0, new Label(stringConstants.hostname() + ":")); - connectionsGrid.setWidget(2, 1, hostnameTextbox); + connectionsGrid.setWidget(3, 0, new Label(stringConstants.hostname() + ":")); + connectionsGrid.setWidget(3, 1, hostnameTextbox); - connectionsGrid.setWidget(3, 0, new Label(stringConstants.port() + ":")); - connectionsGrid.setWidget(3, 1, portIntegerbox); + connectionsGrid.setWidget(4, 0, new Label(stringMessages.manage2SailPort() + ":")); + connectionsGrid.setWidget(4, 1, portIntegerbox); Button btnListRaces = new Button(stringConstants.listRaces()); - connectionsGrid.setWidget(4, 1, btnListRaces); + connectionsGrid.setWidget(5, 1, btnListRaces); btnListRaces.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { fillRaces(sailingService); } }); - - TextColumn raceNameColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.getName(); - } - }; - - TextColumn regattaNameColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.regattaName; - } - }; - - TextColumn seriesNameColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.seriesName; - } - }; - - TextColumn raceIdColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.raceId; - } - }; - TextColumn boatClassColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.boatClass != null ? object.boatClass : ""; - } - }; - - TextColumn genderColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.gender != null ? object.gender : ""; - } - }; - - TextColumn raceStatusColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.raceStatus != null ? object.raceStatus : ""; - } - }; - - TextColumn raceStartTimeColumn = new TextColumn() { - @Override - public String getValue(SwissTimingRaceRecordDTO object) { - return object.raceStartTime==null?"":dateFormatter.render(object.raceStartTime) + " " + timeFormatter.render(object.raceStartTime); - } - }; - HorizontalPanel racesSplitPanel = new HorizontalPanel(); mainPanel.add(racesSplitPanel); @@ -237,7 +203,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane useInternalMarkPassingAlgorithmCheckbox.setWordWrap(false); useInternalMarkPassingAlgorithmCheckbox.setValue(Boolean.FALSE); trackableRacesPanel.add(useInternalMarkPassingAlgorithmCheckbox); - + // text box for filtering the cell table HorizontalPanel filterPanel = new HorizontalPanel(); filterPanel.setSpacing(5); @@ -247,23 +213,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane filterPanel.add(lblFilterEvents); filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE); - raceNameColumn.setSortable(true); - raceStartTimeColumn.setSortable(true); - boatClassColumn.setSortable(true); - raceIdColumn.setSortable(true); - genderColumn.setSortable(true); - raceStatusColumn.setSortable(true); - - AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); - raceTable = new BaseCelltable(/* pageSize */10000, tableRes); - raceTable.addColumn(regattaNameColumn, stringConstants.regatta()); - raceTable.addColumn(seriesNameColumn, stringConstants.series()); - raceTable.addColumn(raceNameColumn, stringConstants.name()); - //raceTable.addColumn(raceIdColumn, stringConstants.id()); - raceTable.addColumn(raceStatusColumn, stringConstants.status()); - raceTable.addColumn(boatClassColumn, stringConstants.boatClass()); - raceTable.addColumn(genderColumn, "Gender"); - raceTable.addColumn(raceStartTimeColumn, stringConstants.startTime()); + raceTable = new FlushableCellTable(/* pageSize */10000, tableRes); raceTable.setWidth("300px"); raceList = new ListDataProvider(); filterablePanelEvents = new LabeledAbstractFilterablePanel(lblFilterEvents, @@ -283,19 +233,99 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane return strings; } }; - raceTable.setSelectionModel(new RefreshableMultiSelectionModel( - new EntityIdentityComparator() { - @Override - public boolean representSameEntity(SwissTimingRaceRecordDTO dto1, SwissTimingRaceRecordDTO dto2) { - return dto1.raceId.equals(dto2.raceId); - } - @Override - public int hashCode(SwissTimingRaceRecordDTO t) { - return t.raceId.hashCode(); - } - }, filterablePanelEvents.getAllListDataProvider()) { - }); + + final EntityIdentityComparator entityIdentityComparator = new EntityIdentityComparator() { + @Override + public boolean representSameEntity(SwissTimingRaceRecordDTO dto1, SwissTimingRaceRecordDTO dto2) { + return dto1.raceId.equals(dto2.raceId); + } + @Override + public int hashCode(SwissTimingRaceRecordDTO t) { + return t.raceId.hashCode(); + } + }; + TextColumn raceNameColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.getName(); + } + }; + + TextColumn regattaNameColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.regattaName; + } + }; + + SelectionCheckboxColumn selectionColumn = new SelectionCheckboxColumn( + tableRes.cellTableStyle().cellTableCheckboxSelected(), + tableRes.cellTableStyle().cellTableCheckboxDeselected(), + tableRes.cellTableStyle().cellTableCheckboxColumnCell(), entityIdentityComparator, raceList, raceTable); + + TextColumn seriesNameColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.seriesName; + } + }; + + TextColumn raceIdColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.raceId; + } + }; + + TextColumn boatClassColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.boatClass != null ? object.boatClass : ""; + } + }; + + TextColumn genderColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.gender != null ? object.gender : ""; + } + }; + + TextColumn raceStatusColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.raceStatus != null ? object.raceStatus : ""; + } + }; + + TextColumn raceStartTimeColumn = new TextColumn() { + @Override + public String getValue(SwissTimingRaceRecordDTO object) { + return object.raceStartTime==null?"":dateFormatter.render(object.raceStartTime) + " " + timeFormatter.render(object.raceStartTime); + } + }; + + raceNameColumn.setSortable(true); + raceStartTimeColumn.setSortable(true); + boatClassColumn.setSortable(true); + raceIdColumn.setSortable(true); + genderColumn.setSortable(true); + raceStatusColumn.setSortable(true); + + + raceTable.addColumn(selectionColumn, selectionColumn.getHeader()); + raceTable.addColumn(regattaNameColumn, stringConstants.regatta()); + raceTable.addColumn(seriesNameColumn, stringConstants.series()); + raceTable.addColumn(raceNameColumn, stringConstants.name()); + //raceTable.addColumn(raceIdColumn, stringConstants.id()); + raceTable.addColumn(raceStatusColumn, stringConstants.status()); + raceTable.addColumn(boatClassColumn, stringConstants.boatClass()); + raceTable.addColumn(genderColumn, stringConstants.gender()); + raceTable.addColumn(raceStartTimeColumn, stringConstants.startTime()); + + raceTable.setSelectionModel(selectionColumn.getSelectionModel(), selectionColumn.getSelectionManager()); + trackableRacesPanel.add(raceTable); raceList.addDataDisplay(raceTable); Handler columnSortHandler = getRaceTableColumnSortHandler(raceList.getList(), regattaNameColumn, seriesNameColumn, @@ -319,6 +349,31 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane }); } + /** + * This function tries to infer a valid JsonUrl for any input given that matches the pattern of an event Id from + * M2S. If there is an event id detected the Json Url gets updated and the event Id textbox is filled with the + * detected event Id. The ID pattern is defined in {@link eventIdPattern}. + */ + private void updateUrlFromEventId(String eventIdTextbox) { + if (eventIdTextbox.matches(".*" + eventIdPattern + ".*")) { + final String inferredEventId = eventIdTextbox.replaceFirst(".*(" + eventIdPattern + ").*", "$1"); + jsonUrlBox.setValue( + manage2sailBaseAPIUrl + inferredEventId + manage2sailAPIaccessToken + manage2sailUrlAppendix); + eventIdBox.setValue(inferredEventId); + } + } + + /** + * Similar to {@link #updateUrlFromEventId} this function tries to extract a M2S event Id by looking at the given + * url in the Json Url Textbox. The value of {@link eventIdBox} is then set to the event ID inferred from the Json Url. + */ + private void updateEventIdFromUrl(String jsonUrlTextBox) { + if (jsonUrlTextBox.matches("http://manage2sail.com/.*" + eventIdPattern + ".*")) { + final String inferredEventId = jsonUrlTextBox.replaceFirst(".*(" + eventIdPattern + ").*", "$1"); + eventIdBox.setValue(inferredEventId); + } + } + private ListHandler getRaceTableColumnSortHandler(List raceRecords, Column regattaNameColumn, Column seriesNameColumn, Column nameColumn, Column trackingStartColumn, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java index dc42407db41..1432949153e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java @@ -47,11 +47,9 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel { } @Override public void onSuccess(RaceDTO result) { - if (result != null) { - selectedRaceDTO = result; - refreshSelectedRaceData(); - TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas(); - } + selectedRaceDTO = result; + refreshSelectedRaceData(); + TrackedRacesManagementPanel.this.regattaRefresher.fillRegattas(); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractSailingEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractSailingEntryPoint.java index 119b2dcaa60..e9103ef00f4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractSailingEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/AbstractSailingEntryPoint.java @@ -1,17 +1,32 @@ package com.sap.sailing.gwt.ui.client; -import com.google.gwt.core.client.GWT; -import com.google.gwt.user.client.rpc.ServiceDefTarget; -import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; +import com.sap.sse.gwt.client.ServiceRoutingProvider; import com.sap.sse.security.ui.client.AbstractSecureEntryPoint; public abstract class AbstractSailingEntryPoint extends AbstractSecureEntryPoint { - protected final SailingServiceAsync sailingService = GWT.create(SailingService.class); + private SailingServiceAsync sailingService; @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); - EntryPointHelper.registerASyncService((ServiceDefTarget) sailingService, RemoteServiceMappingConstants.sailingServiceRemotePath); + } + + /** + * Lazily initialize sailing service. Concrete entry point subclasses may influence sailing service creation by + * implementing a given routing information providing interface {@link ProvidesLeaderboardRouting}. The routing + * information provided via {@link ProvidesLeaderboardRouting#getLeaderboardName()} needs to be available when this + * method is called for the first time. Repetitive calls will not cause a new leaderboard name to take effect. + */ + protected SailingServiceAsync getSailingService() { + if (sailingService == null) { + if (this instanceof ServiceRoutingProvider) { + sailingService = SailingServiceHelper.createSailingServiceInstance((ServiceRoutingProvider)this); + } else { + sailingService = SailingServiceHelper.createSailingServiceInstance(); + } + } + return sailingService; } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java index c2c0300d06a..f84ab2b1116 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaService.java @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.ui.client; +import java.io.UnsupportedEncodingException; + import com.google.gwt.user.client.rpc.RemoteService; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.VideoMetadataDTO; @@ -35,4 +37,10 @@ public interface MediaService extends RemoteService { * Obtains a MediaTrack for the given literal url, if one exists, {@code null} otherwise */ MediaTrack getMediaTrackByUrl(String url); + + /** + * Obtains metadata from the youtube api + * @throws UnsupportedEncodingException + */ + VideoMetadataDTO checkYoutubeMetadata(String url) throws UnsupportedEncodingException; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java index 2139416441e..4e6b6d7a6cb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/MediaServiceAsync.java @@ -35,4 +35,6 @@ public interface MediaServiceAsync { void getMediaTrackByUrl(String url, AsyncCallback asyncCallback); + void checkYoutubeMetadata(String url, AsyncCallback asyncCallback); + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java index 770d1fb5141..40418b7a3e9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingClientFactory.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sse.gwt.client.mvp.ClientFactory; import com.sap.sse.gwt.client.mvp.ErrorView; import com.sap.sse.security.ui.client.WithSecurity; @@ -11,9 +12,12 @@ import com.sap.sse.security.ui.client.WithSecurity; * */ public interface SailingClientFactory extends ClientFactory, WithSecurity { + SailingServiceAsync getSailingService(); + SailingServiceAsync getSailingService(ProvidesLeaderboardRouting routingProvider); + MediaServiceAsync getMediaService(); - + ErrorView createErrorView(String errorMessage, Throwable errorReason); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 8886e776ac6..b7b9351d6eb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -54,7 +54,7 @@ import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition; import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; @@ -540,7 +540,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS RaceCourseDTO getLastCourseDefinitionInRaceLog(String leaderboardName, String raceColumnName, String fleetName) throws NotFoundException; /** - * Adds a fix to the {@link GPSFixStore}, and creates a mapping with a virtual device for exactly the current + * Adds a fix to the {@link SensorFixStore}, and creates a mapping with a virtual device for exactly the current * timepoint. * * @param timePoint @@ -757,7 +757,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS List getRaceDisplayNamesFromLeaderboard(String leaderboardName, List raceColumnNames) throws NotFoundException; - Iterable getAvailableDetailTypesForLeaderboard(String leaderboardName); + Iterable getAvailableDetailTypesForLeaderboard(String leaderboardName, RegattaAndRaceIdentifier raceOrNull); SpotDTO getWindFinderSpot(String spotId) throws Exception; @@ -779,5 +779,8 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS */ SliceRacePreperationDTO prepareForSlicingOfRace(RegattaAndRaceIdentifier raceIdentifier); - Boolean checkIfRaceIsTracked(RegattaAndRaceIdentifier race); + /** + * Checks if the given race is currently in state tracking or loading. + */ + Boolean checkIfRaceIsTracking(RegattaAndRaceIdentifier race); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 359e7c21aaf..85e441f2d30 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -966,7 +966,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void getRaceDisplayNamesFromLeaderboard(final String leaderboardName, List raceColumnNames, AsyncCallback> callback); - void getAvailableDetailTypesForLeaderboard(String leaderboardName, + void getAvailableDetailTypesForLeaderboard(String leaderboardName, RegattaAndRaceIdentifier raceOrNull, AsyncCallback> asyncCallback); void canSliceRace(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); @@ -986,5 +986,8 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan */ void getWindFinderSpot(String spotId, AsyncCallback callback); - void checkIfRaceIsTracked(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback asyncCallback); + /** + * @see SailingService#checkIfRaceIsTracking(RegattaAndRaceIdentifier) + */ + void checkIfRaceIsTracking(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback asyncCallback); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java new file mode 100644 index 00000000000..261db38393a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceHelper.java @@ -0,0 +1,60 @@ +package com.sap.sailing.gwt.ui.client; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.sap.sse.gwt.client.ServiceRoutingProvider; + +/** + * Set of convenience factory methods that create a {@link SailingServiceAsync} instance. + */ +public abstract class SailingServiceHelper { + private final static String moduleBaseURL = GWT.getModuleBaseURL(); + + private SailingServiceHelper() { + } + + /** + * Creates a new {@link SailingServiceAsync} instance, should be used when code resides in the same bundle as the sailing service code. + */ + public static SailingServiceAsync createSailingServiceInstance() { + return createSailingServiceInstance(true, null); + } + /** + * Creates a new {@link SailingServiceAsync} instance. + * + * @param sameBundle {@code false} if using {@link SailingService} from a different OSGi bundle (e.g. dashboards), {@code true} otherwise + */ + public static SailingServiceAsync createSailingServiceInstance(boolean sameBundle) { + return createSailingServiceInstance(sameBundle, null); + } + + /** + * Creates a new {@link SailingServiceAsync} instance that uses a routing provider, for code in same bundle. + */ + public static SailingServiceAsync createSailingServiceInstance(ServiceRoutingProvider routingProvider) { + return createSailingServiceInstance(true, routingProvider); + } + + /** + * Crates a new {@link SailingServiceAsync} instance. + * + * @param sameBundle {@code false} if using {@link SailingService} from a different OSGi bundle (e.g. dashboards), {@code true} otherwise + */ + public static SailingServiceAsync createSailingServiceInstance(boolean sameBundle, ServiceRoutingProvider routingProvider) { + SailingServiceAsync service = GWT.create(SailingService.class); + ServiceDefTarget serviceToRegister = (ServiceDefTarget) service; + StringBuilder baseURL = new StringBuilder(); + baseURL.append(moduleBaseURL.substring(0, moduleBaseURL.lastIndexOf('/', moduleBaseURL.length() - 2) + 1)); + if (!sameBundle) { + baseURL.append(RemoteServiceMappingConstants.WEB_CONTEXT_PATH).append("/"); + } + + baseURL.append(RemoteServiceMappingConstants.sailingServiceRemotePath); + + if (routingProvider != null) { + baseURL.append(routingProvider.routingSuffixPath()); + } + serviceToRegister.setServiceEntryPoint(baseURL.toString()); + return service; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index fa5826d8237..99ec6ca7a61 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -153,7 +153,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String legDetailsToShow(); String columnMoveUp(); String columnMoveDown(); - String port(); String raceStartTimeColumn(); String showOnlySelectedCompetitors(); String showSelectedCompetitorsInfo(); @@ -435,6 +434,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String insertWaypointAfterSelected(); String addGate(); String scoringSchemeLowPointSystem(); + String scoringSchemeLowPointSystemFirstTwoWins(); + String scoringSchemeLowPointSystemFirstTwoWinsDescription(); String scoringSchemeHighPointSystem(); String scoringSchemeLowPointWinnerGetsZero(); String scoringSchemeWinnerGetsFive(); @@ -1182,6 +1183,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String showUncorrectedTotalPoints(); String setStartTimeReceived(); String setStartTimeReceivedDescription(); + String setStartTimeReceivedNotice(); String lastScoreCorrectionsTime(); String lastScoreCorrectionsComment(); String setTimeToNow(); @@ -1296,13 +1298,11 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, @Override String removeUploadedFile(); String showCompetitorBoatColumn(); - String showCompetitorSailIdColumn(); - String showCompetitorSailIdColumnTooltip(String alwaysShowCompetitorNationalityColumnTitle); String showCompetitorShortNameColumn(); - String showCompetitorShortNameColumnTooltip(String alwaysShowCompetitorNationalityColumnTitle); + String showCompetitorShortNameColumnTooltip(String showCompetitorNationalityColumnTitle); String showCompetitorFullNameColumn(); - String alwaysShowCompetitorNationalityColumn(); - String alwaysShowCompetitorNationalityColumnTooltip(); + String showCompetitorNationalityColumn(); + String showCompetitorNationalityColumnTooltip(); String loadingDimensionValues(); String inviteBuoyTenders(); String orMultipleEmails(); @@ -1973,7 +1973,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String windFinderWindSourceTypeTooltip(); String windFinder(); String enterTagsForTheVideo(); - String enterIdOfWindfinderReviewedSpotCollection(); + String enterIdOfWindFinderReviewedSpotCollection(); String enterTagsForTheImage(); String unableToResolveWindFinderSpotId(String id, String message); String windFinderWeatherData(); @@ -2069,6 +2069,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String multiVideoDoNoAdd(); String multiVideoOffsetInput(); String multiVideoDescription(); + String maneuverAngle(); + String jibeAngle(); + String tackAngle(); String multiUrlChangeMediaTrack(); String multiUrlChangeReplace(); String multiUrlChangeFind(); @@ -2077,4 +2080,18 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String multiUrlChangeNewURL(); String multiUrlNoPrefixWarning(); String multiUrlChangeExplain(); + String resetZoom(); + String lastEvent(String locationOrVenue); + String teaserOverallLinkToolTip(); + String middleManeuverAngle(); + String extrapolatedManeuverStartPosition(); + String projectedExtrapolatedManeuverStartPosition(); + String projectedManeuverEndPosition(); + String bearingAtManeuverEndPosition(); + String gender(); + String manage2SailEventIdBox(); + String manage2SailEventURLBox(); + String manage2SailEventIdBoxTooltip(); + String manage2SailPort(); + String windFinderSpotCollectionsList(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index e6fafb8708a..d1abb69040f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -153,7 +153,6 @@ overallDetailsToShow=Overall Details legDetailsToShow=Leg Details columnMoveUp=Up columnMoveDown=Down -port=Port raceStartTimeColumn=Race Started showOnlySelectedCompetitors=Show only selected competitors showSelectedCompetitorsInfo=Show info box for selected competitors @@ -485,6 +484,8 @@ scoringSchemeHighPointFirstGetsTwelveOrEight=High Point, winner gets 12 points ( scoringSchemeHighPointFirstGetsTwelveOrEightDescription=Score in points. Winner scores 12 points, 2nd - 11 points, .... A tie is broken in favour of the competitor with the highest score, if tie remains then tie is broken by the rank in the last race scored. scoringSchemeHighPointFirstGetsTwelveOrEight2017=High Point ESS 2017, winner gets 12 points (or 8) scoringSchemeHighPointFirstGetsTwelveOrEight2017Description=Score in points. Winner scores 12 points, 2nd - 11 points, .... A tie is broken in favour of the competitor with the highest score, if tie remains then tie is broken by the rank in the last race scored. +scoringSchemeLowPointSystemFirstTwoWins=Low Point System, First with two wins in medals is winner +scoringSchemeLowPointSystemFirstTwoWinsDescription=Low Point System. The first in the medal series to win two races wins the medal series. Being the best in the pre medal series counts as one win for this. scoringSystem=Scoring system createFlexibleLeaderboard=Create flexible leaderboard createRegattaLeaderboard=Create regatta leaderboard @@ -1168,7 +1169,8 @@ racesScoredTooltip=Number of races the competitor has completed or gotten a scor averageNumberOfOperationsPerMessage=Average number of operations per message showUncorrectedTotalPoints=Show uncorrected total points setStartTimeReceived=Set start time received -setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace that isn''t persistent. This means that the new value will be forgotten after the server has been restarted. +setStartTimeReceivedDescription=Sets the startTimeReceived of the selected TrackedRace to the provided value. Leave the value empty to remove the currently set startTimeReceived. +setStartTimeReceivedNotice=This change is not persistent, i.e. the new value will be forgotten when the server has been restarted. lastScoreCorrectionsTime=Last score correction time lastScoreCorrectionsComment=Last score correction comment setTimeToNow=Set time to ''now'' @@ -1281,13 +1283,11 @@ uploadSuccessful=Upload successful fileUploadResult=File upload: {0} {1}.\nIn case of error please check that your File Storage service is properly configured. removeUploadedFile=Remove uploaded file showCompetitorBoatColumn=Boat of the competitor -showCompetitorSailIdColumn=Sail Id -showCompetitorSailIdColumnTooltip=Shows the Sail Id plus the nationality flag or competitor image. If available, a competitor image is prioritized over a nationality flag. If "{0}" is set, both icons will be displayed. showCompetitorShortNameColumn=Competitor Short Name showCompetitorShortNameColumnTooltip=Shows the short name plus the nationality flag or competitor image. If available, a competitor image is prioritized over a nationality flag. If "{0}" is set, both icons will be displayed. showCompetitorFullNameColumn=Competitor Full Name -alwaysShowCompetitorNationalityColumn=Always show competitor nationality -alwaysShowCompetitorNationalityColumnTooltip=Show both, nationality flags as well as competitor images if available +showCompetitorNationalityColumn=Additionally show competitor nationality +showCompetitorNationalityColumnTooltip=Show nationality flags in addition to competitor images if available loadingDimensionValues=Loading dimension values inviteBuoyTenders=Invite Buoy Tenders orMultipleEmails=or multiple E-Mails separated by comma @@ -1871,7 +1871,7 @@ anniversaryMajorCountdownTeaser[one]=Countdown! Only {0,number,#,###} race left anniversaryMajorCountdownDescription=We are celebrating our {0,number,#,###}th race on www.sapsailing.com! Which race will break the mark? The organizer of this anniversary race will receive a total sum of 10,000 Euro for charity purposes. The winner will be announced on this website. Stay tuned and count with us. anniversaryRepdigitCountdownTeaser=Countdown! Only {0,number,#,###} races left until the {1,number,#,###}th race. anniversaryRepdigitCountdownTeaser[one]=Countdown! Only {0,number,#,###} race left until the {1,number,#,###}th race. -anniversaryRepdigitCountdownDescription=We are celebrating our lucky number races! Who will make the {0,number, #,###}th race on www.sapsailing.com? The participants of this race will get a free top-class summer festival from SAP. The winners will be announced on this website. Stay tuned and count with us. +anniversaryRepdigitCountdownDescription=We are celebrating our lucky number races! Who will make the {0,number, #,###}th race on www.sapsailing.com? The organizers will get a free barbecue with drinks. The winners will be announced on this website. Stay tuned and count with us. anniversaryAnnouncementTeaser=Mission accomplished! {0,number,#,###} races with SAP Sailing Analytics! anniversaryAnnouncementDescription= 3,2,1... We congratulate the participants of the race {0}. You did it! We thank you for your trust in SAP Sailing Analytics and hopefully continue sailing some more 10,000 races with you! anniversaryRaceLinkText=Show anniversary race @@ -1979,7 +1979,8 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Measured wind from one or more spots of www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Enter tags for the video -enterIdOfWindfinderReviewedSpotCollection=Enter ID of a reviewed WindFinder spot collection, e.g., "schilksee" +enterIdOfWindFinderReviewedSpotCollection=Enter ID of a reviewed WindFinder spot collection, e.g., "schilksee" +windFinderSpotCollectionsList=WindFinder spot collections enterTagsForTheImage=Enter tags for the image unableToResolveWindFinderSpotId=Unable to resolve WindFinder spot with ID {0}: {1} windFinderWeatherData=Weather Data @@ -2064,6 +2065,9 @@ multiVideoIdle=Work queue is idle multiVideoDoNoAdd=Do not add multiVideoOffsetInput=Global video offset in milliseconds: multiVideoDescription=The webserver is required to provide index listing (subfolders supported if indexed) to allow file discovery. After an initial analysis of the metadata contained in the mp4 files, the videos that should be added, need to be selected via the checkbox column on the left. The "Add Audio/Video" button will create media tracks for all selected files, and add those files to all races selected in the right column. +jibeAngle=Jibe angle +tackAngle=Tacking angle +maneuverAngle=Maneuver angle multiUrlChangeMediaTrack=Adjust multipe media track urls multiUrlChangeReplace=Replace with multiUrlChangeFind=Find @@ -2071,4 +2075,17 @@ multiUrlChangeCannotSave=An error occured while saving multiUrlChangeSave=Save url changes multiUrlChangeNewURL=new url multiUrlNoPrefixWarning=No common prefix was found, this usually means that not all selected videos are hosted at the same location currently. Proceed at own risk! -multiUrlChangeExplain=This dialog will bulk replace common parts at the start of mediatrack urls. Make sure all urls start with the same prefix! Also please take a look at the new url column, and test the resulting urls before pressing save! \ No newline at end of file +multiUrlChangeExplain=This dialog will bulk replace common parts at the start of mediatrack urls. Make sure all urls start with the same prefix! Also please take a look at the new url column, and test the resulting urls before pressing save! +resetZoom=Reset zoom +lastEvent=Last Event: {0} +teaserOverallLinkToolTip=To see the overall series, click the yellow corner +middleManeuverAngle=Middle maneuver angle +extrapolatedManeuverStartPosition=Extrapolated Maneuver Start Position +projectedExtrapolatedManeuverStartPosition=Projection of the extrapolated maneuver start position on middle maneuver angle line +projectedManeuverEndPosition=Projection of the maneuver end position on middle maneuver angle line +bearingAtManeuverEndPosition=Bearing at maneuver end position +gender=Gender +manage2SailEventIdBox=Manage2Sail Event ID +manage2SailEventURLBox=Manage2Sail EventURL (json) +manage2SailEventIdBoxTooltip=Event ID or a URL that contains the event ID from Manage2Sail +manage2SailPort=Port \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index e053aab9815..b22bfbda432 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -155,7 +155,6 @@ overallDetailsToShow=Regatta-Details legDetailsToShow=Schenkeldetails columnMoveUp=Hoch columnMoveDown=Runter -port=Backbord raceStartTimeColumn=Rennstart showOnlySelectedCompetitors=Nur ausgewählte Teilnehmer anzeigen showSelectedCompetitorsInfo=Infobox für ausgewählte Teilnehmer anzeigen @@ -475,6 +474,8 @@ scoringSchemeLowPointForLeagueOverallLeaderboardDescription=Bei Punktegleichstan scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly=Low Point System, bei Punktegleichstand zählt letzten Serie scoringSchemeLowPointTieBreakBasedOnLastSeriesOnlyDescription=Bei Punktegleichstand entscheidet die Punktesumme aller (auch der gestrichenen) Wettfahrten der letzten Serie scoringSchemeLowPointWithAutomaticRdg=Low Point System, RDG erhält Durchschnitt aller nicht-RDG-Wertungen als Vorschlag +scoringSchemeLowPointSystemFirstTwoWins=Low Point System, Erster mit zwei Siegen in Medaillen-Rennen gewinnt +scoringSchemeLowPointSystemFirstTwoWinsDescription=Low Point System. Der Erste, der zwei Rennen in den Medaillen-Rennen gewinnt, zählt als Sieger der Medaillen-Serie. Der Beste der Vorserien bekommt einen zusätzlichen Sieg angerechnet. scoringSystem=Bepunktungssystem createFlexibleLeaderboard=Flexible Rangliste anlegen createRegattaLeaderboard=Rangliste für Regatta anlegen @@ -1155,8 +1156,8 @@ racesScoredTooltip=Anzahl von Rennen, die der Segler vollendet oder für die er averageNumberOfOperationsPerMessage=Durchschnittliche Anzahl Operationen pro Nachricht showUncorrectedTotalPoints=Unkorrigierte Punkte anzeigen setStartTimeReceived=Setze die erhaltene Startzeit -setStartTimeReceivedDescription=This sets the startTimeReceived of the selected TrackedRace, that isn''t persistent. This means, that the new value will be forgotten after the server has been restarted. -setStartTimeReceivedDescription=Dies setzt die startTimeReceived des selektierten TrackedRaces, welches nicht persistent ist. Das beudeutet, dass der neue Wert vergessen wird, sobald der Server neu gestartet wurde. +setStartTimeReceivedDescription=Setzt die startTimeReceived des selektierten TrackedRaces auf den angegebenen Wert. Den Wert leer lassen, um die aktuell gesetzte startTimeReceived zu entfernen. +setStartTimeReceivedNotice=Diese Änderung ist nicht persistent, d.h. der neue Wert wird beim Neustart des Servers vergessen. lastScoreCorrectionsTime=Letzter Zeitpunkt der Punktzahl-Korrektur lastScoreCorrectionsComment=Letzter Kommentar der Punktzahl-Korrektur setTimeToNow=Setze den Zeitpunkt auf ''Jetzt'' @@ -1265,14 +1266,12 @@ removeResult=Löschergebnis: {0} {1} uploadSuccessful=Hochladen erfolgreich fileUploadResult=Ergebnis des Hochladens: {0} {1}\nIm Fehlerfall bitte die Konfiguration für "Datei Speicherung" prüfen. removeUploadedFile=Hochgeladene Datei löschen -showCompetitorBoatColumn=Boat des Teilnehmers anzeigen -showCompetitorSailIdColumn=Segelnummer anzeigen -showCompetitorSailIdColumnTooltip=Zeigt die Segelnummer sowie eine Nationalitäten-Flagge oder ein Teilnehmerbild an. Ein Teilnehmerbild wird hierbei bevorzugt angzeigt, falls vorhanden. Falls auch "{0}" ausgewählt ist, werden stets beide Bilder angezeigt. +showCompetitorBoatColumn=Boot des Teilnehmers anzeigen showCompetitorShortNameColumn=Kurzen Teilnehmernamen anzeigen showCompetitorShortNameColumnTooltip=Zeigt den kurzen Teilnehmernamen sowie eine Nationalitäten-Flagge oder ein Teilnehmerbild an. Ein Teilnehmerbild wird hierbei bevorzugt angzeigt, falls vorhanden. Falls auch "{0}" ausgewählt ist, werden stets beide Bilder angezeigt. showCompetitorFullNameColumn=Vollständigen Teilnehmernamen anzeigen -alwaysShowCompetitorNationalityColumn=Immer Nationalität des Teilnehmers anzeigen -alwaysShowCompetitorNationalityColumnTooltip=Sowohl Nationalität des Teilnehmers als auch Teilnehmerbild anzeigen, sofern beides verfügbar ist +showCompetitorNationalityColumn=Zusätzlich Nationalität des Teilnehmers anzeigen +showCompetitorNationalityColumnTooltip=Die Nationalität des Teilnehmers wird zusätzlich zum Teilnehmerbild anzeigt, sofern beides verfügbar ist loadingDimensionValues=Lade Dimensionswerte inviteBuoyTenders=Tonnenleger einladen orMultipleEmails=oder mehrere E-Mails (Komma-separiert) @@ -1608,8 +1607,8 @@ favoriteCompetitors=Favorisierte Teilnehmer favoriteBoatClasses=Favorisierte Bootsklassen notificationAboutNewResults=Über neue Ergebnisse informieren notificationAboutUpcomingRaces=Über demnächst stattfindende Rennen informieren -currentHeelInDegree=Seitenneigung -currentHeelInDegreeTooltip=Die aktuelle Seitenneigung in Grad +currentHeelInDegree=Krängung +currentHeelInDegreeTooltip=Die aktuelle Krängung in Grad currentPitchInDegree=Neigung currentPitchDegreeTooltip=Die aktuelle Neigung in Grad currentRideHeightInMeters=Fahrhöhe @@ -1877,7 +1876,7 @@ anniversaryMajorCountdownTeaser[one]=Countdown! Noch {0,number,#,###} Rennen bis anniversaryMajorCountdownDescription=Wir zelebrieren unser {0,number,#,###}. Rennen auf www.sapsailing.com! Welches Rennen schafft es die Marke zu durchbrechen? Der Veranstalter des Jubiläumsrennens erhält insgesamt 10.000 Euro für Wohltätigkeitszwecke. Der Gewinner wird auf der Website bekannt gegeben. Stay tuned. anniversaryRepdigitCountdownTeaser=Countdown! Noch {0,number,#,###} Rennen bis zum {1,number,#,###}. Rennen. anniversaryRepdigitCountdownTeaser[one]=Countdown! Noch {0,number,#,###} Rennen bis zum {1,number,#,###}. Rennen. -anniversaryRepdigitCountdownDescription=Wir zelebrieren unsere Schnapszahlenrennen! Wer schafft es das {0,number,#,###}. Rennen zu machen? Dem Gewinner schenkt SAP ein Sommerfest der Extraklasse! Der Gewinner wird auf der Website bekannt gegeben. Stay tuned. +anniversaryRepdigitCountdownDescription=Wir zelebrieren unsere Schnapszahlenrennen! Wer schafft es, das {0,number,#,###}. Rennen zu machen? Den Veranstaltern schenkt SAP ein kostenloses Barbecue mit Getränken. Der Gewinner wird auf der Website bekannt gegeben. Stay tuned. anniversaryAnnouncementTeaser=Mission erfüllt! {0,number,#,###} Rennen mit SAP Sailing Analytics! anniversaryAnnouncementDescription=3,2,1... Wir gratulieren den Teilnehmern des Rennens {0}. Ihr habt es geschafft die Marke zu durchbrechen. Wir danken euch für euer Vertrauen in SAP Sailing Analytics und hoffen, dass wir noch viele weitere Rennen mit euch machen werden! anniversaryRaceLinkText=Jubiläumsrennen anzeigen @@ -1975,8 +1974,9 @@ errorWhileSlicingARace=Ein Fehler ist beim Schneiden eines Rennens aufgetreten windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Gemessener Wind von einer oder mehreren Stationen von www.windfinder.com windFinder=WindFinder +windFinderSpotCollectionsList=WindFinder Messstellen-Sammlungen enterTagsForTheVideo=Tags zum Video erfassen -enterIdOfWindfinderReviewedSpotCollection=ID einer geprüften WindFinder Messstellen-Sammlung eingeben, z.B. "schilksee" +enterIdOfWindFinderReviewedSpotCollection=ID einer geprüften WindFinder Messstellen-Sammlung eingeben, z.B. "schilksee" enterTagsForTheImage=Tags zum Bild erfassen unableToResolveWindFinderSpotId=WindFinder-Station mit Kennung {0} wurde nicht gefunden: {1} windFinderWeatherData=Wetter​daten @@ -2060,6 +2060,9 @@ multiVideoIdle=Keine Arbeit offen multiVideoDoNoAdd=Nicht hinzufügen multiVideoOffsetInput=Globales Videooffset in Millisekunden: multiVideoDescription=Ein Webserver mit Folderindex wird benötigt (Unterordner werden unterstützt, sofern diese auch indiziert sind), um die Erkennung von Datein zu ermöglichen. Nach einer initialen Analyse der Metadaten von den mp4 Dateien, müssen die gewünschen Videos in der linken Spalte ausgewählt werden. Der "Audio/Video hinzufügen" Knopf erstellt für die ausgewählten Videos Mediatracks, und fügt diese zu allen Rennen hinzu, die in der rechten Spalte ausgewählt wurden. +jibeAngle=Halsenwinkel +tackAngle=Wendewinkel +maneuverAngle=Manöverwinkel multiUrlChangeMediaTrack=Mehrere Mediatrack URLs anpassen multiUrlChangeReplace=Ersetzen mit multiUrlChangeFind=Suchen @@ -2067,4 +2070,17 @@ multiUrlChangeCannotSave=Es ist ein Fehler beim Speichern aufgetreten multiUrlChangeSave=Änderungen der URLs speichern multiUrlChangeNewURL=Neue Url multiUrlNoPrefixWarning=Es wurde kein gemeinsamer Präfix gefunden, dies deutet darauf hin, dass nicht alle ausgewählten Videos vom gleichen Host kommen. Auf eigene Gefahr fortsetzen! -multiUrlChangeExplain=Dieser Dialog ermöglicht es, mehrere URL-Anfänge gleichzeitig auszutauschen. Hierzu müssen diese den gleichen Präfix besitzen. Bitte unbedingt die Spalte mit den neuen URLs beachten und diese testen bevor gespeichert wird! \ No newline at end of file +multiUrlChangeExplain=Dieser Dialog ermöglicht es, mehrere URL-Anfänge gleichzeitig auszutauschen. Hierzu müssen diese den gleichen Präfix besitzen. Bitte unbedingt die Spalte mit den neuen URLs beachten und diese testen bevor gespeichert wird! +resetZoom=Reset zoom +lastEvent=Letztes Event: {0} +teaserOverallLinkToolTip=Um die Serien-Übersicht zu sehen, in die gelbe Ecke klicken +middleManeuverAngle=Manöverwinkel-Halbierende +extrapolatedManeuverStartPosition=Extrapolierte Manöver Anfangsposition +projectedExtrapolatedManeuverStartPosition=Projektion der extrapolierten Manöver Startposition auf die Manöverwinkel-Halbierende +projectedManeuverEndPosition=Projektion der Manöverendposition auf die Manöverwinkel-Halbierende +bearingAtManeuverEndPosition=Anliegender Kurs an Manöverendposition +gender=Geschlecht +manage2SailEventId=Manage2Sail Event ID +manage2SailEventURL=Manage2Sail Event URL (json) +manage2SailEventIdBoxTooltip=Event ID von Manage 2 Sail oder URL die die Event ID beinhaltet +manage2SailPort=Port \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index 4aed4783095..e036c02256c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties @@ -153,7 +153,6 @@ overallDetailsToShow=Detalles generales legDetailsToShow=Detalles de tramo columnMoveUp=Arriba columnMoveDown=Abajo -port=Babor raceStartTimeColumn=Carrera iniciada showOnlySelectedCompetitors=Mostrar solo competidores seleccionados showSelectedCompetitorsInfo=Mostrar cuadro de información para competidores seleccionados @@ -1276,13 +1275,11 @@ uploadSuccessful=Carga correcta fileUploadResult=Carga de archivos: {0} {1}. En caso de error, compruebe que su servicio de alojamiento de archivos está configurado correctamente. removeUploadedFile=Eliminar archivo showCompetitorBoatColumn=Embarcación del competidor -showCompetitorSailIdColumn=ID de vela -showCompetitorSailIdColumnTooltip=Muestra el ID de vela más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorShortNameColumn=Denominación breve de competidor showCompetitorShortNameColumnTooltip=Muestra el nombre breve más la bandera del país o imagen del competidor. Si está disponible, se prioriza la imagen de competidor a la bandera del país. Se mostrarán ambos iconos si se fija "{0}". showCompetitorFullNameColumn=Nombre completo del competidor -alwaysShowCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor -alwaysShowCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles. +showCompetitorNationalityColumn=Siempre mostrar la nacionalidad del competidor +showCompetitorNationalityColumnTooltip=Muestra ambos, las banderas de país así como las imágenes de competidor, si están disponibles. loadingDimensionValues=Cargando valores de dimensión inviteBuoyTenders=Invitar a balizadores orMultipleEmails=o varios correos electrónicos separados por una coma @@ -1770,7 +1767,7 @@ raceRankShort=Posición de carrera showRaceRankColumn=Mostrar posición de carrera estimatedDistance=Distancia estimada estimatedTime=Hora estimada -nothingUpcoming=Actualmente no existen más carreras planificadas +nothingUpcoming=No existen más carreras que comiencen pronto noUpcomingRaceDataAvailable=Sin datos disponibles de próxima carrera reallyRemoveSeries=¿Desea realmente eliminar la serie "{0}"? Nota: todos los logs de las columnas de carrera anexas también se eliminarán de forma permanente. settingsId=ID de opciones @@ -1866,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=Cuenta atrás. Solo falta {0,number,#,###} anniversaryMajorCountdownDescription=Estamos celebrando nuestra {0,number,#,###} carrera en www.sapsailing.com. ¿Qué carrera batirá la marca? El organizador de esta carrera de aniversario recibirá un total de 10 000 euros para fines benéficos. El ganador se anunciará en este sitio web. Estén atentos y cuenten con nosotros. anniversaryRepdigitCountdownTeaser=Cuenta atrás. Solo faltan {0,number,#,###} carreras hasta la {1,number,#,###} carrera. anniversaryRepdigitCountdownTeaser[one]=Cuenta atrás. Solo falta {0,number,#,###} carrera hasta la {1,number,#,###} carrera. -anniversaryRepdigitCountdownDescription=Celebramos nuestra carrera del número afortunado. ¿Quién realizará la {0,number, #,###} carrera en www.sapsailing.com? Los participantes de esta carrera obtendrán un festival de verano gratuito de primera clase por parte de SAP. Los ganadores se anunciarán en este sitio web. Estén atentos y cuenten con nosotros. +anniversaryRepdigitCountdownDescription=Celebramos nuestras carreras del número afortunado. ¿Quién realizará la {0,number, #,###} carrera en www.sapsailing.com? Los organizadores obtendrán una barbacoa gratuita con bebidas. Los ganadores se anunciarán en este sitio web. Estén atentos y cuenten con nosotros. anniversaryAnnouncementTeaser=Misión cumplida. {0,number,#,###} Carreras con SAP Sailing Analytics anniversaryAnnouncementDescription=3,2,1... Felicitamos a los participantes de la carrera {0}. ¡Lo conseguísteis! Os agradecemos vuestra confianza en SAP Sailing Analytics y esperamos continuar navegando más de 10 000 carreras con vosotros. anniversaryRaceLinkText=Mostrar carrera de aniversario @@ -1973,7 +1970,7 @@ windFinderWindSourceTypeName=Windfinder windFinderWindSourceTypeTooltip=Viento medido de uno o más puntos de www.windfinder.com windFinder=Windfinder enterTagsForTheVideo=Indique las etiquetas para el vídeo -enterIdOfWindfinderReviewedSpotCollection=Indique el ID de una colección de puntos de Windfinder revisada, por ejemplo "schilksee" +enterIdOfWindFinderReviewedSpotCollection=Indique el ID de una colección de puntos de Windfinder revisada, por ejemplo "schilksee" enterTagsForTheImage=Indique las etiquetas para la imagen unableToResolveWindFinderSpotId=Imposible resolver el punto Windfinder con ID {0}: {1} windFinderWeatherData=Datos meteorológicos @@ -2046,7 +2043,7 @@ multiVideoURLOfIndex=Inserte el URL de índice del servidor web multiVideoScan=Escanear índice multiVideoLinking=Añadir varios vídeos multiVideoNotAnalyzed=Vídeo aún por analizar -multiVideoAlreadyKnown=Vídeo ya tiene pistas multimedia existentes +multiVideoAlreadyKnown=Vídeo ya tiene rastreo de medios existente multiVideoClientIsUploading=Vídeo analizado mediante proxy de cliente multiVideoFinishedLinking=Vídeo enlazado multiVideoErrorInAnalyzingFile=Error al analizar el fichero @@ -2058,3 +2055,13 @@ multiVideoIdle=Cola de trabajo inactiva multiVideoDoNoAdd=No añadir multiVideoOffsetInput=Offset de vídeo global en milisegundos: multiVideoDescription=El servidor web debe proporcionar una lista de índices (subcarpetas compatibles si están indexadas) para permitir la detección de ficheros. Tras un análisis inicial de los metadatos contenidos en los ficheros mp4, los vídeos que deben añadirse, deben seleccionarse mediante la columna de la casilla de selección a la izquierda. El botón "Añadir audio/vídeo" creará pistas multimedia para todos los ficheros seleccionados y los añadirá a todas las carreras seleccionadas en la columna de la derecha. +multiUrlChangeMediaTrack=Ajustar URLs de rastreo múltiple de medios +multiUrlChangeReplace=Reemplazar por +multiUrlChangeFind=Buscar +multiUrlChangeCannotSave=Se ha producido un error al grabar +multiUrlChangeSave=Grabar modificaciones de URL +multiUrlChangeNewURL=URL nuevo +multiUrlNoPrefixWarning=No existe ningún prefijo común, esto significa que normalmente no todos los vídeos seleccionados se alojan actualmente en la misma ubicación. Proceda bajo su propio riesgo. +multiUrlChangeExplain=Este diálogo reemplazará en masa las partes comunes al inicio de los URLs del rastreo de medios. Asegúrate de que todas las URL empiezan con el mismo prefijo. Además, eche un vistazo a la nueva columna del URL, y pruebe los URLs resultantes antes de pulsar Grabar. +lastEvent=Último evento: {0} +teaserOverallLinkToolTip=Para visualizar las series generales, haga clic en la esquina amarilla diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties index 02dbe8459a3..547e5d5823c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties @@ -153,7 +153,6 @@ overallDetailsToShow=Détails généraux legDetailsToShow=Détails de la portion de parcours columnMoveUp=Dans le lit columnMoveDown=Arrière -port=À bâbord raceStartTimeColumn=Course lancée showOnlySelectedCompetitors=Afficher uniquement concurrents sélectionnés showSelectedCompetitorsInfo=Afficher carré d''infos pour concurrents sélectionnés @@ -1276,13 +1275,11 @@ uploadSuccessful=Chargement réussi fileUploadResult=Chargement du fichier : {0} {1}.\nEn cas d''erreur, vérifiez que votre service de stockage de fichier est correctement configuré. removeUploadedFile=Supprimer fichier chargé showCompetitorBoatColumn=Bateau du concurrent -showCompetitorSailIdColumn=ID de voile -showCompetitorSailIdColumnTooltip=Affiche l''ID de voile ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorShortNameColumn=Nom court du concurrent showCompetitorShortNameColumnTooltip=Affiche le nom court ainsi que le drapeau national ou l''image du concurrent. Si les deux sont disponibles, l''image du concurrent aura la priorité sur le drapeau national. Si le code "{0}" est activé, les deux icônes seront affichées. showCompetitorFullNameColumn=Nom complet du concurrent -alwaysShowCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent -alwaysShowCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent +showCompetitorNationalityColumn=Afficher toujours la nationalité du concurrent +showCompetitorNationalityColumnTooltip=Si disponibles, afficher les deux : le drapeau national ainsi que l''image du concurrent loadingDimensionValues=Chargement des valeurs de dimension inviteBuoyTenders=Inviter navire baliseur orMultipleEmails=ou plusieurs adresses e-mails séparées par des virgules @@ -1770,7 +1767,7 @@ raceRankShort=Place dans la course showRaceRankColumn=Afficher place dans la course estimatedDistance=Distance estimée estimatedTime=Temps estimé -nothingUpcoming=Il n''existe actuellement aucune autre course planifiée. +nothingUpcoming=Il n''existe aucune autre course dont le départ est proche. noUpcomingRaceDataAvailable=Aucune donnée concernant la prochaine course n''est disponible. reallyRemoveSeries=Voulez-vous vraiment supprimer la série "{0}" ? Note : tous les journaux de la course seront également supprimés de manière permanente pour cette course. settingsId=ID d''options @@ -1866,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=Attention ! Plus qu''{0,number,#,###} cour anniversaryMajorCountdownDescription=Nous célébrons notre {0,number,#,###}e course sur www.sapsailing.com ! Quelle sera cette course ? L''organisateur de cette course anniversaire recevra 10 000 euros au profit de l''association caritative de son choix. Le gagnant sera annoncé sur ce site Web. Restez connecté et ne ratez pas le compte à rebours ! anniversaryRepdigitCountdownTeaser=Attention ! Plus que {0,number,#,###} courses avant la {1,number,#,###}e course. anniversaryRepdigitCountdownTeaser[one]=Attention ! Plus qu''{0,number,#,###} course avant la {1,number,#,###}e course. -anniversaryRepdigitCountdownDescription=Nous célébrons notre numéro de course porte-bonheur ! Qui effectuera la {0,number, #,###}e course sur www.sapsailing.com ? Les participants de cette course seront invités à un festival d''été de haut niveau par SAP. Les gagnants seront annoncés sur ce site Web. Restez connecté et ne ratez pas le compte à rebours ! +anniversaryRepdigitCountdownDescription=Nous célébrons notre numéro de course porte-bonheur ! Qui effectuera la {0,number, #,###}e course sur www.sapsailing.com ? Les organisateurs se verront offrir un barbecue et des boissons. Les gagnants seront annoncés sur ce site Web. Restez connecté et ne ratez pas le compte à rebours ! anniversaryAnnouncementTeaser=Mission accomplie ! {0,number,#,###} courses avec SAP Sailing Analytics ! anniversaryAnnouncementDescription=3, 2, 1... Félicitation aux participants de la course {0}. Vous avez gagné ! Nous vous remercions pour la confiance que vous accordez à SAP Sailing Analytics et nous espérons partager encore de nombreuses courses avec vous ! anniversaryRaceLinkText=Afficher la course anniversaire @@ -1973,7 +1970,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Vent mesuré d''un ou plusieurs endroits de www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Entrez des balises pour la vidéo. -enterIdOfWindfinderReviewedSpotCollection=Entrez l''ID d''un ensemble d''endroits WindFinder révisé, par exemple, "Schilksee". +enterIdOfWindFinderReviewedSpotCollection=Entrez l''ID d''un ensemble d''endroits WindFinder révisé, par exemple, "Schilksee". enterTagsForTheImage=Entrez des balises pour l''image. unableToResolveWindFinderSpotId=Impossible de traiter l''endroit WindFinder ayant l''ID {0} : {1} windFinderWeatherData=Données météo @@ -1988,9 +1985,9 @@ expeditionAws=eAWS expeditionTwa=eTWA expeditionTws=eTWS expeditionTwd=eTWD -expeditionTargTwa=eTWA cible -expeditionBoatSpeed=eVitesse du bateau -expeditionTargBoatSpeed=eVitesse du bateau cible +expeditionTargTwa=eTarget TWA +expeditionBoatSpeed=eBoat speed +expeditionTargBoatSpeed=eTarget boat speed expeditionBsSog=eVB Vf expeditionSOG=eVf expeditionCOG=eRf @@ -2058,3 +2055,13 @@ multiVideoIdle=La réserve de travail est inactive. multiVideoDoNoAdd=Ne pas ajouter multiVideoOffsetInput=Décalage vidéo global en millisecondes : multiVideoDescription=Le serveur Web doit fournir une liste des index (les sous-dossiers sont pris en charge s''ils sont répertoriés) pour permettre la détection de fichier. Après une analyse initiale des métadonnées contenues dans les fichiers MP4, les vidéos à ajouter doivent être sélectionnées en cochant les cases correspondantes dans la colonne de gauche. Le bouton "Ajouter audio/vidéo" va créer des pistes médias pour tous les fichiers sélectionnés, et ajouter ces fichiers à toutes les courses sélectionnées dans la colonne de droite. +multiUrlChangeMediaTrack=Ajuster plusieurs URL de piste média +multiUrlChangeReplace=Remplacer par +multiUrlChangeFind=Rechercher +multiUrlChangeCannotSave=Une erreur s''est produite lors de la sauvegarde. +multiUrlChangeSave=Sauvegarder modifications apportées aux URL +multiUrlChangeNewURL=Nouvelle URL +multiUrlNoPrefixWarning=Aucun préfixe commun n''a été trouvé. Cela signifie généralement que les vidéos sélectionnées ne sont actuellement pas toutes hébergées au même endroit. Vous assumez les risques liés à leur utilisation. +multiUrlChangeExplain=Cette boîte de dialogue remplacera en masse les éléments communs au début des URL de piste média. Assurez-vous que toutes les URL commencent par le même préfixe. Veuillez également prendre le temps d''observer la nouvelle colonne d''URL, et de tester les URL résultantes avant de sauvegarder. +lastEvent=Dernier événement : {0} +teaserOverallLinkToolTip=Pour afficher l''ensemble des séries, cliquez sur le coin jaune. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 8fdc97aadb7..6eab3ac7219 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -153,7 +153,6 @@ overallDetailsToShow=全体詳細 legDetailsToShow=レグ詳細 columnMoveUp=上 columnMoveDown=下 -port=ポート raceStartTimeColumn=レーススタート済 showOnlySelectedCompetitors=選択した競技者のみ表示 showSelectedCompetitorsInfo=選択した競技者の情報ボックスを表示 @@ -1276,13 +1275,11 @@ uploadSuccessful=正常にアップロード済 fileUploadResult=ファイルアップロード: {0} {1}\nエラー発生の場合は、ファイルストレージサービスが適切に設定されていることを確認してください。 removeUploadedFile=アップロード済ファイルの削除 showCompetitorBoatColumn=競技者の艇 -showCompetitorSailIdColumn=セール ID -showCompetitorSailIdColumnTooltip=セール ID に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorShortNameColumn=競技者の名前 (短) showCompetitorShortNameColumnTooltip=名前 (短) に加えて国旗または競技者画像を表示します。競技者画像が利用可能な場合はそれを国旗よりも優先させます。"{0}" が設定されている場合、両方のアイコンが表示されます。 showCompetitorFullNameColumn=競技者氏名 -alwaysShowCompetitorNationalityColumn=競技者国籍を常に表示 -alwaysShowCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 +showCompetitorNationalityColumn=競技者国籍を常に表示 +showCompetitorNationalityColumnTooltip=国旗と競技者画像 (利用可能な場合) の両方を表示 loadingDimensionValues=次元値ロード中 inviteBuoyTenders=ブイ入札の招待 orMultipleEmails=または複数の電子メールをカンマで区切り @@ -1429,7 +1426,6 @@ noFinishedRaces=フィニッシュしたレースがまだありません。 racesOverview=レース概要 listFormatLabel=レース一覧 competitionFormatLabel=競技日 -runAQuery=クエリを実行 latestRegattaStandings=レガッタ最新順位表 openFullscreenView=全画面ビューを開く closeFullscreenView=全画面ビューを閉じる @@ -1771,7 +1767,7 @@ raceRankShort=レースランク showRaceRankColumn=レースランクを表示 estimatedDistance=推定距離 estimatedTime=推定時間 -nothingUpcoming=現時点で予定されているレースはありません +nothingUpcoming=まもなく開始されるレースはありません noUpcomingRaceDataAvailable=予定されているレースのデータはありません reallyRemoveSeries=シリーズ ''{0}'' を削除しますか。注記: 付属しているレース列のレースログもすべて永続的に削除されます。 settingsId=設定 ID @@ -1867,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=カウントダウン情報です。{1,numb anniversaryMajorCountdownDescription=SAP では、www.sapsailing.com における {0,number,#,###} 番目のレースを祝うことにしています。どのレースがゴールテープを切るでしょうか。その記念レースの主催者には、義援目的で合計で 10,000 ユーロが贈呈されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 anniversaryRepdigitCountdownTeaser=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 anniversaryRepdigitCountdownTeaser[one]=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 -anniversaryRepdigitCountdownDescription=SAP では、縁起のいい番号のレースを祝うことにしています。www.sapsailing.com における {0,number, #,###} 番目のレースは誰が行うことになるでしょうか。そのレースの参加者には、SAP から最上級のサマーフェスティバルが無料で提供されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 +anniversaryRepdigitCountdownDescription=SAP では、縁起のいい番号のレースを祝うことにしています。www.sapsailing.com における {0,number, #,###} 番目のレースは誰が行うことになるでしょうか。その主催者にはドリンク込みのバーベキューが無料で提供されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 anniversaryAnnouncementTeaser=SAP Sailing Analytics で {0,number,#,###} レースというミッションが達成されました。 anniversaryAnnouncementDescription=3、2、1...。レース {0} の参加者のみなさん、おめでとうございます。当選をお知らせします。SAP Sailing Analytics をご信頼いただきありがとうございます。さらに 10,000 レースをご一緒に帆走できることを願っております。 anniversaryRaceLinkText=記念レース表示 @@ -1974,7 +1970,7 @@ windFinderWindSourceTypeName=Windfinder windFinderWindSourceTypeTooltip=www.windfinder.com の 1 つ以上のスポットから風を測定しました windFinder=Windfinder enterTagsForTheVideo=動画のタグを入力 -enterIdOfWindfinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee") +enterIdOfWindFinderReviewedSpotCollection=レビューする Windfinder スポットコレクションの ID を入力 (例: "schilksee") enterTagsForTheImage=画像のタグを入力 unableToResolveWindFinderSpotId=ID {0} で Windfinder スポットを決定できませんでした: {1} windFinderWeatherData=気象データ @@ -2059,3 +2055,13 @@ multiVideoIdle=ワークキューが無効です multiVideoDoNoAdd=追加しないでください multiVideoOffsetInput=グローバル動画オフセット (msec): multiVideoDescription=Web サーバは、ファイルディスカバリを可能にするため、インデックスリスト (インデックスが作成されている場合はサブフォルダがサポート済) を用意していることが必要です。MP4 ファイルに含まれているメタデータの初期解析後に、追加する動画を左側のチェックボックス列で選択する必要があります。"音声/動画追加" ボタンにより、選択されているすべてのファイルに対してメディアトラックが作成され、右側の列で選択されているすべてのレースに対してそれらのファイルが追加されます。 +multiUrlChangeMediaTrack=複数メディアトラック URL の調整 +multiUrlChangeReplace=置換後の文字列 +multiUrlChangeFind=検索 +multiUrlChangeCannotSave=保存中にエラーが発生 +multiUrlChangeSave=URL 変更の保存 +multiUrlChangeNewURL=新規 URL +multiUrlNoPrefixWarning=共通の接頭辞が見つかりませんでした。これは通常、選択した動画のうち現在同じ場所に置かれていないものがあることを意味します。自身の責任で進めてください。 +multiUrlChangeExplain=このダイアログはメディアトラック URL 先頭の共通部分をまとめて置換します。すべての URL が同一の接頭辞で始まっていることを確認してください。また、新規 URL 列を参照し、保存を選択する前に結果として得られる URL を吟味してください。 +lastEvent=最終イベント: {0} +teaserOverallLinkToolTip=シリーズ全体を参照するには、黄色になっている隅の部分をクリックしてください diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties index 15a01999128..0206d843906 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties @@ -153,7 +153,6 @@ overallDetailsToShow=Detalhes gerais legDetailsToShow=Detalhes da perna columnMoveUp=Para cima columnMoveDown=Para baixo -port=Bombordo raceStartTimeColumn=Corrida iniciada showOnlySelectedCompetitors=Visualizar somente os competidores selecionados showSelectedCompetitorsInfo=Visualizar caixa de informações para competidores selecionados @@ -1276,13 +1275,11 @@ uploadSuccessful=Carregamento efetuado com êxito fileUploadResult=Carregamento de arquivo: {0} {1}.\nEm caso de erro, verifique se seu serviço de armazenamento de arquivos está configurado de modo apropriado. removeUploadedFile=Remover arquivo carregado showCompetitorBoatColumn=Barco do competidor -showCompetitorSailIdColumn=ID da vela -showCompetitorSailIdColumnTooltip=Exibe o ID da vela mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorShortNameColumn=Nome abreviado do competidor showCompetitorShortNameColumnTooltip=Exibe o nome abreviado mais a bandeira de nacionalidade ou a imagem do competidor. Se disponível, é priorizada uma imagem do competidor em relação a uma bandeira da nacionalidade. Se "{0}" estiver definido, ambos os ícones serão exibidos. showCompetitorFullNameColumn=Nome completo do competidor -alwaysShowCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor -alwaysShowCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis +showCompetitorNationalityColumn=Exibir sempre a nacionalidade do competidor +showCompetitorNationalityColumnTooltip=Exibir ambos, bandeiras de nacionalidade e imagens do competidor, se disponíveis loadingDimensionValues=Carregando valores de dimensão inviteBuoyTenders=Convidar navios-balizadores orMultipleEmails=ou vários e-mails separados por vírgula @@ -1770,7 +1767,7 @@ raceRankShort=Posição na corrida showRaceRankColumn=Exibir posição na corrida estimatedDistance=Distância estimada estimatedTime=Tempo estimado -nothingUpcoming=Atualmente não existem mais corridas planejadas +nothingUpcoming=Não existem mais corridas com início em breve noUpcomingRaceDataAvailable=Sem dados da próxima corrida disponíveis reallyRemoveSeries=Você quer realmente remover a série ''{0}''? Nota: todos os registros da corrida para a coluna da corrida anexada também serão removidos definitivamente! settingsId=ID de configurações @@ -1866,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=Contagem regressiva! Só resta {0,number,#, anniversaryMajorCountdownDescription=Estamos celebrando nossa {0,number,#,###}ª corrida em www.sapsailing.com! Que corrida ultrapassará a marca? O organizador desta corrida de comemoração receberá um total de 10.000 Euros para fins beneficentes. O vencedor será anunciado neste site. Fique atento e conte conosco. anniversaryRepdigitCountdownTeaser=Contagem regressiva! Só restam {0,number,#,###} corridas até a {1,number,#,###}ª corrida. anniversaryRepdigitCountdownTeaser[one]=Contagem regressiva! Só resta {0,number,#,###} corrida até a {1,number,#,###}ª corrida. -anniversaryRepdigitCountdownDescription=Estamos celebrando a corrida do nosso número da sorte! Quem fará parte da {0,number, #,###}ª corrida em www.sapsailing.com? Os participantes desta corrida receberão entradas gratuitas no festival de verão de topo da SAP. Os vencedores serão anunciados neste site. Fique atento e conte conosco. +anniversaryRepdigitCountdownDescription=Estamos celebrando a corrida do nosso número da sorte! Quem fará parte da {0,number, #,###}ª corrida em www.sapsailing.com? Os organizadores receberão um churrasco grátis com bebidas. Os vencedores serão anunciados neste site. Fique atento e conte conosco. anniversaryAnnouncementTeaser=Missão cumprida! {0,number,#,###} corridas com o SAP Sailing Analytics! anniversaryAnnouncementDescription=3,2,1... Felicitamos os participantes da corrida {0}. Você conseguiu! Agradecemos sua confiança no SAP Sailing Analytics e esperamos continuar velejando mais 10.000 corridas com você! anniversaryRaceLinkText=Exibir corrida de comemoração @@ -1973,7 +1970,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Vento medido de um ou mais locais de www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Inserir etiquetas para o vídeo -enterIdOfWindfinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee" +enterIdOfWindFinderReviewedSpotCollection=Inserir ID de uma coleção revisada de locais do WindFinder, por exemplo, "schilksee" enterTagsForTheImage=Inserir etiquetas para a imagem unableToResolveWindFinderSpotId=Impossível resolver local do WindFinder com ID {0}: {1} windFinderWeatherData=Dados metereológicos @@ -2058,3 +2055,13 @@ multiVideoIdle=A lista de trabalho está inativa multiVideoDoNoAdd=Não adicionar multiVideoOffsetInput=Deslocamento de vídeo global em milissegundos: multiVideoDescription=O servidor da web é necessário para fornecer a lista de índices (subpastas suportadas se indexadas) de modo a permitir a descoberta de arquivos. Após uma análise inicial dos metadados contidos nos arquivos mp4, os vídeos que deviam ser adicionados precisam ser selecionados mediante a coluna do campo de seleção à esquerda. O botão "Adicionar áudio/vídeo" irá criar faixas de mídia para todos os arquivos selecionados e adicionar esses arquivos a todas as corridas selecionadas na coluna à direita. +multiUrlChangeMediaTrack=Ajustar vários URLs de faixa de mídia +multiUrlChangeReplace=Substituir por +multiUrlChangeFind=Procurar +multiUrlChangeCannotSave=Ocorreu um erro ao gravar +multiUrlChangeSave=Gravar modificações do URL +multiUrlChangeNewURL=novo URL +multiUrlNoPrefixWarning=Não foi encontrado um prefixo comum. Isso normalmente significa que nem todos os vídeos selecionados estão hospedados atualmente no mesmo local. Continue por sua própria conta e risco! +multiUrlChangeExplain=Este diálogo irá substituir em massa as partes comuns no início dos URLs da faixa de mídia. Assegure que todos os URLs começam com o mesmo prefixo! Veja também a nova coluna do URL e teste os URLs resultantes antes de pressionar Gravar! +lastEvent=Último evento: {0} +teaserOverallLinkToolTip=Para ver a série completa, clique no canto amarelo diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 131faa9fcf3..6847ca0ef94 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -85,7 +85,6 @@ sapSailingAnalytics=Sailing Analytics leaderboard=Таблица лидеров leaderboards=Таблицы лидеров leaderboardSettings=Параметры таблицы лидеров -settingsId=Ид. настроек selectAtLeastOneLegDetail=Выберите подробности минимум одного отрезка currentSpeedOverGroundInKnots=SOG currentSpeedOverGroundInKnotsTooltip=Текущая скорость относительно грунта. @@ -154,7 +153,6 @@ overallDetailsToShow=Общие подробности legDetailsToShow=Подробности columnMoveUp=Вверх columnMoveDown=Вниз -port=Порт raceStartTimeColumn=Гонка начата showOnlySelectedCompetitors=Показать только выбранных участников showSelectedCompetitorsInfo=Показать окно сведений для выбранных участников @@ -1277,13 +1275,11 @@ uploadSuccessful=Отправлено успешно fileUploadResult=Отправка файла: {0} {1}.\nВ случае ошибки проверьте правильность конфигурации службы хранилища файлов. removeUploadedFile=Удалить отправленный файл showCompetitorBoatColumn=Лодка участника -showCompetitorSailIdColumn=Ид. паруса -showCompetitorSailIdColumnTooltip=Показывает ид. паруса плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorShortNameColumn=Краткое имя участника showCompetitorShortNameColumnTooltip=Показывает краткое имя плюс государственный флаг или изображение участника. Изображение участника, при наличии, имеет приоритет над государственным флагом. Если установлен параметр "{0}", будут отображены оба значка. showCompetitorFullNameColumn=Полное имя участника -alwaysShowCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника -alwaysShowCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) +showCompetitorNationalityColumn=Всегда показывать государственную принадлежность участника +showCompetitorNationalityColumnTooltip=Показывать и флаги государств, и изображения участников (при наличии) loadingDimensionValues=Загрузка значений измерения inviteBuoyTenders=Пригласить лоцманов orMultipleEmails=или несколько адресов эл. почты через запятую @@ -1771,9 +1767,10 @@ raceRankShort=Ранг гонки showRaceRankColumn=Показать ранг гонки estimatedDistance=Оценка расстояния estimatedTime=Оценка времени -nothingUpcoming=Дальнейших запланированных гонок сейчас нет +nothingUpcoming=Нет других гонок, стартующих скоро noUpcomingRaceDataAvailable=Данные предстоящей гонки недоступны reallyRemoveSeries=Действительно удалить серии ''{0}''? Примечание: все журналы для вложенных столбцов гонки также будут окончательно удалены! +settingsId=Ид. настроек documentSettingsId=Ид. настроек документа settingsForId=Настройки для ид. ''{0}'' userProfileSettingsTabDescription=Пользовательские настройки генерируются диалогами настроек, расположенными во многих местах страницы. Здесь отображено техническое представление всех собранных настроек для ключевых пользователей. Соблюдайте осторожность, так как восстановить удаленные записи невозможно. @@ -1866,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=Обратный отсчет! До {1,numb anniversaryMajorCountdownDescription={0,number,#,###}-ая гонка на сайте www.sapsailing.com! Какая из них станет юбилейной? Организатор юбилейной гонки получит 10 000 евро, которые сможет потратить в благотворительных целях. Победитель будет объявлен на нашем сайте, оставайтесь с нами. anniversaryRepdigitCountdownTeaser=Обратный отсчет! До {1,number,#,###} гонки осталось всего {0,number,#,###} гонки(ок). anniversaryRepdigitCountdownTeaser[one]=Обратный отсчет! До {1,number,#,###} гонки осталась всего {0,number,#,###} гонка. -anniversaryRepdigitCountdownDescription=Ждем гонку под счастливым номером! Чья гонка станет {0,number, #,###}-ой на сайте www.sapsailing.com? Участники этой гонки получат право на участие в бесплатном высококлассном летнем фестивале от SAP Победитель будет объявлен на нашем сайте, оставайтесь с нами. +anniversaryRepdigitCountdownDescription=Ждем гонку под счастливым номером! Чья гонка станет {0,number, #,###}-ой на сайте www.sapsailing.com? Организаторы получат бесплатный барбекю-обед с напитками. Победители будут объявлен на нашем сайте. Оставайтесь с нами. anniversaryAnnouncementTeaser=Миссия выполнена! Проведено {0,number,#,###} гонок с SAP Sailing Analytics! anniversaryAnnouncementDescription=3, 2, 1... Поздравляем участников гонк {0}. Вы победили! Спасибо, что пользуетесь SAP Sailing Analytics. Надеюсь, нас с вами ждет еще множество гонок! anniversaryRaceLinkText=Показать юбилейную гонку @@ -1973,7 +1970,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Измерения ветра из одного или нескольких мест www.windfinder.com windFinder=WindFinder enterTagsForTheVideo=Введите теги для видео -enterIdOfWindfinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee") +enterIdOfWindFinderReviewedSpotCollection=Введите ид. просмотренной коллекции мест WindFinder (например, "schilksee") enterTagsForTheImage=Введите теги для изображения unableToResolveWindFinderSpotId=Не удалось разрешить место WindFinder по ид. {0}: {1} windFinderWeatherData=Данные погоды @@ -2058,3 +2055,13 @@ multiVideoIdle=Рабочий список неактивен multiVideoDoNoAdd=Не добавлять multiVideoOffsetInput=Смещение глобального видео в мс: multiVideoDescription=Веб-сервер необходим для предоставления списка индекса (подпапки поддерживаются, если проиндексированы) для поиска файлов. После первичного анализа метаданных, содержащихся в файлах mp4, видео, которые должны быть добавлены, необходимо выбрать в столбце с флажками слева. При нажатии кнопки "Добавить аудио/видео" будут созданы медиаканалы для всех выбранных файлов, а файлы будут добавлены ко всем гонкам, выбранным в правом столбце. +multiUrlChangeMediaTrack=Адаптировать URL объектов мультимедиа +multiUrlChangeReplace=Заменить на +multiUrlChangeFind=Найти +multiUrlChangeCannotSave=Ошибка при сохранении +multiUrlChangeSave=Сохранить изменения URL +multiUrlChangeNewURL=Новый URL +multiUrlNoPrefixWarning=Общий префикс не найден, это означает, что не все выбранные видео хранятся в одном и том же месте. Можете продолжить на свой риск! +multiUrlChangeExplain=В этом диалоговом окне можно выполнить массовую замену общих частей в начале URL объектов мультимедиа. Убедитесь, что все URL имеют одинаковый префикс! Проверьте также столбец с новым URL и протестируйте новые URL перед сохранением! +lastEvent=Последнее событие: {0} +teaserOverallLinkToolTip=Чтобы увидеть все серии, нажмите желтый треугольник diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 86f80a0cdaf..cb19fde8474 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -153,7 +153,6 @@ overallDetailsToShow=总体详情 legDetailsToShow=航段详情 columnMoveUp=向上 columnMoveDown=向下 -port=端口 raceStartTimeColumn=比赛轮次开始时间 showOnlySelectedCompetitors=仅显示选择的参赛队 showSelectedCompetitorsInfo=显示所选参赛队的信息框 @@ -1276,13 +1275,11 @@ uploadSuccessful=上传成功 fileUploadResult=文件上传: {0} {1}。\n如果出错,请检查文件存储服务配置是否正确。 removeUploadedFile=移除上传的文件 showCompetitorBoatColumn=参赛队的船只 -showCompetitorSailIdColumn=帆号 -showCompetitorSailIdColumnTooltip=显示帆号和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorShortNameColumn=参赛队短名称 showCompetitorShortNameColumnTooltip=显示短名称和国籍旗帜或参赛队图像。如果可用,参赛队图像优先于国籍旗帜。如果设置 "{0}",将显示这两种图标。 showCompetitorFullNameColumn=参赛队全名 -alwaysShowCompetitorNationalityColumn=始终显示参赛队国籍 -alwaysShowCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 +showCompetitorNationalityColumn=始终显示参赛队国籍 +showCompetitorNationalityColumnTooltip=如果可用,显示两者:国籍旗帜和参赛队图像 loadingDimensionValues=正在加载尺寸值 inviteBuoyTenders=邀请浮标供应船 orMultipleEmails=或使用逗号隔开的多个电子邮件 @@ -1770,7 +1767,7 @@ raceRankShort=比赛轮次名次 showRaceRankColumn=显示比赛轮次名次 estimatedDistance=预计距离 estimatedTime=预计时间 -nothingUpcoming=目前还没有进一步的计划比赛轮次 +nothingUpcoming=没有即将开始的其他比赛轮次 noUpcomingRaceDataAvailable=没有可用的即将进行的比赛轮次数据 reallyRemoveSeries=是否确定移除系列赛 "{0}"?注意:随附比赛轮次列的所有比赛轮次日志也将永久移除! settingsId=设置编号 @@ -1866,7 +1863,7 @@ anniversaryMajorCountdownTeaser[one]=倒计时!距离第{1,number,#,###}轮比 anniversaryMajorCountdownDescription=我们正在 www.sapsailing.com 上庆祝第{0,number,#,###}轮比赛!哪轮比赛会打破记录?本周年纪念赛的主办机构将获得共计 10,000 欧元作为慈善用途。获胜者将在本网站上公布。敬请关注,让我们拭目以待。 anniversaryRepdigitCountdownTeaser=倒计时!距离第{1,number,#,###}轮比赛仅剩{0,number,#,###}轮比赛。 anniversaryRepdigitCountdownTeaser[one]=倒计时!距离第{1,number,#,###}轮比赛仅剩{0,number,#,###}轮比赛。 -anniversaryRepdigitCountdownDescription=我们正在庆祝幸运号码赛!谁将在 www.sapsailing.com 上进行的第{0,number, #,###}轮比赛中获胜?这次比赛的参赛者将获得 SAP 提供的免费一流夏日嘉年华活动。获胜者将在本网站上公布。敬请关注,让我们拭目以待。 +anniversaryRepdigitCountdownDescription=我们正在庆祝幸运号码赛!谁将在 www.sapsailing.com 上进行的第{0,number, #,###}轮比赛中获胜?主办机构将获得免费烧烤和饮料。获胜者将在本网站上公布。敬请关注,让我们拭目以待。 anniversaryAnnouncementTeaser=任务圆满完成!使用 SAP Sailing Analytics 完成{0,number,#,###}轮比赛! anniversaryAnnouncementDescription=3,2,1...我们祝贺{0}比赛的参赛者。你们做到了!我们感谢您对 SAP Sailing Analytics 的信任,并希望与你们在以后的 10,000 多场比赛中继续并肩作战! anniversaryRaceLinkText=显示周年纪念赛 @@ -1973,7 +1970,7 @@ windFinderWindSourceTypeName=WindFinder windFinderWindSourceTypeTooltip=Www.windfinder.com 中一个或多个地点的测量风力 windFinder=WindFinder enterTagsForTheVideo=为视频输入标记 -enterIdOfWindfinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee" +enterIdOfWindFinderReviewedSpotCollection=输入经检查的 WindFinder 地点集合的编号,例如 "schilksee" enterTagsForTheImage=为图像输入标记 unableToResolveWindFinderSpotId=无法解析编号为{0}的 WindFinder 地点:{1} windFinderWeatherData=天气数据 @@ -2058,3 +2055,13 @@ multiVideoIdle=工作队列空闲 multiVideoDoNoAdd=请勿添加 multiVideoOffsetInput=全球视频偏移量(毫秒): multiVideoDescription=需要 Web 服务器提供索引清单(如果已编入索引,则支持的子文件夹),以允许发现文件。初始分析 mp4 文件中包含的元数据后,需要通过左侧的复选框列选择应添加的视频。“添加音频/视频”按钮将为所有选择的文件创建媒体轨道,并将这些文件添加到右列中选择的所有比赛轮次中。 +multiUrlChangeMediaTrack=调整多个媒体轨道 URL +multiUrlChangeReplace=替换为 +multiUrlChangeFind=查找 +multiUrlChangeCannotSave=保存时出错 +multiUrlChangeSave=保存 URL 更改 +multiUrlChangeNewURL=新 URL +multiUrlNoPrefixWarning=未找到通用前缀,这通常意味着并非所有选定的视频都被托管在当前的相同位置。如继续操作,后果自负! +multiUrlChangeExplain=此对话框将在媒体轨道 URL 的起始处批量替换通用部分。确保所有 URL 都以相同的前缀开头!也请注意新的 URL 列,然后在保存之前测试生成的 URL! +lastEvent=上次活动:{0} +teaserOverallLinkToolTip=请查看整体系列赛,请点击黄色角落 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java index 62e6ef26328..3bba073d7c7 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java @@ -78,6 +78,7 @@ public class TimePanel extends AbstractCompositeCom private final Button slowDownButton; private final Button speedUpButton; private final Button toggleAdvancedModeButton; + private final Button resetZoomButton; private final FlowPanel controlsPanel; private final SimplePanel timePanelSlider; @@ -309,6 +310,16 @@ public class TimePanel extends AbstractCompositeCom controlsPanel.add(timeControlPanel); controlsPanel.add(timeToStartControlPanel); + resetZoomButton = new Button(stringMessages.resetZoom()); + resetZoomButton.setEnabled(false); + resetZoomButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + timeRangeProvider.resetTimeZoom(); + } + }); + controlsPanel.add(resetZoomButton); + hideControlsPanel(); } @@ -536,10 +547,12 @@ public class TimePanel extends AbstractCompositeCom @Override public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) { + resetZoomButton.setEnabled(true); } @Override public void onTimeZoomReset() { + resetZoomButton.setEnabled(false); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java index 2db2a59776e..ef40c4c0988 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaDialog.java @@ -13,6 +13,8 @@ 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.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.media.client.Audio; import com.google.gwt.media.client.MediaBase; import com.google.gwt.typedarrays.shared.Int8Array; @@ -40,6 +42,8 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.media.MimeType; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; +import com.sap.sse.gwt.client.controls.datetime.DateAndTimeInput; +import com.sap.sse.gwt.client.controls.datetime.DateTimeInput.Accuracy; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class NewMediaDialog extends DataEntryDialog { @@ -63,7 +67,7 @@ public class NewMediaDialog extends DataEntryDialog { private TextBox titleBox; - protected TextBox startTimeBox; + protected DateAndTimeInput startTimeBox; private SimplePanel infoLabel; // showing either mime type or youtube id @@ -83,6 +87,7 @@ public class NewMediaDialog extends DataEntryDialog { private Button defaultTimeButton; private SimpleBusyIndicator busyIndicator; + boolean manuallyEditedStartTime = false; public NewMediaDialog(MediaServiceAsync mediaService, TimePoint defaultStartTime, StringMessages stringMessages, RegattaAndRaceIdentifier raceIdentifier, DialogCallback dialogCallback) { @@ -92,45 +97,22 @@ public class NewMediaDialog extends DataEntryDialog { this.stringMessages = stringMessages; this.raceIdentifier = raceIdentifier; this.mediaService = mediaService; - registerNativeMethods(); } @Override protected MediaTrack getResult() { mediaTrack.title = titleBox.getValue(); - updateStartTimeFromUi(); - updateDurationFromUi(); connectMediaWithRace(); return mediaTrack; } - private void updateDurationFromUi() { - String duration = durationBox.getValue(); - if (duration != null && !duration.trim().isEmpty()) { - mediaTrack.duration = TimeFormatUtil.hrsMinSecToMilliSeconds(duration); - } else { - mediaTrack.duration = null; - } - } - protected void connectMediaWithRace() { Set assignedRaces = new HashSet(); assignedRaces.add(this.raceIdentifier); mediaTrack.assignedRaces = assignedRaces; } - protected void updateStartTimeFromUi() { - String startTimeText = startTimeBox.getValue(); - if (startTimeText != null && !startTimeText.equals("")) { - try { - Date startTime = TimeFormatUtil.DATETIME_FORMAT.parse(startTimeText); - mediaTrack.startTime = new MillisecondsTimePoint(startTime); - } catch (IllegalArgumentException ex) { - // TODO: highlight format error in UI - } - } - } - + //used for audio only tracks, using native mediaelement to determine time private void loadMediaDuration() { MediaBase mediaBase = Audio.createIfSupported(); if (mediaBase != null) { @@ -150,7 +132,9 @@ public class NewMediaDialog extends DataEntryDialog { }-*/; public void loadedmetadata(MediaElement mediaElement) { - mediaTrack.startTime = this.defaultStartTime; + if (!manuallyEditedStartTime) { + mediaTrack.startTime = this.defaultStartTime; + } double duration = mediaElement.getDuration(); if (duration > 0) { mediaTrack.duration = new MillisecondsDurationImpl((long) Math.round(duration * 1000)); @@ -166,7 +150,19 @@ public class NewMediaDialog extends DataEntryDialog { Grid formGrid = new Grid(6, 2); formGrid.setCellSpacing(3); formGrid.setWidget(0, 0, new Label(stringMessages.url() + ":")); - urlBox = createUrlBox(); + urlBox = createTextBox(null); + urlBox.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + manuallyEditedStartTime = false; + updateFromUrl(); + } + }); + } + }); formGrid.setWidget(0, 1, urlBox); formGrid.setWidget(1, 0, new Label(stringMessages.name() + ":")); titleBox = createTextBox(null); @@ -177,13 +173,23 @@ public class NewMediaDialog extends DataEntryDialog { infoLabel.setWidget(new Label("")); formGrid.setWidget(2, 1, infoLabel); formGrid.setWidget(3, 0, new Label(stringMessages.startTime() + ":")); - startTimeBox = createTextBox(null); + + startTimeBox = new DateAndTimeInput(Accuracy.MILLISECONDS); FlowPanel startTimePanel = new FlowPanel(); startTimePanel.add(startTimeBox); + startTimeBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + manuallyEditedStartTime = true; + mediaTrack.startTime = new MillisecondsTimePoint(event.getValue()); + } + }); + defaultTimeButton = new Button(StringMessages.INSTANCE.resetStartTimeToDefault()); defaultTimeButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { + manuallyEditedStartTime = true; mediaTrack.startTime = defaultStartTime; refreshUI(); } @@ -192,6 +198,17 @@ public class NewMediaDialog extends DataEntryDialog { formGrid.setWidget(3, 1, startTimePanel); formGrid.setWidget(4, 0, new Label(stringMessages.duration() + ":")); durationBox = createTextBox(null); + durationBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + String duration = durationBox.getValue(); + if (duration != null && !duration.trim().isEmpty()) { + mediaTrack.duration = TimeFormatUtil.hrsMinSecToMilliSeconds(duration); + } else { + mediaTrack.duration = null; + } + } + }); formGrid.setWidget(4, 1, durationBox); busyIndicator = new SimpleBusyIndicator(); formGrid.setWidget(5, 0, busyIndicator); @@ -199,29 +216,13 @@ public class NewMediaDialog extends DataEntryDialog { return mainPanel; } - private TextBox createUrlBox() { - TextBox result = createTextBox(null); - result.addChangeHandler(new ChangeHandler() { - @Override - public void onChange(ChangeEvent event) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - updateFromUrl(); - } - }); - } - }); - return result; - } - protected void updateFromUrl() { String url = urlBox.getValue(); String youtubeId = YoutubeApi.getIdByUrl(url); if (youtubeId != null) { mediaTrack.url = youtubeId; mediaTrack.mimeType = MimeType.youtube; - loadYoutubeMetadata(youtubeId); + loadYoutubeMetadata(); } else { mediaTrack.url = url; loadMediaDuration(); @@ -249,6 +250,29 @@ public class NewMediaDialog extends DataEntryDialog { } } + private void loadYoutubeMetadata() { + if(mediaTrack.url != null && !mediaTrack.url.isEmpty()) { + mediaService.checkYoutubeMetadata(mediaTrack.url, new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + infoLabel.setWidget(new Label(caught.getMessage())); + } + + @Override + public void onSuccess(VideoMetadataDTO result) { + if (result.isDownloadable()) { + mediaTrack.duration = result.getDuration(); + mediaTrack.title = result.getMessage(); + refreshUI(); + } else { + infoLabel.setWidget(new Label(result.getMessage())); + } + } + }); + } + } + private String sliceBefore(String lastPathSegment, String slicer) { int paramSegment = lastPathSegment.indexOf(slicer); if (paramSegment > 0) { @@ -257,65 +281,11 @@ public class NewMediaDialog extends DataEntryDialog { return lastPathSegment; } - private native void registerNativeMethods() /*-{ - var that = this; - window.youtubeMetadataCallback = function(metadata) { - var title = metadata.entry.media$group.media$title.$t; - var duration = metadata.entry.media$group.yt$duration.seconds; - var description = metadata.entry.media$group.media$description.$t; - that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::youtubeMetadataCallback(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)(title, duration, description); - } - }-*/; - - /** - * Inspired by https://developers.google.com/web-toolkit/doc/latest/tutorial/Xsite - * - * @param youtubeId - */ - public native void loadYoutubeMetadata(String youtubeId) /*-{ - var that = this; - //Create temporary script element. - window.youtubeMetadataCallbackScript = document.createElement("script"); - window.youtubeMetadataCallbackScript.src = "http://gdata.youtube.com/feeds/api/videos/" - + youtubeId - + "?alt=json&orderby=published&format=6&callback=youtubeMetadataCallback"; - document.body.appendChild(window.youtubeMetadataCallbackScript); - - // Cancel meta data capturing after has 2-seconds timeout. - setTimeout( - function() { - //Remove temporary script element. - if (window != null && window.youtubeMetadataCallbackScript != null) { - document.body.removeChild(window.youtubeMetadataCallbackScript); - delete window.youtubeMetadataCallbackScript; - } - that.@com.sap.sailing.gwt.ui.client.media.NewMediaDialog::setBusy(Z)(false); - }, 2000); - }-*/; - public void setBusy(boolean busy) { busyIndicator.setBusy(busy); } - public void youtubeMetadataCallback(String title, String durationInSeconds, String description) { - busyIndicator.setBusy(false); - mediaTrack.title = title; - try { - long duration = (long) Math.round(1000 * Double - .valueOf(durationInSeconds)); - if (duration > 0) { - mediaTrack.duration = new MillisecondsDurationImpl(duration); - } else { - mediaTrack.duration = null; - } - } catch (NumberFormatException ex) { - mediaTrack.duration = null; - } - mediaTrack.startTime = this.defaultStartTime; - refreshUI(); - } - - private void refreshUI() { + protected void refreshUI() { titleBox.setValue(mediaTrack.title, DONT_FIRE_EVENTS); if (mediaTrack.isYoutube()) { infoLabelLabel.setText(stringMessages.youtubeId() + ":"); @@ -335,10 +305,7 @@ public class NewMediaDialog extends DataEntryDialog { } } TimePoint startTime = mediaTrack.startTime != null ? mediaTrack.startTime : defaultStartTime; - String startTimeText = TimeFormatUtil.DATETIME_FORMAT.format(startTime.asDate()); - - startTimeBox.setText(startTimeText); - updateStartTimeFromUi(); + startTimeBox.setValue(startTime == null ? null : startTime.asDate()); if (mediaTrack.duration != null) { durationBox.setText(TimeFormatUtil.durationToHrsMinSec(mediaTrack.duration)); } else { @@ -372,11 +339,10 @@ public class NewMediaDialog extends DataEntryDialog { /** * For a given url that points to an mp4 video, attempts are made to parse the header, to determine the actual - * starttime of the video and to check for a 360° flag. The video will be analyzed by the backendserver, either via + * starttime of the video and to check for a 360° flag. The video will be analyzed by the backendserver, either via * direct download, or proxied by the client, if a video is only available locally. If the video header cannot be * read, default values are used instead. */ - // TODO Eclipse doesn't find any calls to this private method; can it be removed? private void checkMetadata(String url, Label lbl, AsyncCallback asyncCallback) { // check on server first mediaService.checkMetadata(mediaTrack.url, new AsyncCallback() { @@ -439,7 +405,7 @@ public class NewMediaDialog extends DataEntryDialog { } else { mediaTrack.duration = result.getDuration(); mediaTrack.mimeType = result.isSpherical() ? MimeType.mp4panorama : MimeType.mp4; - if (result.getRecordStartedTime() != null) { + if (result.getRecordStartedTime() != null && !manuallyEditedStartTime) { mediaTrack.startTime = new MillisecondsTimePoint(result.getRecordStartedTime()); } refreshUI(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaWithRaceSelectionDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaWithRaceSelectionDialog.java index 9b13ed8f305..4ed9ecb38c5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaWithRaceSelectionDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/media/NewMediaWithRaceSelectionDialog.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.client.media; +import java.util.Date; import java.util.Set; import com.google.gwt.user.client.ui.VerticalPanel; @@ -13,7 +14,6 @@ import com.sap.sailing.gwt.ui.client.RegattasDisplayer; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.gwt.client.ErrorReporter; public class NewMediaWithRaceSelectionDialog extends NewMediaDialog { @@ -41,19 +41,7 @@ public class NewMediaWithRaceSelectionDialog extends NewMediaDialog { this.regattasDisplayers = regattasDisplayers; } - protected void updateStartTimeFromUi() { - try { - String startTime = startTimeBox.getValue(); - if (startTime != null && !startTime.equals("")) { - mediaTrack.startTime = new MillisecondsTimePoint(TimeFormatUtil.DATETIME_FORMAT.parse(startTime)); - regattaRefresher.fillRegattas(); - listOfRacesForMedia.setVisible(true); - } - } catch (Exception e) { - listOfRacesForMedia.setVisible(false); - } - } - + @Override protected void connectMediaWithRace() { mediaTrack.assignedRaces = racesForMediaDialog.getAssignedRaces(); } @@ -81,6 +69,7 @@ public class NewMediaWithRaceSelectionDialog extends NewMediaDialog { return listOfRacesForMedia = racesForMediaDialog.getAdditionalWidget(); } + @Override protected Widget getAdditionalWidget() { VerticalPanel mainPanel = (VerticalPanel) super.getAdditionalWidget(); mainPanel.add(racesForMedia()); @@ -88,4 +77,17 @@ public class NewMediaWithRaceSelectionDialog extends NewMediaDialog { return mainPanel; } + @Override + protected void refreshUI() { + super.refreshUI(); + try { + Date value = startTimeBox.getValue(); + if (value != null) { + regattaRefresher.fillRegattas(); + listOfRacesForMedia.setVisible(true); + } + } catch (Exception e) { + listOfRacesForMedia.setVisible(false); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartToCsvExporter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartToCsvExporter.java deleted file mode 100644 index 6c20650069f..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartToCsvExporter.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.sap.sailing.gwt.ui.client.shared.charts; - -import org.moxieapps.gwt.highcharts.client.Chart; -import org.moxieapps.gwt.highcharts.client.Point; -import org.moxieapps.gwt.highcharts.client.Series; - -import com.google.gwt.i18n.client.NumberFormat; -import com.google.gwt.user.client.Window; -import com.sap.sailing.gwt.ui.client.StringMessages; - -/** - * Provides a service for export of chart data into CSV format. - * - * @author Vladislav Chumak (D069712) - * - */ -public class ChartToCsvExporter { - - private final StringMessages stringMessages; - - public ChartToCsvExporter(StringMessages stringMessages) { - this.stringMessages = stringMessages; - } - - /** - * Converts the data of the chart into CSV format and stores it in clipboard. Finally, window alert gets shown - * to the user with information that there is CSV content in his clipboard. - * - * @param chartToExport - * Chart with data to export - */ - public void exportChartAsCsvToClipboard(Chart chartToExport) { - String csvContentExport = createCsvExportContentForStatisticsCurve(chartToExport); - copyTextToClipboard(csvContentExport); - Window.alert(stringMessages.csvCopiedToClipboard()); - } - - private static String createCsvExportContentForStatisticsCurve(Chart chartToExport) { - if (chartToExport != null && chartToExport.getSeries().length > 0) { - StringBuilder csvStr = new StringBuilder("Series name"); - for (Point point : chartToExport.getSeries()[0].getPoints()) { - csvStr.append(';'); - csvStr.append(point.getX()); - } - csvStr.append("\r\n"); - for (Series series : chartToExport.getSeries()) { - csvStr.append(series.getName()); - for (Point point : series.getPoints()) { - csvStr.append(';'); - csvStr.append(NumberFormat.getDecimalFormat().format(point.getY())); - } - csvStr.append("\r\n"); - } - String csvContentExport = csvStr.toString(); - return csvContentExport; - } - return "Statistics are empty"; - } - - private static native void copyTextToClipboard(String text) /*-{ - var textArea = document.createElement("textarea"); - // - // *** This styling is an extra step which is likely not required. *** - // - // Why is it here? To ensure: - // 1. the element is able to have focus and selection. - // 2. if element was to flash render it has minimal visual impact. - // 3. less flakyness with selection and copying which **might** occur if - // the textarea element is not visible. - // - // The likelihood is the element won't even render, not even a flash, - // so some of these are just precautions. However in IE the element - // is visible whilst the popup box asking the user for permission for - // the web page to copy to the clipboard. - // - - // Place in top-left corner of screen regardless of scroll position. - textArea.style.position = 'fixed'; - textArea.style.top = 0; - textArea.style.left = 0; - // Ensure it has a small width and height. Setting to 1px / 1em - // doesn't work as this gives a negative w/h on some browsers. - textArea.style.width = '2em'; - textArea.style.height = '2em'; - // We don't need padding, reducing the size if it does flash render. - textArea.style.padding = 0; - // Clean up any borders. - textArea.style.border = 'none'; - textArea.style.outline = 'none'; - textArea.style.boxShadow = 'none'; - // Avoid flash of white box if rendered for any reason. - textArea.style.background = 'transparent'; - textArea.value = text; - document.body.appendChild(textArea); - textArea.select(); - try { - var successful = document.execCommand('copy'); - } catch (err) { - console.log('Unable to copy'); - } - document.body.removeChild(textArea); - }-*/; - -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java index c3a85cc3274..5fb4d1b13c6 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPositionPanel.java @@ -114,7 +114,6 @@ public class EditMarkPositionPanel extends AbstractRaceChart i private LinePlotOptions markSeriesPlotOptions; private final Label noMarkSelectedLabel; private MapWidget map; - private boolean visible; private List courseMarkClickHandlers; private Map> marksFromToTimes; @@ -131,6 +130,7 @@ public class EditMarkPositionPanel extends AbstractRaceChart i private final MarkPositionService markPositionService; private final RaceIdentifierToLeaderboardRaceColumnAndFleetMapper raceIdentifierToLeaderboardRaceColumnAndFleetMapper; + protected boolean nonTrackingWarningWasDisplayed; public EditMarkPositionPanel(Component parent, ComponentContext context, final RaceMap raceMap, final SingleRaceLeaderboardPanel leaderboardPanel, @@ -373,20 +373,25 @@ public class EditMarkPositionPanel extends AbstractRaceChart i overlay.setVisible(false); if (currentFixPositionChooser == null) { OverlayClickHandler.this.unregisterAll(); - currentFixPositionChooser = new FixPositionChooser(EditMarkPositionPanel.this, stringMessages, map, getIndexOfFixInPolyline(mark, fix), polyline.getPath(), overlay, new Callback() { - @Override - public void onFailure(Exception reason) { - overlay.setVisible(true); - OverlayClickHandler.this.registerAll(); - resetCurrentFixPositionChooser(); - } - @Override - public void onSuccess(Position result) { - editMarkFix(mark, fix, result); - overlay.setVisible(true); - OverlayClickHandler.this.registerAll(); - resetCurrentFixPositionChooser(); - } + checkIfTracking(() -> { + currentFixPositionChooser = new FixPositionChooser(EditMarkPositionPanel.this, + stringMessages, map, getIndexOfFixInPolyline(mark, fix), polyline.getPath(), + overlay, new Callback() { + @Override + public void onFailure(Exception reason) { + overlay.setVisible(true); + OverlayClickHandler.this.registerAll(); + resetCurrentFixPositionChooser(); + } + + @Override + public void onSuccess(Position result) { + editMarkFix(mark, fix, result); + overlay.setVisible(true); + OverlayClickHandler.this.registerAll(); + resetCurrentFixPositionChooser(); + } + }); }); } popup.hide(); @@ -407,26 +412,28 @@ public class EditMarkPositionPanel extends AbstractRaceChart i } private void addDeleteItemAndShowPopup(boolean enableDelete) { - final MenuItem delete; + Runnable r = () -> { + final MenuItem delete = new MenuItem(stringMessages.deleteFix(), new ScheduledCommand() { + @Override + public void execute() { + if (enableDelete) { + removeMarkFix(mark, fix); + popup.hide(); + } + } + }); + if (!enableDelete) { + delete.setEnabled(false); + delete.setTitle(stringMessages.theDeletionOfThisFix()); + } + menu.addItem(delete); + popup.setWidget(menu); + }; if (enableDelete) { - delete = new MenuItem(stringMessages.deleteFix(), new ScheduledCommand() { - @Override - public void execute() { - removeMarkFix(mark, fix); - popup.hide(); - } - }); + checkIfTracking(r); } else { - delete = new MenuItem(stringMessages.deleteFix(), new ScheduledCommand() { - @Override - public void execute() { - } - }); - delete.setEnabled(false); - delete.setTitle(stringMessages.theDeletionOfThisFix()); + r.run(); } - menu.addItem(delete); - popup.setWidget(menu); } }); } @@ -704,20 +711,8 @@ public class EditMarkPositionPanel extends AbstractRaceChart i } public void setVisible(boolean visible) { - this.visible = visible; - if(visible && !isVisible()){ - sailingService.checkIfRaceIsTracked(selectedRaceIdentifier, new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - Window.alert(stringMessages.serverError()); - } - @Override - public void onSuccess(Boolean result) { - if (Boolean.FALSE.equals(result)) { - Window.alert(stringMessages.positionEditOnNonTrackingRace()); - } - } - }); + if (visible && !isVisible()) { + checkIfTracking(() -> {}); } if (map == null) { map = raceMap.getMap(); @@ -725,7 +720,7 @@ public class EditMarkPositionPanel extends AbstractRaceChart i setMap(map); } } - if (this.visible) { + if (visible) { if (sideBySideComponentViewer != null) { sideBySideComponentViewer.setLeftComponent(marksPanel); sideBySideComponentViewer.setLeftComponentToggleButtonVisible(false); @@ -756,7 +751,30 @@ public class EditMarkPositionPanel extends AbstractRaceChart i raceMap.unregisterAllCourseMarkInfoWindowClickHandlers(); raceMap.registerAllCourseMarkInfoWindowClickHandlers(); } - super.setVisible(this.visible); + super.setVisible(visible); + } + + private void checkIfTracking(Runnable continuation) { + if (nonTrackingWarningWasDisplayed) { + continuation.run(); + } else { + sailingService.checkIfRaceIsTracking(selectedRaceIdentifier, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + Window.alert(stringMessages.serverError()); + continuation.run(); + } + + @Override + public void onSuccess(Boolean result) { + if (Boolean.FALSE.equals(result)) { + Window.alert(stringMessages.positionEditOnNonTrackingRace()); + nonTrackingWarningWasDisplayed = true; + } + continuation.run(); + } + }); + } } @Override @@ -943,17 +961,21 @@ public class EditMarkPositionPanel extends AbstractRaceChart i * Is thrown when another fix position chooser is still open */ public void createFixPositionChooserToAddFixToMark(MarkDTO mark, Callback callback) { - if (currentFixPositionChooser != null) { + if (currentFixPositionChooser != null) { return; } - int index = 0; - for (GPSFixDTO fix : marks.get(mark).keySet()) { - if (fix.timepoint.after(timer.getTime())) - break; - index++; - } - Polyline polyline = polylines.get(mark); - currentFixPositionChooser = new FixPositionChooser(this, stringMessages, map, index, polyline != null ? polyline.getPath() : null, map.getCenter(), raceMap.getCoordinateSystem(), callback); + checkIfTracking(() -> { + int index = 0; + for (GPSFixDTO fix : marks.get(mark).keySet()) { + if (fix.timepoint.after(timer.getTime())) + break; + index++; + } + Polyline polyline = polylines.get(mark); + currentFixPositionChooser = new FixPositionChooser(EditMarkPositionPanel.this, stringMessages, map, index, + polyline != null ? polyline.getPath() : null, map.getCenter(), raceMap.getCoordinateSystem(), + callback); + }); } public void resetCurrentFixPositionChooser() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionService.java index c56dd513909..f11866ee636 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionService.java @@ -50,12 +50,6 @@ public interface MarkPositionService { void getMarksInTrackedRace(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, AsyncCallback> callback); - // Jonas Dann: I had no great idea for handling of massive amount of fixes. An exception is no real option. I - // think the fixes have to be thinned out somehow and maybe shown in more detail when zoomed in. The aggregation of - // fixes is in my eyes no option, because then you have to define behaviour when moving them. - // I had no remaining time to incorporate the thinned out variable in my code. - void getMarkTracks(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, AsyncCallback callback); - void getMarkTrack(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, String markIdAsString, AsyncCallback callback); /** diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionServiceForSailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionServiceForSailingService.java index 410c67aaa92..05fe6a96b81 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionServiceForSailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MarkPositionServiceForSailingService.java @@ -20,11 +20,6 @@ public class MarkPositionServiceForSailingService implements MarkPositionService sailingService.getMarksInTrackedRace(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), callback); } - @Override - public void getMarkTracks(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, AsyncCallback callback) { - sailingService.getMarkTracks(raceIdentifier.getLeaderboardName(), raceIdentifier.getRaceColumnName(), raceIdentifier.getFleetName(), callback); - } - @Override public void getMarkTrack(LeaderboardNameRaceColumnNameAndFleetName raceIdentifier, String markIdAsString, AsyncCallback callback) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java index cb5558f9bc6..c9cbcf86dd9 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChart.java @@ -6,6 +6,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 org.moxieapps.gwt.highcharts.client.Axis; @@ -226,65 +227,46 @@ public class WindChart extends AbstractRaceChart implements R } private void updateVisibleSeries() { - List currentSeries = Arrays.asList(chart.getSeries()); - Set visibleSeries = new HashSet(currentSeries); + final Set visibleSeries = new HashSet(Arrays.asList(chart.getSeries())); if (preselectFilter != null) { forceSeriesSelection(visibleSeries, windSourceDirectionSeries); forceSeriesSelection(visibleSeries, windSourceSpeedSeries); } else { - if (settings.isShowWindDirectionsSeries()) { - for (Map.Entry e : windSourceDirectionSeries.entrySet()) { - Series series = e.getValue(); - if (settings.getWindDirectionSourcesToDisplay().contains(e.getKey().getType())) { - if (!visibleSeries.contains(series)) { - chart.addSeries(series, false, false); - } - series.select(true); // ensures that the checkbox will be ticked - series.setVisible(true); //and that it is actually being rendered - } else { - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); - } - } - } - } else { - for (Map.Entry e : windSourceDirectionSeries.entrySet()) { - Series series = e.getValue(); - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); - } - } - } - - if (settings.isShowWindSpeedSeries()) { - for (Map.Entry e : windSourceSpeedSeries.entrySet()) { - Series series = e.getValue(); - if (settings.getWindSpeedSourcesToDisplay().contains(e.getKey().getType())) { - if (!visibleSeries.contains(series)) { - chart.addSeries(series, false, false); - } - series.select(true); // ensures that the checkbox will be ticked - series.setVisible(true); //and that it is actually being rendered - } else { - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); - } - } - } - } else { - for (Map.Entry e : windSourceSpeedSeries.entrySet()) { - Series series = e.getValue(); - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); - } - } - } + final boolean showDirectionSeries = settings.isShowWindDirectionsSeries(); + final Set directionSourceTypesToDisplay = settings.getWindDirectionSourcesToDisplay(); + updateSeries(visibleSeries, windSourceDirectionSeries, showDirectionSeries, directionSourceTypesToDisplay); + final boolean showSpeedSeries = settings.isShowWindSpeedSeries(); + final Set speedSourceTypesToDisplay = settings.getWindSpeedSourcesToDisplay(); + updateSeries(visibleSeries, windSourceSpeedSeries, showSpeedSeries, speedSourceTypesToDisplay); } onResize(); } + private void updateSeries(final Set visibleSeries, final Map windSourceToSeriesMap, + final boolean showSeries, final Set windSourceTypesToDisplay) { + if (showSeries) { + for (Entry entry : windSourceToSeriesMap.entrySet()) { + final Series series = entry.getValue(); + if (windSourceTypesToDisplay.contains(entry.getKey().getType())) { + if (!visibleSeries.contains(series)) { + chart.addSeries(series, false, false); + } + } else if (visibleSeries.contains(series)) { + chart.removeSeries(series, false); + } + } + } else { + for (Entry entry : windSourceToSeriesMap.entrySet()) { + final Series series = entry.getValue(); + if (visibleSeries.contains(series)) { + chart.removeSeries(series, false); + } + } + } + } + private boolean forceSeriesSelection(Set visibleSeries, Map toProcess) { boolean wasInResult = false; for (Map.Entry e : toProcess.entrySet()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java index ea87432c188..cfa1fa73717 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatClassVectorGraphicsResolver.java @@ -44,7 +44,8 @@ public class BoatClassVectorGraphicsResolver { BoatClassMasterdata.A_CAT, BoatClassMasterdata.TORNADO, BoatClassMasterdata.FLYING_PHANTOM); BoatClassVectorGraphics keelBoatWithGennaker = new KeelBoatWithGennakerVectorGraphics(BoatClassMasterdata.J70, BoatClassMasterdata.B_ONE, BoatClassMasterdata.J80, BoatClassMasterdata.LASER_SB3, BoatClassMasterdata.LONGTZE, - BoatClassMasterdata.RS_FEVA, BoatClassMasterdata.RS_TERA, BoatClassMasterdata.RS100, BoatClassMasterdata.TP52); + BoatClassMasterdata.RS_FEVA, BoatClassMasterdata.RS_TERA, BoatClassMasterdata.RS100, BoatClassMasterdata.TP52, + BoatClassMasterdata.CLUB_SWAN_50); BoatClassVectorGraphics dinghyWithSpinnaker = new DinghyWithSpinnakerVectorGraphics(BoatClassMasterdata._420, BoatClassMasterdata._470, BoatClassMasterdata._5O5, BoatClassMasterdata.CADET, BoatClassMasterdata.FLYING_DUTCHMAN, BoatClassMasterdata.FOLKBOAT, BoatClassMasterdata.DYAS, BoatClassMasterdata.DRAGON_INT, @@ -53,7 +54,7 @@ public class BoatClassVectorGraphicsResolver { BoatClassMasterdata.TOM_28_MAX, BoatClassMasterdata.DELPHIA_24, BoatClassMasterdata.RS200, BoatClassMasterdata.RS400, BoatClassMasterdata.RS500, BoatClassMasterdata.RS800, BoatClassMasterdata.STREAMLINE, BoatClassMasterdata.SWAN_45, BoatClassMasterdata.TEENY, BoatClassMasterdata.X_99, - BoatClassMasterdata.TRIAS, BoatClassMasterdata.VENT_D_OUEST); + BoatClassMasterdata.TRIAS, BoatClassMasterdata.VENT_D_OUEST, BoatClassMasterdata.FLYING_JUNIOR, BoatClassMasterdata.VAURIEN, BoatClassMasterdata.VARIANTA); BoatClassVectorGraphics circle = new CircleVectorGraphics(BoatClassMasterdata.RUNNING); defaultBoatVectorGraphics = dinghyWithSpinnaker; // TODO see bug 2571; this should be a slup-rigged icon working for 470, 505, J/70 etc. diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java index 9d91e40a447..50cbefb7269 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java @@ -54,7 +54,7 @@ public class CombinedWindPanel extends FlowPanel { newShowStreamletsOverlaySetting, oldRaceMapSettings.isShowSimulationOverlay(), oldRaceMapSettings.isShowMapControls(), oldRaceMapSettings.getManeuverTypesToShow(), oldRaceMapSettings.isShowDouglasPeuckerPoints(), oldRaceMapSettings.isShowEstimatedDuration(), - oldRaceMapSettings.getStartCountDownFontSizeScaling()); + oldRaceMapSettings.getStartCountDownFontSizeScaling(), oldRaceMapSettings.isShowManeuverLossVisualization()); map.updateSettings(newRaceMapSettings); } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LineInfoProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LineInfoProvider.java new file mode 100755 index 00000000000..a3f22b42b26 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/LineInfoProvider.java @@ -0,0 +1,12 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +/** + * Used to get the information that appears on hovering over a line and that is displayed in + * {@link #adjustInfoOverlayForVisibleLine} + */ +interface LineInfoProvider { + String getLineInfo(); + default boolean isShowInfoOverlayWithHelplines() { + return true; + }; +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverColor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverColor.java index bade7d63edf..c55129e5de6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverColor.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverColor.java @@ -6,7 +6,7 @@ public enum ManeuverColor { RED, GREEN; public static ManeuverColor getManeuverColor(ManeuverDTO maneuver) { - return maneuver.directionChangeInDegrees < 0 ? RED : GREEN; + return maneuver.getDirectionChangeInDegrees() < 0 ? RED : GREEN; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkersAndLossIndicators.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkersAndLossIndicators.java new file mode 100755 index 00000000000..c9e99a4ab70 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkersAndLossIndicators.java @@ -0,0 +1,423 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.gwt.canvas.dom.client.CssColor; +import com.google.gwt.event.logical.shared.ValueChangeEvent; +import com.google.gwt.event.logical.shared.ValueChangeHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.overlays.Marker; +import com.google.gwt.maps.client.overlays.MarkerOptions; +import com.google.gwt.maps.client.overlays.Polyline; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; +import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.impl.DegreeBearingImpl; +import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.player.Timer.PlayStates; + +/** + * Encapsulates the diamond-shaped maneuver markers that can be displayed on a competitor's + * tail, as well as the optional maneuver loss indicators that may be shown for a maneuver + * marker.

      + * + * Invariants: + * + *

        + *
      • When a maneuver loss indicator is shown then the corresponding maneuver marker is + * visible as well.
      • + *
      • When adding a maneuver marker to the map display, the {@link RaceMapSettings#isShowManeuverLossVisualization()} setting + * decides whether a maneuver loss indicator is added to the map display as well.
      • + *
      + * + * @author Axel Uhl (d043530) + * + */ +public class ManeuverMarkersAndLossIndicators { + private static final String MANEUVERLOSSLINES_PROJECTEDLINES_COLOR = "#ffff00"; + private static final String MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR = "#ffffff"; + private static final String MANEUVERLOSSLINES_RED = "#ff0000"; + private static final String MANEUVERLOSSLINES_GREEN = "#00ff00"; + private static final double LOWLIGHTED_LINE_OPACITY = 0.5; + private static final int HIGHLIGHTED_LINE_STROKEWEIGHT = 2; + private static final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1); + + private final RaceMap raceMap; + private final SailingServiceAsync sailingService; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private Map> lastManeuverResult; + + /** + * markers displayed in response to + * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} + */ + private final Map, Marker> maneuverMarkers; + + /** The Map where the polylines for the specific maneuver are stored. */ + private final Map, Set> maneuverLossLinesMap; + + /** The Map where the info overlays for a maneuver loss visualization are stored. */ + private final Map, SmallTransparentInfoOverlay> maneuverLossInfoOverlayMap; + + public ManeuverMarkersAndLossIndicators(RaceMap raceMap, SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) { + this.raceMap = raceMap; + this.sailingService = sailingService; + this.errorReporter = errorReporter; + this.stringMessages = stringMessages; + this.maneuverMarkers = new HashMap<>(); + this.maneuverLossLinesMap = new HashMap<>(); + this.maneuverLossInfoOverlayMap = new HashMap<>(); + } + + public void getAndShowManeuvers(RegattaAndRaceIdentifier race, Map timeRange) { + sailingService.getManeuvers(race, timeRange, new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(Map> result) { + lastManeuverResult = result; + removeAllManeuverMarkers(); + if (raceMap.getTimer().getPlayState() != PlayStates.Playing) { + showManeuvers(result); + } + } + }); + } + + /** + * Removes the visualizations of maneuvers from the map. The maneuver objects as received from the server are remembered + * (see {@link #lastManeuverResult}) and may be restored by a subsequent call to {@link #showManeuvers(Map)} as it is, e.g., + * called by {@link #updateManeuverMarkersAfterSettingsChanged()}. + */ + void removeAllManeuverMarkers() { + // clone entry set to avoid concurrent modification exception + for (final Entry, Marker> keyAndMarker : new HashSet<>(maneuverMarkers.entrySet())) { + removeManeuverMarker(keyAndMarker.getKey(), keyAndMarker.getValue()); + } + } + + private void removeManeuverMarker(Triple key, Marker marker) { + marker.setMap((MapWidget) null); + removeManeuverLossLinesAndInfoOverlayForManeuver(key); + maneuverMarkers.remove(key); + } + + private void showManeuvers(Map> maneuvers) { + if (raceMap.getMap() != null && maneuvers != null) { + Set keySet = maneuvers.keySet(); + Iterator iter = keySet.iterator(); + while (iter.hasNext()) { + CompetitorDTO competitorDTO = iter.next(); + List maneuversForCompetitor = maneuvers.get(competitorDTO); + for (ManeuverDTO maneuver : maneuversForCompetitor) { + if (raceMap.getSettings().isShowManeuverType(maneuver.getType())) { + createAndAddMarkerOfManeuver(maneuver, competitorDTO); + } + } + } + } + } + + /** + * Creates a maneuver marker. Additionally, if the {@link RaceMapSettings#isShowManeuverLossVisualization()} setting is {@code true}, + * a maneuver loss indicator is shown. + */ + private void createAndAddMarkerOfManeuver(ManeuverDTO maneuver, CompetitorDTO competitor) { + LatLng latLng = raceMap.getCoordinateSystem().toLatLng(maneuver.getPosition()); + Marker maneuverMarker = raceMap.getRaceMapImageManager().getManeuverIconsForTypeAndDirectionIndicatingColor() + .get(new Pair(maneuver.getType(), ManeuverColor.getManeuverColor(maneuver))); + MarkerOptions options = MarkerOptions.newInstance(); + options.setIcon(maneuverMarker.getIcon_MarkerImage()); + Marker marker = Marker.newInstance(options); + marker.setPosition(latLng); + marker.setTitle(ManeuverTypeFormatter.format(maneuver.getType(), stringMessages)); + marker.addClickHandler(event -> { + LatLng where = raceMap.getCoordinateSystem().toLatLng(maneuver.getPosition()); + Widget content = getInfoWindowContent(maneuver, competitor); + raceMap.getManagedInfoWindow().openAtPosition(content, where); + }); + final Triple key = createManeuverKey(maneuver, competitor); + maneuverMarkers.put(key, marker); + marker.setMap(raceMap.getMap()); + // maneuver loss visualization: if the setting is active, show loss visualization for the maneuver by default + if (raceMap.getSettings().isShowManeuverLossVisualization() && maneuver.getManeuverLoss() != null) { + createManeuverLossLinesAndInfoOverlays(maneuver, competitor); + } + } + + private Triple createManeuverKey(ManeuverDTO maneuver, CompetitorDTO competitor) { + return new Triple<>(competitor.getIdAsString(), maneuver.getTimePoint(), maneuver.getType()); + } + + final LineInfoProvider middleManeuverAngleLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.middleManeuverAngle(); + } + + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + final LineInfoProvider extrapolatedLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.extrapolatedManeuverStartPosition(); + } + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + final LineInfoProvider projectedExtrapolatedLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.projectedExtrapolatedManeuverStartPosition(); + } + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + final LineInfoProvider projectedManeuverEndLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.projectedManeuverEndPosition(); + } + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + final LineInfoProvider maneuverLossLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.maneuverLoss(); + } + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + final LineInfoProvider bearingAtManeuverEndPositionLineInfoProvider = new LineInfoProvider() { + @Override + public String getLineInfo() { + return stringMessages.bearingAtManeuverEndPosition(); + } + @Override + public boolean isShowInfoOverlayWithHelplines() { + return false; + } + }; + + /** + * Removes the polylines and the info overlay visualizing one maneuver from the map. Also removes the identifying + * Triple from the corresponding + * {@link #maneuverLossLinesMap} and {@link #maneuverLossInfoOverlayMap}. + */ + private void removeManeuverLossLinesAndInfoOverlayForManeuver(ManeuverDTO maneuver, CompetitorDTO competitor) { + Triple key = createManeuverKey(maneuver, competitor); + removeManeuverLossLinesAndInfoOverlayForManeuver(key); + } + + private void removeManeuverLossLinesAndInfoOverlayForManeuver(Triple key) { + if (maneuverLossLinesMap.get(key) != null) { + for (Polyline p : maneuverLossLinesMap.get(key)) { + p.setMap((MapWidget) null); + } + maneuverLossLinesMap.remove(key); + } + final SmallTransparentInfoOverlay overlay; + if ((overlay=maneuverLossInfoOverlayMap.remove(key)) != null) { + overlay.removeFromMap(); + } + } + + /** + * Creates the ManeuverLoss Lines as calculated in {@link ManeuverDetectorImpl#getManeuverLossInMeters()} and + * {@link SmallTransparentInfoOverlay}s attached to the {@link Polyline}s visualizing the maneuver. Stores them in + * {@link #maneuverLossInfoOverlayMap} and {@link #maneuverLossLinesMap} with the identifier + * Triple. + */ + private void createManeuverLossLinesAndInfoOverlays(ManeuverDTO maneuver, CompetitorDTO competitor) { + final Set maneuverLossLines = new HashSet<>(); + Bearing bearingBefore = maneuver.getManeuverLoss().getSpeedWithBearingBefore().getBearing(); + Bearing middleManeuverAngle = new DegreeBearingImpl(maneuver.getManeuverLoss().getMiddleManeuverAngle()); + Distance extrapolationOfManeuverStartPoint = + maneuver.getManeuverLoss().getSpeedWithBearingBefore().travel(maneuver.getManeuverLoss().getManeuverDuration()); + Position extrapolatedManeuverStartPosition = maneuver.getManeuverLoss().getManeuverStartPosition() + .translateRhumb(bearingBefore, extrapolationOfManeuverStartPoint); + Position intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint = maneuver.getManeuverLoss().getManeuverStartPosition() + .getIntersection(bearingBefore, maneuver.getPosition(), middleManeuverAngle); + Position projectedExtrapolatedManeuverStartPosition = extrapolatedManeuverStartPosition.projectToLineThrough( + intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle); + Position projectedManeuverEndPosition = maneuver.getManeuverLoss().getManeuverEndPosition().projectToLineThrough( + intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle); + Position startOfBearingAtManeuverEndPositionLineOnMiddleManeuverAngleLine = maneuver.getManeuverLoss() + .getManeuverEndPosition() + .getIntersection(new DegreeBearingImpl(maneuver.getSpeedWithBearingAfter().bearingInDegrees), + intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle); + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, + startOfBearingAtManeuverEndPositionLineOnMiddleManeuverAngleLine, + maneuver.getManeuverLoss().getManeuverEndPosition(), bearingAtManeuverEndPositionLineInfoProvider, + MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, LOWLIGHTED_LINE_OPACITY)); + String color = maneuver.getManeuverLoss().getDistanceLost().compareTo(Distance.NULL) > 0 + ? color = MANEUVERLOSSLINES_RED : MANEUVERLOSSLINES_GREEN; + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, + intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, projectedManeuverEndPosition, + middleManeuverAngleLineInfoProvider, MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR, + RaceMap.STANDARD_LINE_STROKEWEIGHT, LOWLIGHTED_LINE_OPACITY)); + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, + maneuver.getManeuverLoss().getManeuverStartPosition(), extrapolatedManeuverStartPosition, + extrapolatedLineInfoProvider, MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, + LOWLIGHTED_LINE_OPACITY)); + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, extrapolatedManeuverStartPosition, + projectedExtrapolatedManeuverStartPosition, projectedExtrapolatedLineInfoProvider, + MANEUVERLOSSLINES_PROJECTEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, RaceMap.LOWLIGHTED_TAIL_OPACITY)); + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, maneuver.getManeuverLoss().getManeuverEndPosition(), + projectedManeuverEndPosition, projectedManeuverEndLineInfoProvider, + MANEUVERLOSSLINES_PROJECTEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, RaceMap.LOWLIGHTED_TAIL_OPACITY)); + maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, projectedExtrapolatedManeuverStartPosition, + projectedManeuverEndPosition, maneuverLossLineInfoProvider, color, HIGHLIGHTED_LINE_STROKEWEIGHT, + RaceMap.STANDARD_LINE_OPACITY)); + final Triple key = createManeuverKey(maneuver, competitor); + maneuverLossLinesMap.put(key, maneuverLossLines); + StringBuilder sb = new StringBuilder(); + sb.append(stringMessages.maneuverLoss() + ": " + numberFormatOneDecimal.format(maneuver.getManeuverLoss().getDistanceLost().getMeters()) + + stringMessages.metersUnit() + '\n'); + if (maneuver.getType() == ManeuverType.TACK) { + sb.append(stringMessages.tackAngle() + ": "); + } else if (maneuver.getType() == ManeuverType.JIBE) { + sb.append(stringMessages.jibeAngle() + ": "); + } else { + sb.append(stringMessages.maneuverAngle() + ": "); + } + sb.append(numberFormatOneDecimal.format(Math.abs(maneuver.getDirectionChangeInDegrees())) + + stringMessages.degreesUnit()); + CssColor greyWithTransparency = CssColor.make("rgba(255,255,255,0.5)"); + SmallTransparentInfoOverlay maneuverLossInfoOverlay = new SmallTransparentInfoOverlay(raceMap.getMap(), + RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, sb.toString(), raceMap.getCoordinateSystem(), greyWithTransparency); + maneuverLossInfoOverlay.setPosition(projectedManeuverEndPosition.translateGreatCircle(middleManeuverAngle, + maneuver.getManeuverLoss().getDistanceLost().scale(0.5)), /* transition time */ -1); + maneuverLossInfoOverlay.draw(); + maneuverLossInfoOverlayMap.put(key, maneuverLossInfoOverlay); + } + + Widget getInfoWindowContent(ManeuverDTO maneuver, CompetitorDTO competitor) { + SpeedWithBearingDTO before = maneuver.getSpeedWithBearingBefore(); + SpeedWithBearingDTO after = maneuver.getSpeedWithBearingAfter(); + VerticalPanel vPanel = new VerticalPanel(); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.maneuverType(), + ManeuverTypeFormatter.format(maneuver.getType(), stringMessages))); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.time(), + DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.getTimePoint()))); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.directionChange(), + ((int) Math.round(maneuver.getDirectionChangeInDegrees())) + " " + stringMessages.degreesShort() + " (" + + ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> " + + ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")")); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.speedChange(), + NumberFormat.getDecimalFormat().format(after.speedInKnots - before.speedInKnots) + " " + + stringMessages.knotsUnit() + " (" + + NumberFormat.getDecimalFormat().format(before.speedInKnots) + " " + stringMessages.knotsUnit() + + " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " " + + stringMessages.knotsUnit() + ")")); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.maxTurningRate(), + NumberFormat.getDecimalFormat().format(maneuver.getMaxTurningRateInDegreesPerSecond()) + " " + + stringMessages.degreesPerSecondUnit())); + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.avgTurningRate(), + NumberFormat.getDecimalFormat().format(maneuver.getAvgTurningRateInDegreesPerSecond()) + " " + + stringMessages.degreesPerSecondUnit())); + if (maneuver.getType() != ManeuverType.BEAR_AWAY && maneuver.getType() != ManeuverType.HEAD_UP) { + vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.lowestSpeed(), + NumberFormat.getDecimalFormat().format(maneuver.getLowestSpeedInKnots()) + " " + + stringMessages.knotsUnit())); + } + if (maneuver.getManeuverLoss() != null) { + Widget maneuverLossWidget = raceMap.createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), + numberFormatOneDecimal.format(maneuver.getManeuverLoss().getDistanceLost().getMeters()) + " " + stringMessages.metersUnit()); + CheckBox maneuverLossLinesCheckBox = new CheckBox(stringMessages.show()); + Triple t = createManeuverKey(maneuver, competitor); + maneuverLossLinesCheckBox.setValue(maneuverLossLinesMap.containsKey(t)); + maneuverLossLinesCheckBox.addValueChangeHandler(new ValueChangeHandler() { + @Override + public void onValueChange(ValueChangeEvent event) { + if (event.getValue()) { + createManeuverLossLinesAndInfoOverlays(maneuver, competitor); + } else { + removeManeuverLossLinesAndInfoOverlayForManeuver(maneuver, competitor); + } + } + }); + HorizontalPanel hPanel = new HorizontalPanel(); + hPanel.add(maneuverLossWidget); + hPanel.add(maneuverLossLinesCheckBox); + vPanel.add(hPanel); + } + if (maneuver.getMarkPassingTimePoint() != null) { + vPanel.add( + raceMap.createInfoWindowLabelAndValue(stringMessages.markPassing(), + DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.getMarkPassingTimePoint()) + + (maneuver.getMarkPassingSide() == null ? "" + : " (" + (maneuver.getMarkPassingSide() == NauticalSide.PORT + ? stringMessages.portSide() : stringMessages.starboardSide()) + + ")"))); + } + return vPanel; + } + + public void updateManeuverMarkersAfterSettingsChanged() { + // TODO is it really necessary to remove them all first...? + if (lastManeuverResult != null) { + removeAllManeuverMarkers(); + showManeuvers(lastManeuverResult); + } + } + + /** + * Removes all maneuver displays from the map and also clears the structure holding the maneuvers + * last received from the server so that upon the next request to show maneuvers without a previous + * update from the server no maneuvers will be displayed.

      + * + * @see #getAndShowManeuvers(RegattaAndRaceIdentifier, Map) + * @see #removeAllManeuverMarkers() + */ + void clearAllManeuverMarkers() { + removeAllManeuverMarkers(); + lastManeuverResult = null; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index ef4ef34addd..814aac67e3f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -23,8 +23,6 @@ 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.event.shared.HandlerRegistration; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.maps.client.LoadApi; import com.google.gwt.maps.client.LoadApi.LoadLibrary; @@ -74,7 +72,6 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.Bounds; import com.sap.sailing.domain.common.ManeuverType; -import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.WindSource; @@ -95,7 +92,6 @@ import com.sap.sailing.gwt.ui.actions.GetWindInfoAction; import com.sap.sailing.gwt.ui.client.ClientResources; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; -import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; import com.sap.sailing.gwt.ui.client.RaceCompetitorSelectionProvider; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; @@ -115,7 +111,6 @@ import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO; import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType; import com.sap.sailing.gwt.ui.shared.LegInfoDTO; -import com.sap.sailing.gwt.ui.shared.ManeuverDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.QuickRankDTO; import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; @@ -162,13 +157,18 @@ import com.sap.sse.gwt.client.shared.settings.ComponentContext; public class RaceMap extends AbstractCompositeComponent implements TimeListener, CompetitorSelectionChangeListener, RaceTimesInfoProviderListener, TailFactory, RequiresDataInitialization, RequiresResize, QuickRankProvider { - private static final String ADVANTAGE_LINE_COLOR = "#ff9900"; // orange - private static final String FINISH_LINE_COLOR = "#000000"; - private static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200); + /* Line colors */ + static final String ADVANTAGE_LINE_COLOR = "#ff9900"; // orange + static final String START_LINE_COLOR = "#ffffff"; + static final String FINISH_LINE_COLOR = "#000000"; + static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200); + /* Line opacities and stroke weights */ + static final double LOWLIGHTED_TAIL_OPACITY = 0.3; + static final double STANDARD_LINE_OPACITY = 1.0; + static final int STANDARD_LINE_STROKEWEIGHT = 1; + public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData"; public static final String GET_WIND_DATA_CATEGORY = "getWindData"; - private static final double LOWLIGHTED_TAIL_OPACITY = 0.3; - private static final String COMPACT_HEADER_STYLE = "compactHeader"; public static final Color WATER_COLOR = new RGBColor(0, 67, 125); @@ -314,14 +314,6 @@ public class RaceMap extends AbstractCompositeComponent impleme */ protected Set douglasMarkers; - /** - * markers displayed in response to - * {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)} - */ - private Set maneuverMarkers; - - private Map> lastManeuverResult; - private Map> lastDouglasPeuckerResult; private final RaceCompetitorSelectionProvider competitorSelection; @@ -353,7 +345,7 @@ public class RaceMap extends AbstractCompositeComponent impleme */ private int startedProcessingRequestID; - private RaceMapImageManager raceMapImageManager; + private final RaceMapImageManager raceMapImageManager; private RaceMapSettings settings; private final RaceMapLifecycle raceMapLifecycle; @@ -425,8 +417,8 @@ public class RaceMap extends AbstractCompositeComponent impleme */ private boolean orientationChangeInProgress; - private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0"); - private final NumberFormat numberFormatNoDecimal = NumberFormat.getFormat("0"); + private final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1); + private final NumberFormat numberFormatNoDecimal = NumberFormatterFactory.getDecimalFormat(0); /** * The competitor for which the advantage line is currently showing. Should this competitor's quick rank change, or @@ -441,6 +433,8 @@ public class RaceMap extends AbstractCompositeComponent impleme /** Callback to set the visibility of the wind chart. */ private final Consumer showWindChartForProvider; private ManagedInfoWindow managedInfoWindow; + + private final ManeuverMarkersAndLossIndicators maneuverMarkersAndLossIndicators; private class AdvantageLineUpdater implements QuickRanksListener { @Override @@ -471,6 +465,7 @@ public class RaceMap extends AbstractCompositeComponent impleme StringMessages stringMessages, RegattaAndRaceIdentifier raceIdentifier, RaceMapResources raceMapResources, boolean showHeaderPanel, QuickRanksDTOProvider quickRanksDTOProvider, Consumer showWindChartForProvider) { super(parent, context); + this.maneuverMarkersAndLossIndicators = new ManeuverMarkersAndLossIndicators(this, sailingService, errorReporter, stringMessages); this.showHeaderPanel = showHeaderPanel; this.quickRanksDTOProvider = quickRanksDTOProvider; this.raceMapLifecycle = raceMapLifecycle; @@ -526,6 +521,14 @@ public class RaceMap extends AbstractCompositeComponent impleme this.setSize("100%", "100%"); } + ManagedInfoWindow getManagedInfoWindow() { + return managedInfoWindow; + } + + RaceMapImageManager getRaceMapImageManager() { + return raceMapImageManager; + } + public void setQuickRanksDTOProvider(QuickRanksDTOProvider newQuickRanksDTOProvider) { if (this.quickRanksDTOProvider != null) { this.quickRanksDTOProvider.moveListernersTo(newQuickRanksDTOProvider); @@ -1059,10 +1062,7 @@ public class RaceMap extends AbstractCompositeComponent impleme if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - + maneuverMarkersAndLossIndicators.clearAllManeuverMarkers(); if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) { updateCoordinateSystemFromSettings(); } @@ -1093,7 +1093,6 @@ public class RaceMap extends AbstractCompositeComponent impleme } zoomMapToNewBounds(zoomToBounds); mapFirstZoomDone = true; - updateEstimatedDuration(raceMapDataDTO.estimatedDuration); } } else { @@ -1625,12 +1624,12 @@ public class RaceMap extends AbstractCompositeComponent impleme (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE)) && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1, windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider, - "grey"); + "grey", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY); leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */ (settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE)) && startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1, leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider, - "grey"); + "grey", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY); } } @@ -1687,8 +1686,9 @@ public class RaceMap extends AbstractCompositeComponent impleme (settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) || (settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) && (!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg)); - startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine, - startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff"); + startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine, startLineLeftPosition, + startLineRightPosition, startLineInfoProvider, START_LINE_COLOR, STANDARD_LINE_STROKEWEIGHT, + STANDARD_LINE_OPACITY); // draw the finish line final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0); final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1); @@ -1701,8 +1701,9 @@ public class RaceMap extends AbstractCompositeComponent impleme } else { finishLineAdvantageText.delete(0, finishLineAdvantageText.length()); } - finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine, - finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, FINISH_LINE_COLOR); + finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine, finishLineLeftPosition, + finishLineRightPosition, finishLineInfoProvider, FINISH_LINE_COLOR, STANDARD_LINE_STROKEWEIGHT, + STANDARD_LINE_STROKEWEIGHT); // the control point pairs for which we already decided whether or not // to show a course middle line for; values tell whether to show the line and for which zero-based // start waypoint index to do so; when for an equal control point pair multiple decisions with different @@ -1787,13 +1788,10 @@ public class RaceMap extends AbstractCompositeComponent impleme return sb.toString(); } }; - return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0"); + return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, + lineInfoProvider, "#2268a0", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY); } - private interface LineInfoProvider { - String getLineInfo(); - } - /** * @param showLine * tells whether or not to show the line; if the lineToShowOrRemoveOrUpdate references a @@ -1803,8 +1801,9 @@ public class RaceMap extends AbstractCompositeComponent impleme * @return null if the line is not shown; the polyline object representing the line being displayed * otherwise */ - private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine, - final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) { + Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine, + final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, + String lineColorRGB, int strokeWeight, double strokeOpacity) { if (position1DTO != null && position2DTO != null) { if (showLine) { LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO); @@ -1815,8 +1814,8 @@ public class RaceMap extends AbstractCompositeComponent impleme options.setClickable(true); options.setGeodesic(true); options.setStrokeColor(lineColorRGB); - options.setStrokeWeight(1); - options.setStrokeOpacity(1.0); + options.setStrokeWeight(strokeWeight); + options.setStrokeOpacity(strokeOpacity); pointsAsArray = MVCArray.newInstance(); lineToShowOrRemoveOrUpdate = Polyline.newInstance(options); lineToShowOrRemoveOrUpdate.setPath(pointsAsArray); @@ -1862,22 +1861,25 @@ public class RaceMap extends AbstractCompositeComponent impleme private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider) { - SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate); - if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) { - if (infoOverlay == null) { - infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem); - infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay); - infoOverlay.addToMap(); + if (lineInfoProvider.isShowInfoOverlayWithHelplines()) { + SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate); + if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) { + if (infoOverlay == null) { + infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, + lineInfoProvider.getLineInfo(), coordinateSystem); + infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay); + infoOverlay.addToMap(); + } else { + infoOverlay.setInfoText(lineInfoProvider.getLineInfo()); + } + infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO), + position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1); + infoOverlay.draw(); } else { - infoOverlay.setInfoText(lineInfoProvider.getLineInfo()); - } - infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO), - position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1); - infoOverlay.draw(); - } else { - if (infoOverlay != null) { - infoOverlay.removeFromMap(); - infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate); + if (infoOverlay != null) { + infoOverlay.removeFromMap(); + infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate); + } } } } @@ -2143,7 +2145,7 @@ public class RaceMap extends AbstractCompositeComponent impleme } } - private Widget createInfoWindowLabelAndValue(String labelName, String value) { + Widget createInfoWindowLabelAndValue(String labelName, String value) { Label valueLabel = new Label(value); valueLabel.setWordWrap(false); return createInfoWindowLabelWithWidget(labelName, valueLabel); @@ -2171,51 +2173,6 @@ public class RaceMap extends AbstractCompositeComponent impleme return vPanel; } - private Widget getInfoWindowContent(ManeuverDTO maneuver) { - SpeedWithBearingDTO before = maneuver.speedWithBearingBefore; - SpeedWithBearingDTO after = maneuver.speedWithBearingAfter; - VerticalPanel vPanel = new VerticalPanel(); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverType(), - ManeuverTypeFormatter.format(maneuver.type, stringMessages))); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.time(), - DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.timePoint))); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.directionChange(), - ((int) Math.round(maneuver.directionChangeInDegrees)) + " " + stringMessages.degreesShort() + " (" - + ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> " - + ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")")); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.speedChange(), - NumberFormat.getDecimalFormat().format(after.speedInKnots - before.speedInKnots) + " " - + stringMessages.knotsUnit() + " (" - + NumberFormat.getDecimalFormat().format(before.speedInKnots) + " " + stringMessages.knotsUnit() - + " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " " - + stringMessages.knotsUnit() + ")")); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.maxTurningRate(), - NumberFormat.getDecimalFormat().format(maneuver.maxTurningRateInDegreesPerSecond) + " " - + stringMessages.degreesPerSecondUnit())); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.avgTurningRate(), - NumberFormat.getDecimalFormat().format(maneuver.avgTurningRateInDegreesPerSecond) + " " - + stringMessages.degreesPerSecondUnit())); - if (maneuver.type != ManeuverType.BEAR_AWAY && maneuver.type != ManeuverType.HEAD_UP) { - vPanel.add(createInfoWindowLabelAndValue(stringMessages.lowestSpeed(), - NumberFormat.getDecimalFormat().format(maneuver.lowestSpeedInKnots) + " " - + stringMessages.knotsUnit())); - } - if (maneuver.maneuverLossInMeters != null) { - vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), - numberFormatOneDecimal.format(maneuver.maneuverLossInMeters) + " " + stringMessages.metersUnit())); - } - if (maneuver.markPassingTimePoint != null) { - vPanel.add( - createInfoWindowLabelAndValue(stringMessages.markPassing(), - DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.markPassingTimePoint) - + (maneuver.markPassingSide == null ? "" - : " (" + (maneuver.markPassingSide == NauticalSide.PORT - ? stringMessages.portSide() : stringMessages.starboardSide()) - + ")"))); - } - return vPanel; - } - private Widget getInfoWindowContent(final WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) { WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); final VerticalPanel vPanel = new VerticalPanel(); @@ -2276,7 +2233,7 @@ public class RaceMap extends AbstractCompositeComponent impleme speedWithBearing = new SpeedWithBearingDTO(0, 0); } vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(), - NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit())); + numberFormatOneDecimal.format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit())); vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort())); if (lastFix.degreesBoatToTheWind != null) { vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(), @@ -2310,24 +2267,7 @@ public class RaceMap extends AbstractCompositeComponent impleme } } }); - sailingService.getManeuvers(race, timeRange, new AsyncCallback>>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */); - } - - @Override - public void onSuccess(Map> result) { - lastManeuverResult = result; - if (maneuverMarkers != null) { - removeAllManeuverMarkers(); - } - if (!(timer.getPlayState() == PlayStates.Playing)) { - showManeuvers(result); - } - } - }); - + maneuverMarkersAndLossIndicators.getAndShowManeuvers(race, timeRange); } } return vPanel; @@ -2370,15 +2310,6 @@ public class RaceMap extends AbstractCompositeComponent impleme douglasMarkers = null; } - protected void removeAllManeuverMarkers() { - if (maneuverMarkers != null) { - for (Marker marker : maneuverMarkers) { - marker.setMap((MapWidget) null); - } - maneuverMarkers = null; - } - } - private void showMarkDouglasPeuckerPoints(Map> gpsFixPointMapForCompetitors) { douglasMarkers = new HashSet(); if (map != null && gpsFixPointMapForCompetitors != null) { @@ -2399,42 +2330,7 @@ public class RaceMap extends AbstractCompositeComponent impleme } } } - - private void showManeuvers(Map> maneuvers) { - maneuverMarkers = new HashSet(); - if (map != null && maneuvers != null) { - Set keySet = maneuvers.keySet(); - Iterator iter = keySet.iterator(); - while (iter.hasNext()) { - CompetitorDTO competitorDTO = iter.next(); - List maneuversForCompetitor = maneuvers.get(competitorDTO); - for (ManeuverDTO maneuver : maneuversForCompetitor) { - if (settings.isShowManeuverType(maneuver.type)) { - createAndAddMarkerOfManeuver(maneuver); - } - } - } - } - } - - private void createAndAddMarkerOfManeuver(ManeuverDTO maneuver) { - LatLng latLng = coordinateSystem.toLatLng(maneuver.position); - Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndDirectionIndicatingColor - .get(new Pair(maneuver.type, ManeuverColor.getManeuverColor(maneuver))); - MarkerOptions options = MarkerOptions.newInstance(); - options.setIcon(maneuverMarker.getIcon_MarkerImage()); - Marker marker = Marker.newInstance(options); - marker.setPosition(latLng); - marker.setTitle(ManeuverTypeFormatter.format(maneuver.type, stringMessages)); - marker.addClickHandler(event -> { - LatLng where = getCoordinateSystem().toLatLng(maneuver.position); - Widget content = getInfoWindowContent(maneuver); - managedInfoWindow.openAtPosition(content, where); - }); - maneuverMarkers.add(marker); - marker.setMap(map); - } - + /** * @param date * the point in time for which to determine the competitor's boat position; approximated by using the fix @@ -2626,20 +2522,18 @@ public class RaceMap extends AbstractCompositeComponent impleme @Override public void updateSettings(RaceMapSettings newSettings) { boolean maneuverTypeSelectionChanged = false; + boolean showManeuverLossChanged = false; boolean requiresRedraw = false; boolean requiresUpdateCoordinateSystem = false; + if (newSettings.isShowManeuverLossVisualization() != settings.isShowManeuverLossVisualization()) { + showManeuverLossChanged = true; + } for (ManeuverType maneuverType : ManeuverType.values()) { if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) { maneuverTypeSelectionChanged = true; } } - if (maneuverTypeSelectionChanged) { - if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) { - removeAllManeuverMarkers(); - showManeuvers(lastManeuverResult); - } - } if (newSettings.isShowDouglasPeuckerPoints() != settings.isShowDouglasPeuckerPoints()) { if (!(timer.getPlayState() == PlayStates.Playing) && lastDouglasPeuckerResult != null && newSettings.isShowDouglasPeuckerPoints()) { removeAllMarkDouglasPeuckerpoints(); @@ -2651,7 +2545,7 @@ public class RaceMap extends AbstractCompositeComponent impleme if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) { requiresRedraw = true; } - if (newSettings.getBuoyZoneRadius() != settings.getBuoyZoneRadius()) { + if (!newSettings.getBuoyZoneRadius().equals(settings.getBuoyZoneRadius())) { requiresRedraw = true; } if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) { @@ -2696,7 +2590,11 @@ public class RaceMap extends AbstractCompositeComponent impleme requiresRedraw = true; } this.settings = newSettings; - + if (maneuverTypeSelectionChanged || showManeuverLossChanged) { + if (timer.getPlayState() != PlayStates.Playing) { + maneuverMarkersAndLossIndicators.updateManeuverMarkersAfterSettingsChanged(); + } + } if (requiresUpdateCoordinateSystem) { updateCoordinateSystemFromSettings(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java index 8404c590b7a..92cd181cb30 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapImageManager.java @@ -25,19 +25,19 @@ public class RaceMapImageManager { /** * An arrow showing the combined wind depending on the wind direction */ - protected ImageTransformer combinedWindIconTransformer; + private final ImageTransformer combinedWindIconTransformer; /** * An arrow showing the true north direction */ - protected ImageTransformer trueNorthIndicatorIconTransformer; + private final ImageTransformer trueNorthIndicatorIconTransformer; /** * An arrow showing the wind provided by a wind sensor on a boat */ - protected ImageTransformer windSensorIconTransformer; + private final ImageTransformer windSensorIconTransformer; - protected Map, Marker> maneuverIconsForTypeAndDirectionIndicatingColor; + private final Map, Marker> maneuverIconsForTypeAndDirectionIndicatingColor; private final RaceMapResources resources; @@ -61,11 +61,11 @@ public class RaceMapImageManager { } Marker icon; for (ManeuverType maneuver : maneuvers) { - icon = createMarker(resources.maneuverMarkerRed()); - maneuverIconsForTypeAndDirectionIndicatingColor + icon = createMarker(getResources().maneuverMarkerRed()); + getManeuverIconsForTypeAndDirectionIndicatingColor() .put(new Util.Pair(maneuver, ManeuverColor.RED), icon); - icon = createMarker(resources.maneuverMarkerGreen()); - maneuverIconsForTypeAndDirectionIndicatingColor + icon = createMarker(getResources().maneuverMarkerGreen()); + getManeuverIconsForTypeAndDirectionIndicatingColor() .put(new Util.Pair(maneuver, ManeuverColor.GREEN), icon); } } @@ -81,19 +81,27 @@ public class RaceMapImageManager { return marker; } - public ImageTransformer getTrueNorthIndicatorIconTransformer() { + ImageTransformer getTrueNorthIndicatorIconTransformer() { return trueNorthIndicatorIconTransformer; } - public ImageTransformer getCombinedWindIconTransformer() { + ImageTransformer getCombinedWindIconTransformer() { return combinedWindIconTransformer; } - public ImageTransformer getWindSensorIconTransformer() { + ImageTransformer getWindSensorIconTransformer() { return windSensorIconTransformer; } - public TextResource getWindFinderLogo() { - return resources.getWindFinderLogo(); + TextResource getWindFinderLogo() { + return getResources().getWindFinderLogo(); + } + + Map, Marker> getManeuverIconsForTypeAndDirectionIndicatingColor() { + return maneuverIconsForTypeAndDirectionIndicatingColor; + } + + private RaceMapResources getResources() { + return resources; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapLifecycle.java index 9eb8a6ab5f5..b5aa864dc75 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapLifecycle.java @@ -53,7 +53,8 @@ public class RaceMapLifecycle implements ComponentLifecycle { settings.isShowOnlySelectedCompetitors(), settings.isShowSelectedCompetitorsInfo(), settings.isShowWindStreamletColors(), settings.isShowWindStreamletOverlay(), settings.isShowSimulationOverlay(), settings.isShowMapControls(), settings.getManeuverTypesToShow(), - settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), settings.getStartCountDownFontSizeScaling()); + settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), + settings.getStartCountDownFontSizeScaling(), settings.isShowManeuverLossVisualization()); } @Override @@ -65,6 +66,7 @@ public class RaceMapLifecycle implements ComponentLifecycle { settings.isShowOnlySelectedCompetitors(), settings.isShowSelectedCompetitorsInfo(), settings.isShowWindStreamletColors(), settings.isShowWindStreamletOverlay(), settings.isShowSimulationOverlay(), settings.isShowMapControls(), settings.getManeuverTypesToShow(), - settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), settings.getStartCountDownFontSizeScaling()); + settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), + settings.getStartCountDownFontSizeScaling(), settings.isShowManeuverLossVisualization()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java index ecf6a13ce1e..640af2904ad 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java @@ -60,6 +60,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { private BooleanSetting showMapControls; + private BooleanSetting showManeuverLossVisualization; + /** * If true, all map contents will be transformed to a water-only environment, rotating all directions / * bearings / headings so that an assumed average wind direction for the race is coming from the top of the map @@ -97,6 +99,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { showDouglasPeuckerPoints = new BooleanSetting("showDouglasPeuckerPoints", this, false); showEstimatedDuration = new BooleanSetting("showEstimatedDuration", this, false); startCountDownFontSizeScaling = new DoubleSetting("startCountDownFontSizeScaling", this, 1.0); + showManeuverLossVisualization = new BooleanSetting("showManeuverLossVisualization", this, false); } public RaceMapSettings() { @@ -107,7 +110,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { Distance buoyZoneRadius, Boolean showOnlySelectedCompetitors, Boolean showSelectedCompetitorsInfo, Boolean showWindStreamletColors, Boolean showWindStreamletOverlay, Boolean showSimulationOverlay, Boolean showMapControls, Collection maneuverTypesToShow, Boolean showDouglasPeuckerPoints, - Boolean showEstimatedDuration, Double startCountDownFontSizeScaling) { + Boolean showEstimatedDuration, Double startCountDownFontSizeScaling, Boolean showManeuverLossVisualization) { this.zoomSettings.init(zoomSettings); this.helpLinesSettings.init(helpLinesSettings); this.transparentHoverlines.setValue(transparentHoverlines); @@ -125,6 +128,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { this.showDouglasPeuckerPoints.setValue(showDouglasPeuckerPoints); this.showEstimatedDuration.setValue(showEstimatedDuration); this.startCountDownFontSizeScaling.setValue(startCountDownFontSizeScaling); + this.showManeuverLossVisualization.setValue(showManeuverLossVisualization); } public static RaceMapSettings getDefaultWithShowMapControls(boolean showMapControlls) { @@ -155,7 +159,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { /* maneuverTypesToShow */ getDefaultManeuvers(), /* showDouglasPeuckerPoints */ false, /* showEstimatedDuration*/ false, - /* startCountDownFontSizeScaling */ 1.0); + /* startCountDownFontSizeScaling */ 1.0, + /* showManeuverLossVisualization */ false); } private static Set createHelpLineSettings(boolean showCourseGeometry) { @@ -197,7 +202,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { /* maneuverTypesToShow */ settings.getManeuverTypesToShow(), /* showDouglasPeuckerPoints */ settings.isShowDouglasPeuckerPoints(), /* showEstimatedDuration */ settings.isShowEstimatedDuration(), - /* startCountDownFontSizeScaling */ settings.getStartCountDownFontSizeScaling()); + /* startCountDownFontSizeScaling */ settings.getStartCountDownFontSizeScaling(), + /* showManeuverLossLineVisualization */ settings.isShowManeuverLossVisualization()); } /** @@ -271,6 +277,10 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { return startCountDownFontSizeScaling.getValue(); } + public boolean isShowManeuverLossVisualization() { + return showManeuverLossVisualization.getValue(); + } + public static RaceMapSettings createSettingsWithNewBuoyZoneRadius(RaceMapSettings settings, Distance newDefaultBuoyZoneRadius) { final RaceMapSettings newRaceMapSettings = new RaceMapSettings( settings.getZoomSettings(), settings.getHelpLinesSettings(), @@ -284,7 +294,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings { settings.isShowSimulationOverlay(), settings.isShowMapControls(), settings.getManeuverTypesToShow(), settings.isShowDouglasPeuckerPoints(),settings.isShowEstimatedDuration(), - settings.getStartCountDownFontSizeScaling()); + settings.getStartCountDownFontSizeScaling(), + settings.isShowManeuverLossVisualization()); return newRaceMapSettings; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java index 0ee72988649..1e376565dc2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java @@ -54,6 +54,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent maneuverCellTable; @@ -158,86 +151,33 @@ public class ManeuverTablePanel extends AbstractCompositeComponent createSortableAbsMinMaxColumn( + Function extractor, String title, String unit) { + return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ true); + } + + /** + * Creates a sortable column with signed values. + */ private SortableColumn createSortableMinMaxColumn( Function extractor, String title, String unit) { - final SortableColumn col = new AbstractSortableColumnWithMinMax( - new TextCell(), SortingOrder.ASCENDING) { - final InvertibleComparator comparatorWithAbs = new InvertibleComparatorAdapter() { - @Override - public int compare(ManeuverTableData o1, ManeuverTableData o2) { - Double o1v = extractor.apply(o1); - Double o2v = extractor.apply(o2); - if (o1v == null && o2v == null) { - return 0; - } - if (o1v == null && o2v != null) { - return -1; - } - if (o1v != null && o2v == null) { - return 1; - } - return Double.compare(Math.abs(o1v), Math.abs(o2v)); - } - }; - final HasStringAndDoubleValue dataProvider = new HasStringAndDoubleValue() { - @Override - public String getStringValueToRender(ManeuverTableData row) { - Double value = extractor.apply(row); - if (value == null) { - return null; - } - return towDigitAccuracy.format(value); - } - - @Override - public Double getDoubleValue(ManeuverTableData row) { - Double value = extractor.apply(row); - return value == null ? null : Math.abs(value); - } - }; - - final MinMaxRenderer renderer = new MinMaxRenderer(dataProvider, comparatorWithAbs); - - @Override - public InvertibleComparator getComparator() { - return comparatorWithAbs; - } - - @Override - public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) { - renderer.render(context, object, title, sb); - } - - @Override - public Header getHeader() { - return new TextHeader(title + " [" + unit + "]"); - } - - @Override - public String getValue(ManeuverTableData object) { - return dataProvider.getStringValueToRender(object); - } - - @Override - public void updateMinMax() { - renderer.updateMinMax(maneuverCellTable.getDataProvider().getList()); - } - }; - col.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); - return col; + return new SortableMinMaxColumn(extractor, title, unit, maneuverCellTable.getDataProvider(), /* absolute */ false); } private SortableColumn createManeuverTypeColumn() { return new SortableColumn(new TextCell(), SortingOrder.ASCENDING) { - @Override public InvertibleComparator getComparator() { return new InvertibleComparatorAdapter() { @@ -267,7 +207,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent col = new SortableColumn( new DateCell(DateTimeFormat.getFormat(PredefinedFormat.TIME_LONG)), SortingOrder.ASCENDING) { @Override @@ -295,7 +234,6 @@ public class ManeuverTablePanel extends AbstractCompositeComponent column = new SortableColumn( new AbstractCell() { @Override @@ -330,9 +268,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent(new TextCell(), SortingOrder.ASCENDING) { - @Override public InvertibleComparator getComparator() { return comparator; @@ -382,7 +318,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent> cachedData = competitorDataProvider.getCachedData(); for (final Entry> entry : cachedData.entrySet()) { for (ManeuverDTO maneuver : entry.getValue()) { - if (settings.getSelectedManeuverTypes().contains(maneuver.type)) { + if (settings.getSelectedManeuverTypes().contains(maneuver.getType())) { data.add(new ManeuverTableData(entry.getKey(), maneuver)); } } @@ -497,7 +433,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent m.timePoint, LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true); + super(timeRangeProvider, timer, m -> m.getTimePoint(), LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true); this.asyncActionsExecutor = asyncActionsExecutor; this.raceIdentifier = raceIdentifier; this.sailingService = sailingService; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableSettings.java index 81f63aa188d..c1f69e37536 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/ManeuverTableSettings.java @@ -13,11 +13,10 @@ public class ManeuverTableSettings extends AbstractGenericSerializableSettings { private EnumSetSetting selectedManeuverTypes; - private Iterable defaultTypes = Arrays.asList(ManeuverType.values()); - @Override protected void addChildSettings() { - selectedManeuverTypes = new EnumSetSetting<>("selectedManeuverTypes", this, defaultTypes, + selectedManeuverTypes = new EnumSetSetting<>("selectedManeuverTypes", this, + Arrays.asList(ManeuverType.TACK, ManeuverType.JIBE, ManeuverType.PENALTY_CIRCLE), ManeuverType::valueOf); } @@ -25,9 +24,6 @@ public class ManeuverTableSettings extends AbstractGenericSerializableSettings { * The default settings */ public ManeuverTableSettings() { - selectedManeuverTypes.addValue(ManeuverType.TACK); - selectedManeuverTypes.addValue(ManeuverType.JIBE); - selectedManeuverTypes.addValue(ManeuverType.PENALTY_CIRCLE); } public ManeuverTableSettings(Set selectedManeuverTypes) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java new file mode 100755 index 00000000000..21aa9864c17 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/maneuver/SortableMinMaxColumn.java @@ -0,0 +1,94 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap.maneuver; + +import java.util.Comparator; +import java.util.function.Function; + +import com.google.gwt.cell.client.Cell.Context; +import com.google.gwt.cell.client.TextCell; +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.Header; +import com.google.gwt.user.cellview.client.TextHeader; +import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.view.client.ListDataProvider; +import com.sap.sailing.domain.common.InvertibleComparator; +import com.sap.sailing.domain.common.SortingOrder; +import com.sap.sailing.domain.common.impl.InvertibleComparatorAdapter; +import com.sap.sailing.gwt.ui.client.NumberFormatterFactory; +import com.sap.sailing.gwt.ui.client.shared.controls.AbstractSortableColumnWithMinMax; +import com.sap.sailing.gwt.ui.leaderboard.HasStringAndDoubleValue; +import com.sap.sailing.gwt.ui.leaderboard.MinMaxRenderer; + +public class SortableMinMaxColumn extends AbstractSortableColumnWithMinMax { + private final static NumberFormat TWO_DIGIT_ACCURACY = NumberFormatterFactory.getDecimalFormat(2); + private final String title; + private final String unit; + + final InvertibleComparator comparator; + + final HasStringAndDoubleValue dataProvider; + + final MinMaxRenderer renderer; + + final ListDataProvider maneuverTableListDataProvider; + + public SortableMinMaxColumn(final Function extractor, String title, String unit, + ListDataProvider maneuverTableListDataProvider, boolean absolute) { + super(new TextCell(), SortingOrder.ASCENDING); + this.title = title; + this.unit = unit; + this.maneuverTableListDataProvider = maneuverTableListDataProvider; + this.comparator = new InvertibleComparatorAdapter() { + @Override + public int compare(ManeuverTableData o1, ManeuverTableData o2) { + Double o1v = extractor.apply(o1); + Double o2v = extractor.apply(o2); + return Comparator.nullsFirst((Double v1, Double v2)->Double.compare(absolute?Math.abs(v1):v1, absolute?Math.abs(v2):v2)).compare(o1v, o2v); + } + }; + this.dataProvider = new HasStringAndDoubleValue() { + @Override + public String getStringValueToRender(ManeuverTableData row) { + Double value = extractor.apply(row); + if (value == null) { + return null; + } + return TWO_DIGIT_ACCURACY.format(value); + } + + @Override + public Double getDoubleValue(ManeuverTableData row) { + Double value = extractor.apply(row); + return value == null ? null : absolute ? Math.abs(value) : value; + } + }; + + this.renderer = new MinMaxRenderer(dataProvider, comparator); + this.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + } + + @Override + public InvertibleComparator getComparator() { + return comparator; + } + + @Override + public void render(Context context, ManeuverTableData object, SafeHtmlBuilder sb) { + renderer.render(context, object, title, sb); + } + + @Override + public Header getHeader() { + return new TextHeader(title + " [" + unit + "]"); + } + + @Override + public String getValue(ManeuverTableData object) { + return dataProvider.getStringValueToRender(object); + } + + @Override + public void updateMinMax() { + renderer.updateMinMax(maneuverTableListDataProvider.getList()); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java index b46b018cfbf..31daecf897d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java @@ -69,16 +69,13 @@ public class DataMiningEntryPoint extends AbstractSailingEntryPoint { @Override public Widget get() { DataMiningSettingsControl settingsControl = new AnchorDataMiningSettingsControl(null, null); - ResultsPresenter resultsPresenter = new TabbedSailingResultsPresenter(/* parent */ null, - /* context */ null, /* delegate drillDownCallback */ groupKey -> { - queryDefinitionProviderWithControls.drillDown(groupKey, - /* onSuccessCallback */ () -> queryRunner.runQuery()); - }, getStringMessages()); + ResultsPresenter resultsPresenter = new TabbedSailingResultsPresenter(/*parent*/ null, /*context*/ null, + /*drillDownCallback*/ groupKey -> queryDefinitionProviderWithControls.drillDown(groupKey, queryRunner::runQuery), + getStringMessages()); DockLayoutPanel selectionDockPanel = new DockLayoutPanel(Unit.PX); queryDefinitionProviderWithControls = new QueryDefinitionProviderWithControls(null, null, session, dataMiningService, DataMiningEntryPoint.this, settingsControl, settingsManager, resultsPresenter); - queryDefinitionProviderWithControls.getEntryWidget().addStyleName("dataMiningPanel"); selectionDockPanel.add(queryDefinitionProviderWithControls.getEntryWidget()); queryRunner = new SimpleQueryRunner(null, null, session, dataMiningService, DataMiningEntryPoint.this, queryDefinitionProviderWithControls, resultsPresenter); @@ -89,7 +86,7 @@ public class DataMiningEntryPoint extends AbstractSailingEntryPoint { * automatic execution of queries. Re-enable this, when this functionality is desired again. */ // settingsControl.addSettingsComponent(queryRunner); - SplitLayoutPanel splitPanel = new SplitLayoutPanel(15); + SplitLayoutPanel splitPanel = new SplitLayoutPanel(10); splitPanel.addSouth(resultsPresenter.getEntryWidget(), 350); splitPanel.add(selectionDockPanel); return splitPanel; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java index e72a1c95a2d..5d0cd95c08c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java @@ -19,11 +19,11 @@ import com.google.gwt.user.client.ui.SimpleLayoutPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.datamining.shared.ManeuverSpeedDetailsAggregation; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.charts.ChartToCsvExporter; import com.sap.sse.common.settings.Settings; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; +import com.sap.sse.datamining.ui.client.ChartToCsvExporter; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.settings.ComponentContext; @@ -80,7 +80,7 @@ public class ManeuverSpeedDetailsResultsPresenter extends AbstractSailingResults addControl(chartConfigPanel); - ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages); + ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard()); Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(), new ClickHandler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/NumberPairResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/NumberPairResultsPresenter.java index 0ead4ea8645..ccfe0118e7e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/NumberPairResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/NumberPairResultsPresenter.java @@ -86,20 +86,16 @@ public class NumberPairResultsPresenter extends AbstractSailingResultsPresenter< protected void internalShowResults(QueryResultDTO res) { result = res; createAndAddSeriesToChart(); - for (Entry resultEntry : result.getResults().entrySet()) { @SuppressWarnings("unchecked") PairWithStats value = (PairWithStats) resultEntry.getValue(); - - if(value.getIndividualPairs() != null) { - for(Pair pair : value.getIndividualPairs()) { + if (value.getIndividualPairs() != null) { + for (Pair pair : value.getIndividualPairs()) { createAndAddPoint(resultEntry.getKey(), pair.getA(), pair.getB()); } - } - else { + } else { createAndAddPoint(resultEntry.getKey(), value.getAverage().getA(), value.getAverage().getB()); } - } chart.getXAxis().setAxisTitleText(result.getResultSignifier()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/TabbedSailingResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/TabbedSailingResultsPresenter.java index 69efcd24436..88b9400075d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/TabbedSailingResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/TabbedSailingResultsPresenter.java @@ -7,28 +7,28 @@ import com.sap.sailing.gwt.ui.polarmining.PolarResultsPresenter; import com.sap.sailing.polars.datamining.shared.PolarAggregation; import com.sap.sailing.polars.datamining.shared.PolarBackendData; import com.sap.sse.datamining.shared.data.PairWithStats; -import com.sap.sse.datamining.ui.client.presentation.AbstractTabbedResultsPresenter; +import com.sap.sse.datamining.ui.client.presentation.TabbedResultsPresenter; import com.sap.sse.datamining.ui.client.presentation.ResultsChart.DrillDownCallback; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.settings.ComponentContext; -public class TabbedSailingResultsPresenter extends AbstractTabbedResultsPresenter { +public class TabbedSailingResultsPresenter extends TabbedResultsPresenter { public TabbedSailingResultsPresenter(Component parent, ComponentContext context, DrillDownCallback drillDownCallback, StringMessages stringMessages) { super(parent, context, drillDownCallback); - registerResultPresenter(PolarAggregation.class, + registerResultsPresenter(PolarAggregation.class, new PolarResultsPresenter(TabbedSailingResultsPresenter.this, getComponentContext(), stringMessages)); - registerResultPresenter(PolarBackendData.class, new PolarBackendResultsPresenter( + registerResultsPresenter(PolarBackendData.class, new PolarBackendResultsPresenter( TabbedSailingResultsPresenter.this, getComponentContext(), stringMessages)); - registerResultPresenter(ManeuverSpeedDetailsAggregation.class, new ManeuverSpeedDetailsResultsPresenter( + registerResultsPresenter(ManeuverSpeedDetailsAggregation.class, new ManeuverSpeedDetailsResultsPresenter( TabbedSailingResultsPresenter.this, getComponentContext(), stringMessages)); - registerResultPresenter(PairWithStats.class, new NumberPairResultsPresenter(TabbedSailingResultsPresenter.this, + registerResultsPresenter(PairWithStats.class, new NumberPairResultsPresenter(TabbedSailingResultsPresenter.this, getComponentContext(), stringMessages)); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorColumnBase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorColumnBase.java index 2b18cbfbd82..d73267c3c89 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorColumnBase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/CompetitorColumnBase.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.leaderboard; import java.util.Comparator; import com.google.gwt.cell.client.AbstractSafeHtmlCell; +import com.google.gwt.cell.client.Cell.Context; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; @@ -17,12 +18,13 @@ import com.sap.sse.common.InvertibleComparator; import com.sap.sse.common.impl.InvertibleComparatorAdapter; public class CompetitorColumnBase { + private final LeaderboardFetcher leaderboardFetcher; - protected final StringMessages stringMessages; + private final StringMessages stringMessages; private final CompetitorFetcher competitorFetcher; - public CompetitorColumnBase(LeaderboardFetcher leaderboardFetcher, StringMessages stringMessages, CompetitorFetcher competitorFetcher) { - super(); + public CompetitorColumnBase(LeaderboardFetcher leaderboardFetcher, StringMessages stringMessages, + CompetitorFetcher competitorFetcher) { this.leaderboardFetcher = leaderboardFetcher; this.stringMessages = stringMessages; this.competitorFetcher = competitorFetcher; @@ -36,11 +38,11 @@ public class CompetitorColumnBase { return new AbstractSafeHtmlCell(new AbstractSafeHtmlRenderer() { @Override public SafeHtml render(T row) { - return new SafeHtmlBuilder().appendEscaped(leaderboard.getDisplayName(competitorFetcher.getCompetitor(row))).toSafeHtml(); + return SafeHtmlUtils.fromString(leaderboard.getDisplayName(competitorFetcher.getCompetitor(row))); } }) { @Override - protected void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml data, SafeHtmlBuilder sb) { + protected void render(Context context, SafeHtml data, SafeHtmlBuilder sb) { sb.append(data); } }; @@ -50,8 +52,10 @@ public class CompetitorColumnBase { return new InvertibleComparatorAdapter() { @Override public int compare(T o1, T o2) { - return Comparator.nullsLast((c1, c2)->new NaturalComparator(/* caseSensitive */ false).compare(c1.getShortName(), c2.getShortName())).compare( - competitorFetcher.getCompetitor(o1), competitorFetcher.getCompetitor(o2)); + return Comparator + . nullsLast((c1, c2) -> new NaturalComparator(/* caseSensitive */ false) + .compare(c1.getShortName(), c2.getShortName())) + .compare(competitorFetcher.getCompetitor(o1), competitorFetcher.getCompetitor(o2)); } }; } @@ -61,10 +65,8 @@ public class CompetitorColumnBase { stringMessages.competitorColumnTooltip()); } - public void render(T t, String competitorColorBarStyle, SafeHtmlBuilder sb) { - sb.appendHtmlConstant("

      "); - sb.appendEscaped(getLeaderboard().getDisplayName(competitorFetcher.getCompetitor(t))); - sb.appendHtmlConstant("
      "); + public void render(Context context, T row, SafeHtmlBuilder sb) { + sb.appendEscaped(getLeaderboard().getDisplayName(competitorFetcher.getCompetitor(row))); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 6ed205f16b9..9133d11f089 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -1,6 +1,7 @@ package com.sap.sailing.gwt.ui.leaderboard; import java.util.UUID; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; @@ -17,6 +18,7 @@ import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardContextDefinition; import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardPerspectiveLifecycle; import com.sap.sailing.gwt.settings.client.leaderboard.LeaderboardPerspectiveOwnSettings; @@ -25,6 +27,8 @@ import com.sap.sailing.gwt.settings.client.leaderboard.MultiCompetitorLeaderboar import com.sap.sailing.gwt.settings.client.leaderboard.MultiCompetitorLeaderboardChartSettings; import com.sap.sailing.gwt.settings.client.utils.StoredSettingsLocationFactory; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.SailingServiceHelper; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; @@ -40,12 +44,13 @@ import com.sap.sse.gwt.settings.SettingsToUrlSerializer; import com.sap.sse.security.ui.settings.ComponentContextWithSettingsStorage; import com.sap.sse.security.ui.settings.StoredSettingsLocation; -public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { +public class LeaderboardEntryPoint extends AbstractSailingEntryPoint implements ProvidesLeaderboardRouting { public static final long DEFAULT_REFRESH_INTERVAL_MILLIS = 3000l; private static final Logger logger = Logger.getLogger(LeaderboardEntryPoint.class.getName()); private StringMessages stringmessages = StringMessages.INSTANCE; + private UUID eventId; private String leaderboardName; private String leaderboardGroupName; private AbstractLeaderboardDTO leaderboardDTO; @@ -58,7 +63,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { leaderboardContextDefinition = new SettingsToUrlSerializer() .deserializeFromCurrentLocation(new LeaderboardContextDefinition()); - final UUID eventId = leaderboardContextDefinition.getEventId(); + eventId = leaderboardContextDefinition.getEventId(); leaderboardName = leaderboardContextDefinition.getLeaderboardName(); leaderboardGroupName = leaderboardContextDefinition.getLeaderboardGroupName(); @@ -67,9 +72,8 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { if (eventId == null) { checkLeaderboardNameAndCreateUI(); // use null-initialized event field } else { - // TODO it seems we do not really need the EventDTO. What's the intention of loading it? Should we - // visualize some information in the header? - sailingService.getEventById(eventId, /* withStatisticalData */false, + // TODO it seems we do not really need the EventDTO. What's the intention of loading it? Should we visualize some information in the header? + getSailingService().getEventById(eventId, /* withStatisticalData */false, new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { @@ -78,7 +82,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { @Override public void onSuccess(EventDTO result) { - if (result != null) { + if(result != null) { leaderboardDTO = result.getLeaderboardByName(leaderboardName); } checkLeaderboardNameAndCreateUI(); @@ -89,27 +93,27 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard())); } } - + private void checkLeaderboardNameAndCreateUI() { - if (leaderboardDTO == null) { - sailingService.getLeaderboard(leaderboardName, - new MarkedAsyncCallback(new AsyncCallback() { - @Override - public void onSuccess(StrippedLeaderboardDTO leaderboardDTO) { - if (leaderboardDTO != null) { - LeaderboardEntryPoint.this.leaderboardDTO = leaderboardDTO; - Window.setTitle(leaderboardName); - loadSettingsAndCreateUI(); - } else { - RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard())); + if(leaderboardDTO == null) { + getSailingService().getLeaderboard(leaderboardName, + new MarkedAsyncCallback(new AsyncCallback() { + @Override + public void onSuccess(StrippedLeaderboardDTO leaderboardDTO) { + if (leaderboardDTO != null) { + LeaderboardEntryPoint.this.leaderboardDTO = leaderboardDTO; + Window.setTitle(leaderboardName); + loadSettingsAndCreateUI(); + } else { + RootPanel.get().add(new Label(getStringMessages().noSuchLeaderboard())); + } } - } - @Override - public void onFailure(Throwable t) { - reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); - } - })); + @Override + public void onFailure(Throwable t) { + reportError("Error trying to obtain list of leaderboard names: " + t.getMessage()); + } + })); } else { loadSettingsAndCreateUI(); } @@ -118,13 +122,13 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { private void loadSettingsAndCreateUI() { long delayBetweenAutoAdvancesInMilliseconds = DEFAULT_REFRESH_INTERVAL_MILLIS; final Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); - + // make a single live request as the default but don't continue to play by default final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory .createStoredSettingsLocatorForLeaderboard(leaderboardContextDefinition); - sailingService.getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onFailure(Throwable caught) { logger.log(Level.SEVERE, "Could not load detailtypes", caught); @@ -132,6 +136,13 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { @Override public void onSuccess(Iterable result) { + final Function sailingServiceFactory = leaderboardName -> SailingServiceHelper + .createSailingServiceInstance(new ProvidesLeaderboardRouting() { + @Override + public String getLeaderboardName() { + return leaderboardName; + } + }); if (leaderboardDTO.type.isMetaLeaderboard()) { // overall MetaLeaderboardPerspectiveLifecycle rootComponentLifeCycle = new MetaLeaderboardPerspectiveLifecycle( @@ -144,10 +155,9 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { public void onSuccess( PerspectiveCompositeSettings defaultSettings) { configureWithSettings(defaultSettings, timer); - final MetaLeaderboardViewer leaderboardViewer = new MetaLeaderboardViewer( null, context, rootComponentLifeCycle, defaultSettings, - sailingService, new AsyncActionsExecutor(), timer, null, + sailingServiceFactory, new AsyncActionsExecutor(), timer, null, leaderboardGroupName, leaderboardName, LeaderboardEntryPoint.this, getStringMessages(), getActualChartDetailType(defaultSettings), result); @@ -174,8 +184,8 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { @Override public void onSuccess( PerspectiveCompositeSettings defaultSettings) { - sailingService.getAvailableDetailTypesForLeaderboard(leaderboardName, - new AsyncCallback>() { + getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, + null, new AsyncCallback>() { @Override public void onFailure(Throwable caught) { @@ -189,7 +199,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { configureWithSettings(defaultSettings, timer); final MultiRaceLeaderboardViewer leaderboardViewer = new MultiRaceLeaderboardViewer( null, context, rootComponentLifeCycle, - defaultSettings, sailingService, + defaultSettings, sailingServiceFactory, new AsyncActionsExecutor(), timer, leaderboardGroupName, leaderboardName, LeaderboardEntryPoint.this, getStringMessages(), @@ -212,25 +222,26 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { } } }); + } - + private DetailType getActualChartDetailType( PerspectiveCompositeSettings settings) { MultiCompetitorLeaderboardChartSettings chartSettings = settings .findSettingsByComponentId(MultiCompetitorLeaderboardChartLifecycle.ID); DetailType chartDetailType = chartSettings == null ? null : chartSettings.getDetailType(); - + if (chartDetailType == DetailType.REGATTA_NET_POINTS_SUM) { return chartDetailType; } return MultiCompetitorLeaderboardChartSettings.getDefaultDetailType(leaderboardDTO.type.isMetaLeaderboard()); } - + private void createUi(Widget leaderboardViewer, PerspectiveCompositeSettings settings, Timer timer, LeaderboardContextDefinition leaderboardContextSettings) { LeaderboardPerspectiveOwnSettings ownSettings = settings.getPerspectiveOwnSettings(); - + DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(mainPanel); if (!ownSettings.isEmbedded()) { @@ -258,11 +269,15 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { + ");-o-transform-origin: 0 0;-webkit-transform: scale(" + zoomTo + ");-webkit-transform-origin: 0 0;"); } - + if (perspectiveOwnSettings.isLifePlay()) { timer.setPlayMode(PlayModes.Live); // the leaderboard, viewed via the entry point, goes "live" and "playing" // if an auto-refresh } } - + + @Override + public String getLeaderboardName() { + return leaderboardName; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 6cf682dc9af..a5c5713376f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import com.google.gwt.cell.client.AbstractSafeHtmlCell; @@ -339,7 +340,7 @@ public abstract class LeaderboardPanel extends A private Label scoreCorrectionCommentLabel; private Label liveRaceLabel; - protected boolean isEmbedded; + private final boolean isEmbedded; private ImageResource pauseIcon; private ImageResource playIcon; @@ -778,7 +779,7 @@ public abstract class LeaderboardPanel extends A protected abstract void setDefaultRaceColumnSelection(LS settings); - private void setValuesWithReferenceOrder(Iterable valuesToSet, List referenceOrder, + private void setValuesWithReferenceOrder(Iterable valuesToSet, Collection referenceOrder, List collectionToSetValuesTo) { collectionToSetValuesTo.clear(); collectionToSetValuesTo.addAll(referenceOrder); @@ -898,30 +899,9 @@ public abstract class LeaderboardPanel extends A @Override public void render(Context context, T object, SafeHtmlBuilder sb) { - CompetitorDTO competitor = competitorFetcher.getCompetitor(object); - final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); - final String flagImageURL = competitor.getFlagImageURL(); - boolean boatColorShown = renderBoatColorIfNecessary(competitorFetcher.getCompetitor(object), sb); - if (isShowCompetitorNationality || flagImageURL == null || flagImageURL.isEmpty()) { - final ImageResource nationalityFlagImageResource; - if (twoLetterIsoCountryCode == null || twoLetterIsoCountryCode.isEmpty()) { - nationalityFlagImageResource = flagImageResolver.getEmptyFlagImageResource(); - } else { - nationalityFlagImageResource = flagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); - } - if (nationalityFlagImageResource != null) { - style.renderNationalityFlag(nationalityFlagImageResource, sb); - sb.appendHtmlConstant(" "); - } - } - if (flagImageURL != null && !flagImageURL.isEmpty()) { - style.renderFlagImage(flagImageURL, sb, competitor); - sb.appendHtmlConstant(" "); - } - sb.appendEscaped(competitor.getShortInfo()); - if (boatColorShown) { - sb.appendHtmlConstant("
    6. "); - } + final CompetitorDTO competitor = competitorFetcher.getCompetitor(object); + LeaderboardPanel.this.renderCompetitorText(competitor, isShowCompetitorShortName(), + !isShowCompetitorFullName(), sb, builder -> builder.appendEscaped(competitor.getShortInfo())); } @Override @@ -936,11 +916,9 @@ public abstract class LeaderboardPanel extends A */ private class BoatInfoColumn extends LeaderboardSortableColumnWithMinMax { private final BoatFetcher boatFetcher; - private final CompetitorFetcher competitorFetcher; - public BoatInfoColumn(CompetitorFetcher competitorFetcher, BoatFetcher boatFetcher, LeaderBoardStyle style) { + public BoatInfoColumn(BoatFetcher boatFetcher, LeaderBoardStyle style) { super(new TextCell(), SortingOrder.ASCENDING, LeaderboardPanel.this); - this.competitorFetcher = competitorFetcher; this.boatFetcher = boatFetcher; // This style is adding to avoid contained images CSS property "max-width: 100%", which could cause // an overflow to the next column (see https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3537) @@ -956,12 +934,7 @@ public abstract class LeaderboardPanel extends A @Override public void render(Context context, T object, SafeHtmlBuilder sb) { BoatDTO boat = boatFetcher.getBoat(object); - CompetitorDTO competitor = competitorFetcher.getCompetitor(object); - boolean boatColorShown = renderBoatColorIfNecessary(competitor, sb); sb.appendEscaped(getShortInfo(boat)); - if (boatColorShown) { - sb.appendHtmlConstant("
      "); - } } private String getShortInfo(BoatDTO boat) { @@ -1179,8 +1152,6 @@ public abstract class LeaderboardPanel extends A } } - public abstract boolean renderBoatColorIfNecessary(CompetitorDTO competitor, SafeHtmlBuilder sb); - private class TextRaceColumn extends RaceColumn implements RaceNameProvider { /** * Remembers the leg columns; null-padded, if {@link #getLegColumn(int)} asks for a column index @@ -3185,9 +3156,8 @@ public abstract class LeaderboardPanel extends A if (getLeaderboardTable().getColumnCount() <= columnIndexWhereToInsertTheNextColumn || !(getLeaderboardTable() .getColumn(columnIndexWhereToInsertTheNextColumn) instanceof LeaderboardPanel.BoatInfoColumn)) { - CompetitorFetcher competitorFetcher = (LeaderboardRowDTO row) -> row.competitor; BoatFetcher boatFetcher = (LeaderboardRowDTO row) -> row.boat; - BoatInfoColumn boatInfoColumn = new BoatInfoColumn(competitorFetcher, boatFetcher, style); + BoatInfoColumn boatInfoColumn = new BoatInfoColumn(boatFetcher, style); insertColumn(columnIndexWhereToInsertTheNextColumn, boatInfoColumn); } columnIndexWhereToInsertTheNextColumn++; @@ -3586,17 +3556,43 @@ public abstract class LeaderboardPanel extends A @Override public void render(Context context, LeaderboardRowDTO object, SafeHtmlBuilder sb) { - String competitorColor = getCompetitorColor(object.competitor); - String competitorColorBarStyle; - if (LeaderboardPanel.this.isEmbedded && competitorColor != null) { - competitorColorBarStyle = "style=\"border-bottom: 2px solid " + competitorColor + ";\""; - } else { - competitorColorBarStyle = "style=\"border: none;\""; - } - base.render(object, competitorColorBarStyle, sb); + LeaderboardPanel.this.renderCompetitorText(object.competitor, !isShowCompetitorShortName(), + isShowCompetitorFullName(), sb, builder -> base.render(context, object, builder)); } } - public abstract String getCompetitorColor(CompetitorDTO competitor); + protected abstract String getCompetitorColor(CompetitorDTO competitor); + + private void renderCompetitorText(final CompetitorDTO competitor, final boolean withFlags, final boolean withColor, + final SafeHtmlBuilder sb, final Consumer textRenderer) { + final String competitorColor = getCompetitorColor(competitor); + final boolean showColor = withColor && competitorColor != null; + final String divStyle = showColor ? style.determineBoatColorDivStyle(competitorColor) : "border: none;"; + sb.appendHtmlConstant("
      "); + + if (withFlags) { + final String flagImageURL = competitor.getFlagImageURL(); + if (isShowCompetitorNationality || flagImageURL == null || flagImageURL.isEmpty()) { + final String twoLetterIsoCountryCode = competitor.getTwoLetterIsoCountryCode(); + final ImageResource nationalityFlagImageResource; + if (twoLetterIsoCountryCode == null || twoLetterIsoCountryCode.isEmpty()) { + nationalityFlagImageResource = flagImageResolver.getEmptyFlagImageResource(); + } else { + nationalityFlagImageResource = flagImageResolver.getFlagImageResource(twoLetterIsoCountryCode); + } + if (nationalityFlagImageResource != null) { + style.renderNationalityFlag(nationalityFlagImageResource, sb); + sb.appendHtmlConstant(" "); + } + } + if (flagImageURL != null && !flagImageURL.isEmpty()) { + style.renderFlagImage(flagImageURL, sb, competitor); + sb.appendHtmlConstant(" "); + } + } + + textRenderer.accept(sb); + sb.appendHtmlConstant("
      "); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java index 7a14b373bec..0d43b6594fa 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.ui.leaderboard; +import java.util.function.Function; + import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; @@ -37,12 +39,12 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer> componentContext, MetaLeaderboardPerspectiveLifecycle lifecycle, PerspectiveCompositeSettings settings, - SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + Function sailingServiceFactory, AsyncActionsExecutor asyncActionsExecutor, Timer timer, String preselectedLeaderboardName, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, DetailType chartDetailType, Iterable availableDetailTypes) { this(parent, componentContext, lifecycle, settings, new CompetitorSelectionModel(/* hasMultiSelection */true), - sailingService, asyncActionsExecutor, timer, + sailingServiceFactory, asyncActionsExecutor, timer, preselectedLeaderboardName, leaderboardGroupName, metaLeaderboardName, errorReporter, stringMessages, chartDetailType, availableDetailTypes); } @@ -51,7 +53,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer> componentContext, MetaLeaderboardPerspectiveLifecycle lifecycle, PerspectiveCompositeSettings settings, - CompetitorSelectionModel competitorSelectionModel, SailingServiceAsync sailingService, + CompetitorSelectionModel competitorSelectionModel, Function sailingServiceFactory, AsyncActionsExecutor asyncActionsExecutor, Timer timer, String preselectedLeaderboardName, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, @@ -59,7 +61,8 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { protected static final String BACKGROUND_BAR_STYLE_GOOD = "minMaxBackgroundBarGood"; private final HasStringAndDoubleValue valueProvider; + /** used to determine minimum and maximum values for the rendered bars.*/ private final Comparator comparator; private Double minimumValue; private Double maximumValue; @@ -119,7 +120,8 @@ public class MinMaxRenderer { * @param row * The row to get the percentage for. */ - protected int getPercentage(T row) { + + protected int getPercentage(T row) { int percentage = 0; Double value = valueProvider.getDoubleValue(row); if (value != null) { @@ -128,10 +130,8 @@ public class MinMaxRenderer { percentage = (int) (minBarLength + (100. - minBarLength) * (value - getMinimumDouble()) / (getMaximumDouble() - getMinimumDouble())); } - - } + } return percentage; - } private Double getMinimumDouble() { @@ -149,23 +149,24 @@ public class MinMaxRenderer { * The values of {@link LeaderboardRowDTO}s to determine the minimum and maximum values for. */ public void updateMinMax(Iterable displayedLeaderboardRowsProvider) { - T minimumRow = null; - T maximumRow = null; + T minimumOrderRow = null; + T maximumOrderRow = null; for (T row : displayedLeaderboardRowsProvider) { if (valueProvider.getDoubleValue(row) != null - && (minimumRow == null || comparator.compare(minimumRow, row) > 0)) { - minimumRow = row; + && (minimumOrderRow == null || comparator.compare(minimumOrderRow, row) > 0)) { + minimumOrderRow = row; } if (valueProvider.getDoubleValue(row) != null - && (maximumRow == null || comparator.compare(maximumRow, row) < 0)) { - maximumRow = row; + && (maximumOrderRow == null || comparator.compare(maximumOrderRow, row) < 0)) { + maximumOrderRow = row; } + } + if (minimumOrderRow != null) { + minimumValue = valueProvider.getDoubleValue(minimumOrderRow); + } - if (minimumRow != null) { - minimumValue = valueProvider.getDoubleValue(minimumRow); - } - if (maximumRow != null) { - maximumValue = valueProvider.getDoubleValue(maximumRow); + if (maximumOrderRow != null) { + maximumValue = valueProvider.getDoubleValue(maximumOrderRow); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardProxyPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardProxyPanel.java index 6fa2d5f1c6b..359b3fa29fe 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardProxyPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MultiLeaderboardProxyPanel.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Style.FontWeight; @@ -70,22 +71,24 @@ public class MultiLeaderboardProxyPanel extends AbstractLazyComponent availableDetailTypes; + private Function sailingServiceFactory; public MultiLeaderboardProxyPanel(Component parent, ComponentContext context, - SailingServiceAsync sailingService, String metaLeaderboardName, + Function sailingServiceFactory, String metaLeaderboardName, AsyncActionsExecutor asyncActionsExecutor, Timer timer, boolean isEmbedded, String preselectedLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, boolean showRaceDetails, boolean autoExpandLastRaceColumn, MultiRaceLeaderboardSettings settings, FlagImageResolver flagImageResolver, Iterable availableDetailTypes) { super(parent, context); + this.sailingServiceFactory = sailingServiceFactory; loadedSettings = settings; this.availableDetailTypes = availableDetailTypes; this.stringMessages = stringMessages; this.errorReporter = errorReporter; - this.sailingService = sailingService; + this.sailingService = sailingServiceFactory.apply(metaLeaderboardName); this.metaLeaderboardName = metaLeaderboardName; this.asyncActionsExecutor = asyncActionsExecutor; this.showRaceDetails = showRaceDetails; @@ -237,8 +240,9 @@ public class MultiLeaderboardProxyPanel extends AbstractLazyComponent { - public MultiRaceLeaderboardPanel(Component parent, ComponentContext context, - SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - MultiRaceLeaderboardSettings settings, boolean isEmbedded, - CompetitorSelectionProvider competitorSelectionProvider, String leaderboardGroupName, - String leaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, boolean showRaceDetails, - LeaderBoardStyle style, FlagImageResolver flagImageResolver, Iterable availableDetailTypes) { - super(parent, context, sailingService, asyncActionsExecutor, settings, isEmbedded, competitorSelectionProvider, - leaderboardGroupName, leaderboardName, errorReporter, stringMessages, showRaceDetails, style, flagImageResolver, availableDetailTypes); - initialize(settings); - } - public MultiRaceLeaderboardPanel(Component parent, ComponentContext context, SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, MultiRaceLeaderboardSettings settings, boolean isEmbedded, @@ -114,13 +102,7 @@ public class MultiRaceLeaderboardPanel extends LeaderboardPanel> componentContext, LeaderboardPerspectiveLifecycle lifecycle, PerspectiveCompositeSettings settings, - final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, + final Function sailingServiceFactory, final AsyncActionsExecutor asyncActionsExecutor, final Timer timer, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, DetailType chartDetailType, Iterable availableDetailTypes) { this(parent, componentContext, lifecycle, settings, new CompetitorSelectionModel(/* hasMultiSelection */true), - sailingService, asyncActionsExecutor, timer, + sailingServiceFactory, asyncActionsExecutor, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, chartDetailType, availableDetailTypes); } @@ -51,13 +52,16 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer settings, CompetitorSelectionModel competitorSelectionModel, - final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, + final Function sailingServiceFactory, final AsyncActionsExecutor asyncActionsExecutor, final Timer timer, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, DetailType chartDetailType, Iterable availableDetailTypes) { super(parent, componentContext, lifecycle, settings, competitorSelectionModel, asyncActionsExecutor, timer, stringMessages); - init(new MultiRaceLeaderboardPanel(this, getComponentContext(), sailingService, asyncActionsExecutor, + + final SailingServiceAsync sailingServiceForMainLeaderboard = sailingServiceFactory.apply(leaderboardName); + + init(new MultiRaceLeaderboardPanel(this, getComponentContext(), sailingServiceForMainLeaderboard, asyncActionsExecutor, settings.findSettingsByComponentId(LeaderboardPanelLifecycle.ID), false, competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, settings.getPerspectiveOwnSettings().isShowRaceDetails(), @@ -71,9 +75,8 @@ public class MultiRaceLeaderboardViewer extends AbstractLeaderboardViewer>( + if (perspectiveSettings.isShowOverallLeaderboard()) { + sailingServiceForMainLeaderboard.getOverallLeaderboardNamesContaining(leaderboardName, new MarkedAsyncCallback>( new AsyncCallback>() { @Override public void onSuccess(List result) { - if(result.size() == 1) { + if (result.size() == 1) { String overallLeaderboardName = result.get(0); + final SailingServiceAsync sailingServiceForOverallLeaderboard = sailingServiceFactory.apply(overallLeaderboardName); overallLeaderboardPanel = new OverallLeaderboardPanel(MultiRaceLeaderboardViewer.this, - getComponentContext(), sailingService, + getComponentContext(), sailingServiceForOverallLeaderboard, asyncActionsExecutor, settings.findSettingsByComponentId(OverallLeaderboardPanelLifecycle.ID), false, competitorSelectionProvider, timer, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java index d0d295a59d9..b5ad2383a81 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ScoringSchemeTypeFormatter.java @@ -46,6 +46,8 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeLowPointTieBreakBasedOnLastSeriesOnly(); case LOW_POINT_WITH_AUTOMATIC_RDG: return stringMessages.scoringSchemeLowPointWithAutomaticRdg(); + case LOW_POINT_FIRST_TO_WIN_TWO_RACES: + return stringMessages.scoringSchemeLowPointSystemFirstTwoWins(); } return null; } @@ -72,6 +74,8 @@ public class ScoringSchemeTypeFormatter { return stringMessages.scoringSchemeHighPointMatchRacingDescription(); case LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY: return stringMessages.scoringSchemeLowPointTieBreakBasedOnLastSeriesOnlyDescription(); + case LOW_POINT_FIRST_TO_WIN_TWO_RACES: + return stringMessages.scoringSchemeLowPointSystemFirstTwoWinsDescription(); default: return format(scoringSchemeType, stringMessages); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SingleRaceLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SingleRaceLeaderboardPanel.java index 43f6381add5..8fb286d5888 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SingleRaceLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/SingleRaceLeaderboardPanel.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; import com.google.gwt.cell.client.TextCell; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.user.cellview.client.SafeHtmlHeader; import com.sap.sailing.domain.common.DetailType; @@ -165,17 +164,7 @@ public class SingleRaceLeaderboardPanel extends LeaderboardPanel"); - } - return showBoatColor; + return isEmbedded() ? getRaceCompetitorSelection().getColor(competitor, preSelectedRace).getAsHtml() : null; } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java index eb0ff96be36..da72ccf8fb1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/EditableLeaderboardPanel.java @@ -67,6 +67,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.impl.InvertibleComparatorAdapter; import com.sap.sse.gwt.adminconsole.AdminConsoleTableResources; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.celltable.BaseCelltable; import com.sap.sse.gwt.client.dialog.DataEntryDialog.DialogCallback; @@ -194,8 +196,7 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel { @Override public void render(Context context, CompetitorDTO competitor, SafeHtmlBuilder sb) { - String competitorColorBarStyle = "style=\"border: none;\""; - base.render(competitor, competitorColorBarStyle, sb); + base.render(context, competitor, sb); } } @@ -708,7 +709,7 @@ public class EditableLeaderboardPanel extends MultiRaceLeaderboardPanel { } @Override public void onSuccess(Void result) { - Window.setStatus("Successfully unsuppressed competitor "+object.getName()); + Notification.notify("Successfully unsuppressed competitor "+object.getName(), NotificationType.INFO); // force a reload of the entire editable leaderboard to hide the now suppressed competitor loadCompleteLeaderboard(/* showProgress */ true); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java index f946be78aae..c941e0998fe 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/LeaderboardEditPage.java @@ -16,6 +16,7 @@ import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.security.Permission; import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.settings.client.leaderboardedit.LeaderboardEditContextDefinition; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardEntryPoint; @@ -28,22 +29,23 @@ import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator; import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication; -public class LeaderboardEditPage extends AbstractSailingEntryPoint { +public class LeaderboardEditPage extends AbstractSailingEntryPoint implements ProvidesLeaderboardRouting { private static final Logger logger = Logger.getLogger(LeaderboardEntryPoint.class.getName()); + private String leaderboardName; @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); - sailingService.getLeaderboardNames(new MarkedAsyncCallback>( + final LeaderboardEditContextDefinition settings = new SettingsToUrlSerializer() + .deserializeFromCurrentLocation(new LeaderboardEditContextDefinition()); + leaderboardName = settings.getLeaderboardName(); + getSailingService().getLeaderboardNames(new MarkedAsyncCallback>( new AsyncCallback>() { @Override public void onSuccess(List leaderboardNames) { - final LeaderboardEditContextDefinition settings = new SettingsToUrlSerializer() - .deserializeFromCurrentLocation(new LeaderboardEditContextDefinition()); - final String leaderboardName = settings.getLeaderboardName(); if (leaderboardNames.contains(leaderboardName)) { - sailingService.getAvailableDetailTypesForLeaderboard(leaderboardName, new AsyncCallback>() { + getSailingService().getAvailableDetailTypesForLeaderboard(leaderboardName, null, new AsyncCallback>() { @Override public void onFailure(Throwable caught) { @@ -59,7 +61,7 @@ public class LeaderboardEditPage extends AbstractSailingEntryPoint { authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() { @Override public Widget get() { - EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(sailingService, new AsyncActionsExecutor(), leaderboardName, null, + EditableLeaderboardPanel leaderboardPanel = new EditableLeaderboardPanel(getSailingService(), new AsyncActionsExecutor(), leaderboardName, null, LeaderboardEditPage.this, getStringMessages(), userAgent, result); leaderboardPanel.ensureDebugId("EditableLeaderboardPanel"); return leaderboardPanel; @@ -90,4 +92,9 @@ public class LeaderboardEditPage extends AbstractSailingEntryPoint { header.getElement().getStyle().setWidth(100, Unit.PCT); return header; } + + @Override + public String getLeaderboardName() { + return leaderboardName; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java index 9dbfea79ba3..6a8e1771168 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboardedit/MatchAndApplyScoreCorrectionsDialog.java @@ -16,7 +16,6 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Grid; @@ -39,6 +38,8 @@ import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO; import com.sap.sailing.gwt.ui.shared.RegattaScoreCorrectionDTO.ScoreCorrectionEntryDTO; import com.sap.sse.common.Util; import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.dialog.DataEntryDialog; public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog { @@ -433,7 +434,7 @@ public class MatchAndApplyScoreCorrectionsDialog extends DataEntryDialog() { @Override public void onFailure(Throwable caught) { @@ -68,12 +71,12 @@ public class PairingListEntryPoint extends AbstractSailingEntryPoint { contentPanel.getElement().getStyle().setProperty("marginTop", "15px"); contentPanel.getElement().getStyle().setProperty("marginBottom", "15px"); scrollPanel.add(contentPanel); - sailingService.getPairingListFromRaceLogs(pairingListContextDefinition.getLeaderboardName(), + getSailingService().getPairingListFromRaceLogs(pairingListContextDefinition.getLeaderboardName(), new AsyncCallback() { @Override public void onSuccess(PairingListDTO result) { if (strippedLeaderboardDTO != null) { - sailingService.getRaceDisplayNamesFromLeaderboard(strippedLeaderboardDTO.getName(), + getSailingService().getRaceDisplayNamesFromLeaderboard(strippedLeaderboardDTO.getName(), result.getRaceColumnNames(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { @@ -126,7 +129,7 @@ public class PairingListEntryPoint extends AbstractSailingEntryPoint { pairingListPanel.getElement().getStyle().setProperty("marginTop", "15px"); return pairingListPanel; } - + private native void printPairingListGrid(String pageHTMLContent) /*-{ var frameID = '__gwt_historyFrame'; var frame = $doc.getElementById(frameID); @@ -167,4 +170,8 @@ public class PairingListEntryPoint extends AbstractSailingEntryPoint { }, 100); }-*/; + @Override + public String getLeaderboardName() { + return leaderboardName; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java index ded676f65da..95701d0ab0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarBackendResultsPresenter.java @@ -20,7 +20,6 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.SimpleLayoutPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.charts.ChartToCsvExporter; import com.sap.sailing.gwt.ui.datamining.presentation.AbstractSailingResultsPresenter; import com.sap.sailing.gwt.ui.datamining.presentation.ChartFactory; import com.sap.sailing.polars.datamining.shared.PolarBackendData; @@ -28,6 +27,7 @@ import com.sap.sse.common.settings.Settings; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; +import com.sap.sse.datamining.ui.client.ChartToCsvExporter; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.settings.ComponentContext; @@ -88,7 +88,7 @@ public class PolarBackendResultsPresenter extends AbstractSailingResultsPresente dockLayoutPanel.addWest(polarChartWrapperPanel, 40); dockLayoutPanel.addEast(speedAndAngleChart, 60); - ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages); + ChartToCsvExporter chartToCsvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard()); Button exportStatisticsCurveToCsvButton = new Button(stringMessages.exportStatisticsCurveToCsv(), new ClickHandler() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java index 013243f97fa..648c27acd2a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java @@ -30,7 +30,6 @@ import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.SimpleLayoutPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.shared.charts.ChartToCsvExporter; import com.sap.sailing.gwt.ui.datamining.presentation.AbstractSailingResultsPresenter; import com.sap.sailing.gwt.ui.datamining.presentation.ChartFactory; import com.sap.sailing.polars.datamining.shared.PolarAggregation; @@ -39,6 +38,7 @@ import com.sap.sse.common.settings.Settings; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; +import com.sap.sse.datamining.ui.client.ChartToCsvExporter; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.settings.ComponentContext; @@ -109,7 +109,7 @@ public class PolarResultsPresenter extends AbstractSailingResultsPresenter() { + getSailingService().getRaceIdentifier(regattaLikeName, raceColumnName, fleetName, new AsyncCallback() { @Override public void onSuccess(final RegattaAndRaceIdentifier selectedRaceIdentifier) { if (selectedRaceIdentifier == null) { createErrorPage(getStringMessages().couldNotObtainRace(regattaLikeName, raceColumnName, fleetName, /* technicalErrorMessage */ "")); } else { - sailingService.getCompetitorBoats(selectedRaceIdentifier, new AsyncCallback>() { + getSailingService().getCompetitorBoats(selectedRaceIdentifier, new AsyncCallback>() { @Override public void onSuccess(Map result) { createEmbeddedMap(selectedRaceIdentifier, result, raceboardPerspectiveSettings, raceMapSettings, @@ -187,7 +189,7 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint return true; } }; - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, /* errorReporter */ this, + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, /* errorReporter */ this, Collections.singleton(selectedRaceIdentifier), 30000l /* requestInterval*/); raceTimesInfoProvider.addRaceTimesInfoProviderListener(new RaceTimesInfoProviderListener() { @Override @@ -208,7 +210,7 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint competitorSelection = createEmptyFilterCompetitorModel(colorProvider, competitorsAndBoats); // show no competitors } final RaceMap raceMap = new RaceMap(null, null, new RaceMapLifecycle(getStringMessages()), raceMapSettings, - sailingService, asyncActionsExecutor, /* errorReporter */ EmbeddedMapAndWindChartEntryPoint.this, timer, + getSailingService(), asyncActionsExecutor, /* errorReporter */ EmbeddedMapAndWindChartEntryPoint.this, timer, competitorSelection, new RaceCompetitorSet(competitorSelection), getStringMessages(), selectedRaceIdentifier, raceMapResources, /* showHeaderPanel */ false, new DefaultQuickRanksDTOProvider()) { @Override @@ -219,7 +221,7 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint }; final WindChart windChart; if (raceboardPerspectiveSettings.isShowWindChart()) { - windChart = new WindChart(null, null, new WindChartLifecycle(getStringMessages()), sailingService, + windChart = new WindChart(null, null, new WindChartLifecycle(getStringMessages()), getSailingService(), selectedRaceIdentifier, timer, timeRangeWithZoomProvider, new WindChartSettings(), getStringMessages(), asyncActionsExecutor, /* errorReporter */ @@ -254,4 +256,9 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint p.insert(raceMap.getEntryWidget(), raceMap, Direction.CENTER, 400); p.addStyleName("dockLayoutPanel"); } + + @Override + public String getLeaderboardName() { + return regattaLikeName; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index d5ab94621a9..4b6cfbe7256 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -14,6 +14,7 @@ import com.google.gwt.user.client.ui.RootLayoutPanel; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication; +import com.sap.sailing.gwt.common.communication.routing.ProvidesLeaderboardRouting; import com.sap.sailing.gwt.settings.client.raceboard.RaceBoardPerspectiveOwnSettings; import com.sap.sailing.gwt.settings.client.raceboard.RaceboardContextDefinition; import com.sap.sailing.gwt.settings.client.utils.StoredSettingsLocationFactory; @@ -38,7 +39,7 @@ import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAut import com.sap.sse.security.ui.settings.ComponentContextWithSettingsStorage; import com.sap.sse.security.ui.settings.StoredSettingsLocation; -public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { +public class RaceBoardEntryPoint extends AbstractSailingEntryPoint implements ProvidesLeaderboardRouting { private RaceWithCompetitorsAndBoatsDTO selectedRace; /** @@ -48,10 +49,13 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { private RaceboardContextDefinition raceboardContextDefinition; + private String leaderBoardName; + @Override protected void doOnModuleLoad() { super.doOnModuleLoad(); - EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, RemoteServiceMappingConstants.mediaServiceRemotePath); + EntryPointHelper.registerASyncService((ServiceDefTarget) mediaService, + RemoteServiceMappingConstants.mediaServiceRemotePath); raceboardContextDefinition = new SettingsToUrlSerializer() .deserializeFromCurrentLocation(new RaceboardContextDefinition()); final RaceBoardModes finalMode; @@ -61,12 +65,16 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { finalMode = null; } if (raceboardContextDefinition.getRegattaName() == null || raceboardContextDefinition.getRegattaName().isEmpty() - || raceboardContextDefinition.getRaceName() == null || raceboardContextDefinition.getRaceName().isEmpty() + || raceboardContextDefinition.getRaceName() == null + || raceboardContextDefinition.getRaceName().isEmpty() || raceboardContextDefinition.getLeaderboardName() == null || raceboardContextDefinition.getLeaderboardName().isEmpty()) { createErrorPage(getStringMessages().requiresRegattaRaceAndLeaderboard()); return; } + + leaderBoardName = raceboardContextDefinition.getLeaderboardName(); + AsyncCallback asyncCallback = new AsyncCallback() { @Override public void onSuccess(RaceboardDataDTO raceboardData) { @@ -93,17 +101,18 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { } } if (raceboardData.getRace() == null) { - createErrorPage(getStringMessages().couldNotFindRaceInRegatta(raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getRegattaName())); + createErrorPage(getStringMessages().couldNotFindRaceInRegatta( + raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getRegattaName())); return; } - + final boolean showChartMarkEditMediaButtonsAndVideo = !DeviceDetector.isMobile(); final StoredSettingsLocation storageDefinition = StoredSettingsLocationFactory .createStoredSettingsLocatorForRaceBoard(raceboardContextDefinition, finalMode != null ? finalMode.name() : null); // TODO bug2822: also determine regatta's boat change parameter and parameterize Leaderboard settings // --> show boat info column yes/no? - sailingService.determineDetailTypesForCompetitorChart( + getSailingService().determineDetailTypesForCompetitorChart( raceboardContextDefinition.getLeaderboardGroupName(), raceboardData.getRace().getRaceIdentifier(), new AsyncCallback>() { @@ -114,9 +123,9 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { @Override public void onSuccess(Iterable chartAllowedTypes) { - sailingService.getAvailableDetailTypesForLeaderboard( + getSailingService().getAvailableDetailTypesForLeaderboard( raceboardContextDefinition.getLeaderboardName(), - new AsyncCallback>() { + raceboardData.getRace().getRaceIdentifier(), new AsyncCallback>() { @Override public void onFailure(Throwable caught) { reportError( @@ -152,13 +161,13 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { } }); } - + @Override public void onFailure(Throwable caught) { reportError("Error trying to create the raceboard: " + caught.getMessage()); } }; - sailingService.getRaceboardData(raceboardContextDefinition.getRegattaName(), + getSailingService().getRaceboardData(raceboardContextDefinition.getRegattaName(), raceboardContextDefinition.getRaceName(), raceboardContextDefinition.getLeaderboardName(), raceboardContextDefinition.getLeaderboardGroupName(), raceboardContextDefinition.getEventId(), asyncCallback); @@ -185,10 +194,10 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { selectedRace = raceboardData.getRace(); Window.setTitle(selectedRace.getName()); AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor(); - RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, asyncActionsExecutor, this, + RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(getSailingService(), asyncActionsExecutor, this, Collections.singletonList(selectedRace.getRaceIdentifier()), 5000l /* requestInterval*/); RaceBoardPanel raceBoardPerspective = new RaceBoardPanel(parent, context, raceLifeCycle, settings, - sailingService, mediaService, getUserService(), asyncActionsExecutor, + getSailingService(), mediaService, getUserService(), asyncActionsExecutor, raceboardData.getCompetitorAndTheirBoats(), timer, selectedRace.getRaceIdentifier(), raceboardContextDefinition.getLeaderboardName(), raceboardContextDefinition.getLeaderboardGroupName(), raceboardContextDefinition.getEventId(), RaceBoardEntryPoint.this, getStringMessages(), userAgent, @@ -196,4 +205,9 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { RootLayoutPanel.get().add(raceBoardPerspective.getEntryWidget()); return raceBoardPerspective; } -} + + @Override + public String getLeaderboardName() { + return leaderBoardName; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index 261021d657c..4c430dcea21 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -397,7 +397,9 @@ public class RaceBoardPanel selectedRaceIdentifier, stringMessages, competitorSelectionProvider, errorReporter, timer, maneuverTableSettings, timeRangeWithZoomModel, new ClassicLeaderboardStyle(), userService); maneuverTablePanel.getEntryWidget().setTitle(stringMessages.maneuverTable()); - componentsForSideBySideViewer.add(maneuverTablePanel); + if (showChartMarkEditMediaButtonsAndVideo) { + componentsForSideBySideViewer.add(maneuverTablePanel); + } editMarkPassingPanel = new EditMarkPassingsPanel(this, getComponentContext(), sailingService, selectedRaceIdentifier, stringMessages, @@ -596,7 +598,6 @@ public class RaceBoardPanel @Override public void updatedLeaderboard(LeaderboardDTO leaderboard) { - leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); if (editMarkPassingPanel != null) { editMarkPassingPanel.setLeaderboard(leaderboard); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java index 96c19556174..71c4c5c7033 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java @@ -75,8 +75,6 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { private LayoutPanel mainPanel; private TouchSplitLayoutPanel splitLayoutPanel; - private int savedSplitPosition = -1; - private boolean layoutForLeftComponentForcedOnce = false; public SideBySideComponentViewer(final Component leftComponentP, final Component rightComponentP, final MediaPlayerManagerComponent mediaPlayerManagerComponent, List> components, @@ -131,18 +129,7 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { this.leftScrollPanel = new ScrollPanel(); this.leftScrollPanel.add(leftComponentP.getEntryWidget()); this.leftScrollPanel.setTitle(leftComponentP.getEntryWidget().getTitle()); - this.mainPanel = new LayoutPanel() { - @Override - public void onResize() { - int leftWidth = leftScrollPanel.getOffsetWidth(); - // The left scroll panel is potentially resized to ensure it is not too wide when the screen gets narrower, - // e.g. when resizing browser window or changing mobile device orientation. An offset of 40px is used, so - // the panels size slider and its toggle button is always accessable if it is open. - savedSplitPosition = Math.min(leftWidth > 0 ? leftWidth : savedSplitPosition, Window.getClientWidth() - 40); - splitLayoutPanel.setWidgetSize(leftScrollPanel, savedSplitPosition); - super.onResize(); - } - }; + this.mainPanel = new LayoutPanel(); this.mainPanel.setSize("100%", "100%"); this.mainPanel.getElement().getStyle().setMarginTop(-12, Unit.PX); this.mainPanel.setStyleName("SideBySideComponentViewer-MainPanel"); @@ -153,8 +140,7 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { initializeComponents(); // initialize the leaderboard component - savedSplitPosition = MIN_LEADERBOARD_WIDTH; - splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, savedSplitPosition); + splitLayoutPanel.insert(leftScrollPanel, leftComponent, Direction.WEST, MIN_LEADERBOARD_WIDTH); // create a panel that will contain the horizontal toggle buttons ResizableAbsolutePanel panelForMapAndHorizontalToggleButtons = new ResizableAbsolutePanel(); @@ -255,15 +241,12 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { if (!leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard is not visible, but the map is if (isWidgetInSplitPanel(leftScrollPanel)) { - if (leftScrollPanel.getOffsetWidth() > 0) { - savedSplitPosition = Math.min(savedSplitPosition, leftScrollPanel.getOffsetWidth()); - } splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */true, - savedSplitPosition); + MIN_LEADERBOARD_WIDTH); } } else if (leftComponent.isVisible() && rightComponent.isVisible()) { // the leaderboard and the map are visible - splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, savedSplitPosition); + splitLayoutPanel.setWidgetVisibility(leftScrollPanel, leftComponent, /* hidden */false, MIN_LEADERBOARD_WIDTH); } else if (!leftComponent.isVisible() && !rightComponent.isVisible()) { } @@ -292,16 +275,6 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { return ""; } - public void setLeftComponentWidth(int width) { - // TODO: The information provided by width is wrong - // need to find a way to get the correct information - if (!layoutForLeftComponentForcedOnce) { - savedSplitPosition = MIN_LEADERBOARD_WIDTH; - forceLayout(); - } - layoutForLeftComponentForcedOnce = true; - } - @Override public void onUserStatusChange(UserDTO user, boolean preAuthenticated) { final Splitter markPassingsSplitter = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel); @@ -361,4 +334,8 @@ public class SideBySideComponentViewer implements UserStatusEventHandler { leftScrollPanelSplitter.getToggleButton().setVisible(visible); } } + + public ScrollPanel getLeftScrollPanel() { + return leftScrollPanel; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java index 990e5148cf9..d75f0c8245f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SingleRaceLeaderboardPanelLifecycle.java @@ -63,8 +63,8 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc currentLeaderboardSettings.isShowCompetitorShortNameColumn(), currentLeaderboardSettings.isShowCompetitorFullNameColumn(), currentLeaderboardSettings.isShowCompetitorBoatInfoColumn(), - currentLeaderboardSettings.isShowRaceRankColumn(), - currentLeaderboardSettings.isShowCompetitorNationality()); + currentLeaderboardSettings.isShowCompetitorNationality(), + currentLeaderboardSettings.isShowRaceRankColumn()); return SettingsUtil.copyValues(contextSpecificLeaderboardSettings, defaultLeaderboardSettings); } @@ -79,7 +79,8 @@ public class SingleRaceLeaderboardPanelLifecycle extends LeaderboardPanelLifecyc currentLeaderboardSettings.isShowCompetitorShortNameColumn(), currentLeaderboardSettings.isShowCompetitorFullNameColumn(), currentLeaderboardSettings.isShowCompetitorBoatInfoColumn(), - currentLeaderboardSettings.isShowCompetitorNationality(),currentLeaderboardSettings.isShowRaceRankColumn()); + currentLeaderboardSettings.isShowCompetitorNationality(), + currentLeaderboardSettings.isShowRaceRankColumn()); return SettingsUtil.copyValues(globalLeaderboardSettings, defaultLeaderboardSettings); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/StartAnalysisMode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/StartAnalysisMode.java index 9ab82b6f8f0..bfd1ccd3c8c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/StartAnalysisMode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/StartAnalysisMode.java @@ -81,7 +81,8 @@ public class StartAnalysisMode extends RaceBoardModeWithPerRaceCompetitors { defaultSettings.getManeuverTypesToShow(), defaultSettings.isShowDouglasPeuckerPoints(), defaultSettings.isShowEstimatedDuration(), - defaultSettings.getStartCountDownFontSizeScaling()); + defaultSettings.getStartCountDownFontSizeScaling(), + defaultSettings.isShowManeuverLossVisualization()); ((RaceBoardComponentContext) raceMap.getComponentContext()).addModesPatching(raceMap, additiveSettings, new OnSettingsReloadedCallback() { @Override public void onSettingsReloaded(RaceMapSettings patchedSettings) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java index 18adba1e143..c8d4cb11d3f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/TouchSplitLayoutPanel.java @@ -809,16 +809,18 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { *

      * * @param hidden set this to true if the panel should be hidden - * @param size the size of the panel. Always provide the visible size even when hidden is true. + * @param initialSize the size of the panel. only required to be valid if not hidden. */ public void setWidgetVisibility(Widget widget, Component associatedComponentToWidget, - final boolean hidden, final int size) { + final boolean hidden, final int initialSize) { super.setWidgetHidden(widget, hidden); final Splitter splitter = getAssociatedSplitter(widget); if (splitter != null) { LayoutData layoutData = (LayoutData) widget.getLayoutData(); if (hidden) { - layoutData.oldSize = layoutData.size; + if(layoutData.size > 0) { + layoutData.oldSize = layoutData.size; + } widget.setVisible(false); if (associatedComponentToWidget != null) { if (associatedComponentToWidget.isVisible()) { @@ -833,7 +835,16 @@ public class TouchSplitLayoutPanel extends DockLayoutPanel { associatedComponentToWidget.setVisible(true); } } - splitter.setAssociatedWidgetSize((layoutData.oldSize > 0 ? layoutData.oldSize : size), /* defer */false); + if (layoutData.size <= 0) { + //it is not yet sized + if(layoutData.oldSize > 0) { + //and we have a stored old size it had before minimizing it + splitter.setAssociatedWidgetSize(layoutData.oldSize, /* defer */false); + } else { + //and we do not have any size yet, so use the default size + splitter.setAssociatedWidgetSize(initialSize, /* defer */false); + } + } splitter.setDraggerVisible(!hidden); splitter.setVisible(!hidden); splitter.getToggleButton().removeStyleDependentName("Closed"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java index bfb6d5ff1a6..e8621d61e5d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/WinningLanesMode.java @@ -77,7 +77,8 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors { defaultSettings.getManeuverTypesToShow(), defaultSettings.isShowDouglasPeuckerPoints(), defaultSettings.isShowEstimatedDuration(), - defaultSettings.getStartCountDownFontSizeScaling()); + defaultSettings.getStartCountDownFontSizeScaling(), + defaultSettings.isShowManeuverLossVisualization()); ((RaceBoardComponentContext) raceMap.getComponentContext()).addModesPatching(raceMap, additiveSettings, new OnSettingsReloadedCallback() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java index 39689495fb0..430fd4b7e49 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.ui.server; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; @@ -7,17 +8,24 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; +import java.net.URLConnection; +import java.net.URLEncoder; import java.nio.channels.Channels; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -37,6 +45,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import com.google.gwt.thirdparty.json.JSONException; +import com.google.gwt.thirdparty.json.JSONObject; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.VideoMetadataDTO; @@ -49,6 +59,7 @@ import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.security.shared.Permission.DefaultModes; public class MediaServiceImpl extends RemoteServiceServlet implements MediaService { + private String YOUTUBE_V3_API_KEY = "AIzaSyBzCJ9cxb9_PPzuYfrHIEdSRtR631b64Xs"; private static final Logger logger = Logger.getLogger(MediaServiceImpl.class.getName()); @@ -57,6 +68,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); private ServiceTracker racingEventServiceTracker; + private static final int REQUIRED_SIZE_IN_BYTES = 10000000; private static final long serialVersionUID = -8917349579281305977L; @@ -230,17 +242,24 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi try { tmp = createFileFromData(start, end, skipped); try (IsoFile isof = new IsoFile(tmp)) { - recordStartedTimer = determineRecordingStart(isof); - spherical = determine360(isof); - duration = determineDuration(isof); - removeTempFiles(isof); + try { + recordStartedTimer = determineRecordingStart(isof); + spherical = determine360(isof); + duration = determineDuration(isof); + } finally { + removeTempFiles(isof); + } } } catch (Exception e) { logger.log(Level.WARNING, "Error in video analysis ", e); message = e.getMessage(); } finally { if (tmp != null) { - tmp.delete(); + try { + Files.delete(tmp.toPath()); + } catch (IOException e) { + logger.log(Level.SEVERE, "Could not delete tmp mp4 file", e); + } } } return new VideoMetadataDTO(true, duration, spherical, recordStartedTimer, message); @@ -344,4 +363,45 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi } return result; } + + @Override + public VideoMetadataDTO checkYoutubeMetadata(String videoId) throws UnsupportedEncodingException { + ensureUserCanManageMedia(); + boolean canDownload = false; + String message = ""; + Duration duration = null; + if (videoId.isEmpty()) { + message = "Empty id"; + } else { + videoId = URLEncoder.encode(videoId, StandardCharsets.UTF_8.name()); + try { + URL apiURL = new URL( + "https://www.googleapis.com/youtube/v3/videos?id=" + videoId + "&key=" + YOUTUBE_V3_API_KEY + + "&part=snippet,contentDetails&fields=items(snippet/title,contentDetails/duration)"); + URLConnection connection = apiURL.openConnection(); + connection.setRequestProperty("Referer", "http://mediaservice.sapsailing.com/"); + connection.setConnectTimeout(METADATA_CONNECTION_TIMEOUT); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { + String pageText = reader.lines().collect(Collectors.joining("\n")); + JSONObject jsonAnswer = new JSONObject(pageText); + final JSONObject item = jsonAnswer.getJSONArray("items").getJSONObject(0); + message = item.getJSONObject("snippet").getString("title"); + String rawDuration = item.getJSONObject("contentDetails").getString("duration"); + duration = new MillisecondsDurationImpl(java.time.Duration.parse(rawDuration).toMillis()); + canDownload = true; + } catch (JSONException e) { + message = e.getMessage(); + logger.log(Level.WARNING, "Error in youtube metadata call", e); + } + } catch (IOException e) { + message = e.getMessage(); + logger.log(Level.WARNING, "Error in youtube metadata call", e); + } + } + //sanitize, as we inject it into an url with our api key! + + + return new VideoMetadataDTO(canDownload, duration, false, null, message); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 015d40ca4b8..acd7b91b0c2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -53,6 +53,10 @@ import java.util.stream.StreamSupport; import java.util.zip.GZIPInputStream; import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import org.apache.http.client.ClientProtocolException; import org.apache.shiro.SecurityUtils; @@ -278,6 +282,7 @@ import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTracki import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.sharding.ShardingType; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; @@ -306,10 +311,10 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.RaceStateOfSameDayHelper; -import com.sap.sailing.domain.racelog.impl.GPSFixStoreImpl; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler; import com.sap.sailing.domain.racelogtracking.DeviceMapping; +import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingImpl; @@ -318,6 +323,7 @@ import com.sap.sailing.domain.regattalike.HasRegattaLike; import com.sap.sailing.domain.regattalike.IsRegattaLike; import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike; import com.sap.sailing.domain.regattalog.RegattaLogStore; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sailing.domain.swisstimingadapter.StartList; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapter; import com.sap.sailing.domain.swisstimingadapter.SwissTimingAdapterFactory; @@ -391,6 +397,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupBaseDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LegInfoDTO; import com.sap.sailing.gwt.ui.shared.ManeuverDTO; +import com.sap.sailing.gwt.ui.shared.ManeuverLossDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO; import com.sap.sailing.gwt.ui.shared.PathDTO; @@ -522,8 +529,8 @@ import com.sap.sse.common.CountryCode; import com.sap.sse.common.Distance; import com.sap.sse.common.Duration; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.Speed; import com.sap.sse.common.PairingListCreationException; +import com.sap.sse.common.Speed; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Timed; @@ -2610,7 +2617,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } final FleetDTO fleetDTO = baseDomainFactory.convertToFleetDTO(fleet); - RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), raceColumn.getExplicitFactor(), raceColumn.getFactor(), + RaceColumnDTO raceColumnDTO = leaderboardDTO.addRace(raceColumn.getName(), + raceColumn.getExplicitFactor(), leaderboard.getScoringScheme().getScoreFactor(raceColumn), raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getRegatta().getName() : null, raceColumn instanceof RaceColumnInSeries ? ((RaceColumnInSeries) raceColumn).getSeries().getName() : null, fleetDTO, raceColumn.isMedalRace(), raceIdentifier, raceDTO, raceColumn instanceof MetaLeaderboardColumn); @@ -3633,16 +3641,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final SpeedWithBearingDTO speedBefore = createSpeedWithBearingDTO(maneuver.getSpeedWithBearingBefore()); final SpeedWithBearingDTO speedAfter = createSpeedWithBearingDTO(maneuver.getSpeedWithBearingAfter()); final double directionChangeInDegrees = maneuver.getDirectionChangeInDegrees(); - final Double maneuverLossInMeters = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getMeters(); final double maxTurningRateInDegreesPerSecond = maneuver.getMaxTurningRateInDegreesPerSecond(); final double averageTurningRateInDegreesPerSecond = maneuver.getAvgTurningRateInDegreesPerSecond(); final double lowestSpeedInKnots = maneuver.getLowestSpeed().getKnots(); final Date markPassingTimePoint = maneuver.isMarkPassing() ? maneuver.getMarkPassing().getTimePoint().asDate() : null; final NauticalSide markPassingSide = maneuver.isMarkPassing() ? maneuver.getToSide() : null; - result.add(new ManeuverDTO(type, newTack, position, timepoint, timePointBefore, speedBefore, speedAfter, - directionChangeInDegrees, maneuverLossInMeters, maxTurningRateInDegreesPerSecond, - averageTurningRateInDegreesPerSecond, lowestSpeedInKnots, markPassingTimePoint, markPassingSide)); + final SpeedWithBearing speedWithBearingBeforeManeuverLoss = maneuver.getManeuverLoss() == null ? null + : maneuver.getManeuverLoss().getSpeedWithBearingBefore(); + final Double middleManeuverAngle = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getMiddleManeuverAngle().getDegrees(); + final ManeuverLossDTO maneuverLoss = maneuver.getManeuverLoss() == null ? null + : new ManeuverLossDTO(maneuver.getManeuverLoss().getManeuverStartPosition(), + maneuver.getManeuverLoss().getManeuverEndPosition(), speedWithBearingBeforeManeuverLoss, middleManeuverAngle, + maneuver.getManeuverLoss().getManeuverDuration(), maneuver.getManeuverLoss().getProjectedDistanceLost()); + result.add(new ManeuverDTO(type, newTack, position, timepoint, timePointBefore, speedBefore, + speedAfter, directionChangeInDegrees, maxTurningRateInDegreesPerSecond, + averageTurningRateInDegreesPerSecond, lowestSpeedInKnots, markPassingTimePoint, markPassingSide, maneuverLoss)); } return result; } @@ -5112,7 +5126,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final MasterDataImporter importer = new MasterDataImporter(baseDomainFactory, getService()); importer.importFromStream(inputStream, importOperationId, override); - } catch (Exception e) { + } catch (Throwable e) { // do not assume that RuntimeException is logged properly logger.log(Level.SEVERE, e.getMessage(), e); getService() @@ -5474,6 +5488,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S dto.defaultRacingProcedureType = configuration.getDefaultRacingProcedureType(); dto.defaultCourseDesignerMode = configuration.getDefaultCourseDesignerMode(); + dto.defaultProtestTimeDuration = configuration.getDefaultProtestTimeDuration(); if (configuration.getRRS26Configuration() != null) { dto.rrs26Configuration = new DeviceConfigurationDTO.RegattaConfigurationDTO.RRS26ConfigurationDTO(); @@ -5531,6 +5546,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S RegattaConfigurationImpl configuration = new RegattaConfigurationImpl(); configuration.setDefaultRacingProcedureType(dto.defaultRacingProcedureType); configuration.setDefaultCourseDesignerMode(dto.defaultCourseDesignerMode); + configuration.setDefaultProtestTimeDuration(dto.defaultProtestTimeDuration); if (dto.rrs26Configuration != null) { RRS26ConfigurationImpl config = new RRS26ConfigurationImpl(); applyGeneralRacingProcedureConfigProperties(dto.rrs26Configuration, config); @@ -5960,10 +5976,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Util.addAll(competitorToBoatMappingsRegistered.keySet(), competitorsToUnregister); for (final Entry e : competitorToBoatMappingsRegistered.entrySet()) { CompetitorType competitor = e.getKey(); - if (competitorToBoatMappingsToRegister.get(competitor) == e.getValue()) { - // User wants to map competitor to boat, and that mapping already exists; neither add nor remove this registration but leave as is: - competitorToBoatMappingsToRegister.remove(competitor); - competitorsToUnregister.remove(competitor); + if (competitorToBoatMappingsToRegister.containsKey(competitor)) { // is competitor to be registered? + final Boat boatOfCompetitorToRegister = competitorToBoatMappingsToRegister.get(competitor); + final Boat boatOfRegisteredCompetitor = e.getValue(); + if (boatOfCompetitorToRegister == boatOfRegisteredCompetitor) { + // User wants to map competitor to boat, and that mapping already exists; neither add nor remove this registration but leave as is: + competitorToBoatMappingsToRegister.remove(competitor); + competitorsToUnregister.remove(competitor); + } } } return competitorsToUnregister; @@ -6488,15 +6508,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public RaceDTO setStartTimeReceivedForRace(RaceIdentifier raceIdentifier, Date newStartTimeReceived) { - if (newStartTimeReceived != null) { - RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName( - raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()); - DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier); - trackedRace.setStartTimeReceived(new MillisecondsTimePoint(newStartTimeReceived)); - - return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace); - } - return null; + RegattaNameAndRaceName regattaAndRaceIdentifier = new RegattaNameAndRaceName(raceIdentifier.getRegattaName(), + raceIdentifier.getRaceName()); + DynamicTrackedRace trackedRace = getService().getTrackedRace(regattaAndRaceIdentifier); + trackedRace.setStartTimeReceived( + newStartTimeReceived == null ? null : new MillisecondsTimePoint(newStartTimeReceived)); + return baseDomainFactory.createRaceDTO(getService(), false, regattaAndRaceIdentifier, trackedRace); } @Override @@ -7045,7 +7062,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return new MarkTracksDTO(markTracks); } - private MarkTrackDTO getMarkTrack(Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, String markIdAsString) { + private MarkTrackDTO getMarkTrack(Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, + String markIdAsString) { MarkDTO markDTO = null; Mark mark = null; for (final Mark currMark : raceColumn.getAvailableMarks(fleet)) { @@ -7055,25 +7073,39 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S break; } } - + if (markDTO != null) { final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); final GPSFixTrack markTrack; if (trackedRace != null) { markTrack = trackedRace.getOrCreateTrack(mark); } else { - DynamicGPSFixTrackImpl writeableMarkTrack = new DynamicGPSFixTrackImpl(mark, BoatClass.APPROXIMATE_AVERAGE_MANEUVER_DURATION.asMillis()); + DynamicGPSFixTrackImpl writeableMarkTrack = new DynamicGPSFixTrackImpl(mark, + BoatClass.APPROXIMATE_AVERAGE_MANEUVER_DURATION.asMillis()); markTrack = writeableMarkTrack; final RaceLog raceLog = raceColumn.getRaceLog(fleet); final RegattaLog regattaLog = raceColumn.getRegattaLog(); final TrackingTimesFinder trackingTimesFinder = new TrackingTimesFinder(raceLog); - final Pair trackingTimes = trackingTimesFinder.analyze(); + final Pair trackingTimes = trackingTimesFinder + .analyze(); try { - GPSFixStore gfs = new GPSFixStoreImpl(getService().getSensorFixStore()); - gfs.loadMarkTrack(writeableMarkTrack, regattaLog, mark, - trackingTimes.getA().getTimePoint(), trackingTimes.getB().getTimePoint()); + SensorFixStore sensorFixStore = getService().getSensorFixStore(); + List> mappings = new RegattaLogDeviceMarkMappingFinder( + regattaLog).analyze().get(mark); + if (mappings != null) { + for (DeviceMapping mapping : mappings) { + final TimePoint from = Util.getLatestOfTimePoints(trackingTimes.getA().getTimePoint(), + mapping.getTimeRange().from()); + final TimePoint to = Util.getEarliestOfTimePoints(trackingTimes.getB().getTimePoint(), + mapping.getTimeRange().to()); + sensorFixStore. loadFixes(loadedFix -> writeableMarkTrack.add(loadedFix, true), + mapping.getDevice(), from, to, false, () -> false, progressIgnoringConsumer -> { + }); + } + } } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { - logger.info("Error trying to load mark track for mark "+mark+" from "+trackingTimes.getA()+" to "+trackingTimes.getB()); + logger.info("Error trying to load mark track for mark " + mark + " from " + trackingTimes.getA() + + " to " + trackingTimes.getB()); } } Iterable gpsFixDTOTrack = convertToGPSFixDTOTrack(markTrack); @@ -7485,7 +7517,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Iterable getAvailableDetailTypesForLeaderboard(String leaderboardName) { + public Iterable getAvailableDetailTypesForLeaderboard(String leaderboardName, RegattaAndRaceIdentifier raceIdentifierOrNull) { final Set allowed = new HashSet<>(); allowed.addAll(DetailType.getAllNonRestrictedDetailTypes()); final Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); @@ -7494,7 +7526,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S boolean hasExtendedBravoFixes = false; abort: for (RaceColumn race : leaderboard.getRaceColumns()) { for (Fleet fleet : race.getFleets()) { - TrackedRace trace = race.getTrackedRace(fleet); + if (raceIdentifierOrNull != null && !raceIdentifierOrNull.equals(race.getRaceIdentifier(fleet))) { + continue; + } + + final TrackedRace trace = race.getTrackedRace(fleet); if (trace != null) { final DynamicTrackedRace trackedRace = getService().getTrackedRace(trace.getRaceIdentifier()); if (trackedRace != null) { @@ -7870,7 +7906,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Boolean checkIfRaceIsTracked(RegattaAndRaceIdentifier race) { + public Boolean checkIfRaceIsTracking(RegattaAndRaceIdentifier race) { boolean result = false; DynamicTrackedRace trace = getService().getTrackedRace(race); if (trace != null) { @@ -7881,4 +7917,19 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return result; } + + @Override + public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { + ShardingType identifiedShardingType = null; + try { + if (req instanceof HttpServletRequest) { + identifiedShardingType = ShardingContext.identifyAndSetShardingConstraint(((HttpServletRequest) req).getPathInfo()); + } + super.service(req, res); + } finally { + if (identifiedShardingType != null) { + ShardingContext.clearShardingConstraint(identifiedShardingType); + } + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java index 4a8244a7f72..cb56068911b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/DeviceConfigurationDTO.java @@ -6,6 +6,7 @@ import com.google.gwt.user.client.rpc.IsSerializable; 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.sse.common.Duration; public class DeviceConfigurationDTO implements IsSerializable { @@ -40,6 +41,7 @@ public class DeviceConfigurationDTO implements IsSerializable { } public RacingProcedureType defaultRacingProcedureType; public CourseDesignerMode defaultCourseDesignerMode; + public Duration defaultProtestTimeDuration; public RRS26ConfigurationDTO rrs26Configuration; public SWCStartConfigurationDTO swcStartConfiguration; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverDTO.java index 06f0284da8b..85a3c1daa46 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverDTO.java @@ -13,41 +13,42 @@ import com.sap.sailing.domain.common.Tack; import com.sap.sailing.gwt.ui.client.StringMessages; public class ManeuverDTO implements IsSerializable { - public ManeuverType type; + private ManeuverType type; - public Tack newTack; + private Tack newTack; - public Position position; + private Position position; - public Date timePoint; + private Date timePoint; - public Date timePointBefore; + private Date timePointBefore; + + private SpeedWithBearingDTO speedWithBearingBefore; - public SpeedWithBearingDTO speedWithBearingBefore; + private SpeedWithBearingDTO speedWithBearingAfter; - public SpeedWithBearingDTO speedWithBearingAfter; + private double directionChangeInDegrees; - public double directionChangeInDegrees; + private double maxTurningRateInDegreesPerSecond; - public Double maneuverLossInMeters; + private double avgTurningRateInDegreesPerSecond; - public double maxTurningRateInDegreesPerSecond; + private double lowestSpeedInKnots; - public double avgTurningRateInDegreesPerSecond; + private Date markPassingTimePoint; - public double lowestSpeedInKnots; + private NauticalSide markPassingSide; + + private ManeuverLossDTO maneuverLoss; - public Date markPassingTimePoint; - - public NauticalSide markPassingSide; - - public ManeuverDTO() {} + @Deprecated // for GWT serialization only + ManeuverDTO() {} public ManeuverDTO(ManeuverType type, Tack newTack, Position position, Date timePoint, Date timePointBefore, SpeedWithBearingDTO speedWithBearingBefore, SpeedWithBearingDTO speedWithBearingAfter, - double directionChangeInDegrees, Double maneuverLossInMeters, double maxTurningRateInDegreesPerSecond, + double directionChangeInDegrees, double maxTurningRateInDegreesPerSecond, double avgTurningRateInDegreesPerSecond, double lowestSpeedInKnots, Date markPassingTimePoint, - NauticalSide markPassingSide) { + NauticalSide markPassingSide, ManeuverLossDTO maneuverLoss) { this.type = type; this.newTack = newTack; this.position = position; @@ -56,23 +57,23 @@ public class ManeuverDTO implements IsSerializable { this.speedWithBearingBefore = speedWithBearingBefore; this.speedWithBearingAfter = speedWithBearingAfter; this.directionChangeInDegrees = directionChangeInDegrees; - this.maneuverLossInMeters = maneuverLossInMeters; this.maxTurningRateInDegreesPerSecond = maxTurningRateInDegreesPerSecond; this.avgTurningRateInDegreesPerSecond = avgTurningRateInDegreesPerSecond; this.lowestSpeedInKnots = lowestSpeedInKnots; this.markPassingTimePoint = markPassingTimePoint; this.markPassingSide = markPassingSide; + this.maneuverLoss = maneuverLoss; } public String toString(StringMessages stringMessages) { - SpeedWithBearingDTO before = this.speedWithBearingBefore; - SpeedWithBearingDTO after = this.speedWithBearingAfter; + SpeedWithBearingDTO before = this.getSpeedWithBearingBefore(); + SpeedWithBearingDTO after = this.getSpeedWithBearingAfter(); final DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL); - String timeAndManeuver = dateTimeFormat.format(this.timePoint) + ": " + this.type.name(); - String timePointBefore = " (started: " + dateTimeFormat.format(this.timePointBefore) + ")"; + String timeAndManeuver = dateTimeFormat.format(this.getTimePoint()) + ": " + this.getType().name(); + String timePointBefore = " (started: " + dateTimeFormat.format(this.getTimePointBefore()) + ")"; String directionChange = stringMessages.directionChange() + ": " - + ((int) Math.round(this.directionChangeInDegrees)) + " " + stringMessages.degreesShort() + " (" + + ((int) Math.round(this.getDirectionChangeInDegrees())) + " " + stringMessages.degreesShort() + " (" + ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> " + ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")"; String speedChange = stringMessages.speedChange() + ": " @@ -81,25 +82,81 @@ public class ManeuverDTO implements IsSerializable { + stringMessages.knotsUnit() + " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " " + stringMessages.knotsUnit() + ")"; String maxTurningRate = stringMessages.maxTurningRate() + ": " - + NumberFormat.getDecimalFormat().format(this.maxTurningRateInDegreesPerSecond) + " " + + NumberFormat.getDecimalFormat().format(this.getMaxTurningRateInDegreesPerSecond()) + " " + stringMessages.degreesPerSecondUnit(); String avgTurningRate = stringMessages.avgTurningRate() + ": " - + NumberFormat.getDecimalFormat().format(this.avgTurningRateInDegreesPerSecond) + " " + + NumberFormat.getDecimalFormat().format(this.getAvgTurningRateInDegreesPerSecond()) + " " + stringMessages.degreesPerSecondUnit(); String lowestSpeed = stringMessages.lowestSpeed() + ": " - + NumberFormat.getDecimalFormat().format(this.lowestSpeedInKnots) + " " + stringMessages.knotsUnit(); - String maneuverLoss = this.maneuverLossInMeters == null ? "" + + NumberFormat.getDecimalFormat().format(this.getLowestSpeedInKnots()) + " " + stringMessages.knotsUnit(); + String maneuverLoss = (this.getManeuverLoss() == null || this.getManeuverLoss().getDistanceLost() == null) ? "" : ("; " + stringMessages.maneuverLoss() + ": " - + NumberFormat.getDecimalFormat().format(this.maneuverLossInMeters) + " " + + NumberFormat.getDecimalFormat().format(this.getManeuverLoss().getDistanceLost().getMeters()) + " " + stringMessages.metersUnit()); - String markPassing = markPassingTimePoint == null ? "" + String markPassing = getMarkPassingTimePoint() == null ? "" : "; " + stringMessages.markPassedToAt( - this.markPassingSide == null ? "" - : this.markPassingSide == NauticalSide.PORT ? stringMessages.portSide() + this.getMarkPassingSide() == null ? "" + : this.getMarkPassingSide() == NauticalSide.PORT ? stringMessages.portSide() : stringMessages.starboardSide(), - DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(this.markPassingTimePoint)); + DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(this.getMarkPassingTimePoint())); String maneuverTitle = timeAndManeuver + timePointBefore + "; " + directionChange + "; " + speedChange + "; " + maxTurningRate + "; " + avgTurningRate + "; " + lowestSpeed + maneuverLoss + markPassing; return maneuverTitle; } + + public ManeuverType getType() { + return type; + } + + public Tack getNewTack() { + return newTack; + } + + public Position getPosition() { + return position; + } + + public Date getTimePoint() { + return timePoint; + } + + public Date getTimePointBefore() { + return timePointBefore; + } + + public SpeedWithBearingDTO getSpeedWithBearingBefore() { + return speedWithBearingBefore; + } + + public SpeedWithBearingDTO getSpeedWithBearingAfter() { + return speedWithBearingAfter; + } + + public double getDirectionChangeInDegrees() { + return directionChangeInDegrees; + } + + public double getMaxTurningRateInDegreesPerSecond() { + return maxTurningRateInDegreesPerSecond; + } + + public double getAvgTurningRateInDegreesPerSecond() { + return avgTurningRateInDegreesPerSecond; + } + + public double getLowestSpeedInKnots() { + return lowestSpeedInKnots; + } + + public Date getMarkPassingTimePoint() { + return markPassingTimePoint; + } + + public NauticalSide getMarkPassingSide() { + return markPassingSide; + } + + public ManeuverLossDTO getManeuverLoss() { + return maneuverLoss; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverLossDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverLossDTO.java new file mode 100755 index 00000000000..4967020e1ce --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/ManeuverLossDTO.java @@ -0,0 +1,49 @@ +package com.sap.sailing.gwt.ui.shared; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.Duration; + +public class ManeuverLossDTO implements IsSerializable { + private Position maneuverStartPosition; + private Position maneuverEndPosition; + private SpeedWithBearing speedWithBearingBefore; + private Double middleManeuverAngle; + private Duration maneuverDuration; + private Distance projectedDistanceLost; + + @Deprecated // for GWT serialization only + ManeuverLossDTO() { + } + + public ManeuverLossDTO(Position maneuverStartPosition, Position maneuverEndPosition, + SpeedWithBearing speedWithBearingBefore, Double middleManeuverAngle, Duration maneuverDuration, Distance projectedDistanceLost) { + this.maneuverStartPosition = maneuverStartPosition; + this.maneuverEndPosition = maneuverEndPosition; + this.speedWithBearingBefore = speedWithBearingBefore; + this.middleManeuverAngle = middleManeuverAngle; + this.maneuverDuration = maneuverDuration; + this.projectedDistanceLost = projectedDistanceLost; + } + + public Duration getManeuverDuration() { + return maneuverDuration; + } + public Position getManeuverStartPosition() { + return maneuverStartPosition; + } + public Position getManeuverEndPosition() { + return maneuverEndPosition; + } + public Double getMiddleManeuverAngle() { + return middleManeuverAngle; + } + public SpeedWithBearing getSpeedWithBearingBefore() { + return speedWithBearingBefore; + } + public Distance getDistanceLost() { + return projectedDistanceLost; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceSimulationOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceSimulationOverlay.java index 43e8900e55e..b6e133dce2b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceSimulationOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/RaceSimulationOverlay.java @@ -15,7 +15,6 @@ import com.google.gwt.i18n.client.TimeZone; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.controls.ControlPosition; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.LegIdentifier; import com.sap.sailing.domain.common.LegIdentifierImpl; @@ -31,6 +30,8 @@ import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; import com.sap.sailing.gwt.ui.simulator.racemap.FullCanvasOverlay; import com.sap.sailing.gwt.ui.simulator.util.ColorPalette; import com.sap.sailing.gwt.ui.simulator.util.ColorPaletteGenerator; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; @@ -338,8 +339,8 @@ public class RaceSimulationOverlay extends FullCanvasOverlay { @Override public void onFailure(Throwable caught) { // TODO: add corresponding message to string-messages - // Window.setStatus(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); - Window.setStatus(GET_SIMULATION_CATEGORY); + // Notification.error(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); + Notification.notify(GET_SIMULATION_CATEGORY, NotificationType.WARNING); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java index dd4c6dce336..454418dda8f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/WindStreamletsRaceboardOverlay.java @@ -13,7 +13,6 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.controls.ControlPosition; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.WindSource; @@ -31,6 +30,8 @@ import com.sap.sailing.gwt.ui.simulator.streamlets.Swarm; import com.sap.sailing.gwt.ui.simulator.streamlets.VectorField; import com.sap.sailing.gwt.ui.simulator.streamlets.WindInfoForRaceVectorField; import com.sap.sse.common.Util; +import com.sap.sse.gwt.client.Notification; +import com.sap.sse.gwt.client.Notification.NotificationType; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.player.Timer; @@ -217,7 +218,8 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { sailingService.getWindSourcesInfo(raceIdentifier, new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.setStatus(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); + Notification.notify(stringMessages.errorFetchingWindStreamletData(caught.getMessage()), + NotificationType.WARNING); } @Override @@ -256,7 +258,8 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { new MarkedAsyncCallback<>(new AsyncCallback() { @Override public void onFailure(Throwable caught) { - Window.setStatus(stringMessages.errorFetchingWindStreamletData(caught.getMessage())); + Notification.notify(stringMessages.errorFetchingWindStreamletData(caught.getMessage()), + NotificationType.WARNING); } @Override @@ -266,18 +269,20 @@ public class WindStreamletsRaceboardOverlay extends MovingCanvasOverlay { // confidences for (Entry e : result.windTrackInfoByWindSource.entrySet()) { WindTrackInfoDTO windTrackForSource = windInfoForRace.windTrackInfoByWindSource.get(e.getKey()); - final WindTrackInfoDTO resultWindTrackInfoDTO = result.windTrackInfoByWindSource.get(e.getKey()); - windTrackForSource.resolutionOutsideOfWhichNoFixWillBeReturned = resultWindTrackInfoDTO.resolutionOutsideOfWhichNoFixWillBeReturned; - if (windTrackForSource.windFixes == null) { - windTrackForSource.windFixes = resultWindTrackInfoDTO.windFixes; - } else { - windTrackForSource.windFixes.addAll(resultWindTrackInfoDTO.windFixes); - } - if (resultWindTrackInfoDTO.maxWindConfidence > windTrackForSource.maxWindConfidence) { - windTrackForSource.maxWindConfidence = resultWindTrackInfoDTO.maxWindConfidence; - } - if (resultWindTrackInfoDTO.minWindConfidence < windTrackForSource.minWindConfidence) { - windTrackForSource.minWindConfidence = resultWindTrackInfoDTO.minWindConfidence; + if (windTrackForSource != null) { + final WindTrackInfoDTO resultWindTrackInfoDTO = result.windTrackInfoByWindSource.get(e.getKey()); + windTrackForSource.resolutionOutsideOfWhichNoFixWillBeReturned = resultWindTrackInfoDTO.resolutionOutsideOfWhichNoFixWillBeReturned; + if (windTrackForSource.windFixes == null) { + windTrackForSource.windFixes = resultWindTrackInfoDTO.windFixes; + } else { + windTrackForSource.windFixes.addAll(resultWindTrackInfoDTO.windFixes); + } + if (resultWindTrackInfoDTO.maxWindConfidence > windTrackForSource.maxWindConfidence) { + windTrackForSource.maxWindConfidence = resultWindTrackInfoDTO.maxWindConfidence; + } + if (resultWindTrackInfoDTO.minWindConfidence < windTrackForSource.minWindConfidence) { + windTrackForSource.minWindConfidence = resultWindTrackInfoDTO.minWindConfidence; + } } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java index 4c42318a50a..b72ddfead17 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/SpectatorEntryPoint.java @@ -38,7 +38,7 @@ public class SpectatorEntryPoint extends AbstractSailingEntryPoint implements Re } else { groupName = groupParamValue; Window.setTitle(groupName); - sailingService.getLeaderboardGroupByName(groupName, false /*withGeoLocationData*/, new AsyncCallback() { + getSailingService().getLeaderboardGroupByName(groupName, false /*withGeoLocationData*/, new AsyncCallback() { @Override public void onFailure(Throwable t) { reportError(getStringMessages().noLeaderboardGroupWithNameFound(groupName)); @@ -58,11 +58,11 @@ public class SpectatorEntryPoint extends AbstractSailingEntryPoint implements Re // DON'T DELETE -> the EventOverviewPanel will replace the LeaderboardGroupOverviewPanel later on // EventOverviewPanel eventOverviewPanel = new EventOverviewPanel(sailingService, this, stringMessages, showRaceDetails); // groupOverviewPanel.add( eventOverviewPanel); - LeaderboardGroupOverviewPanel leaderboardGroupOverviewPanel = new LeaderboardGroupOverviewPanel(sailingService, this, getStringMessages(), settings.isShowRaceDetails()); + LeaderboardGroupOverviewPanel leaderboardGroupOverviewPanel = new LeaderboardGroupOverviewPanel(getSailingService(), this, getStringMessages(), settings.isShowRaceDetails()); groupOverviewPanel.add(leaderboardGroupOverviewPanel); rootPanel.add(groupOverviewPanel); } else { - LeaderboardGroupPanel groupPanel = new LeaderboardGroupPanel(sailingService, getStringMessages(), this, + LeaderboardGroupPanel groupPanel = new LeaderboardGroupPanel(getSailingService(), getStringMessages(), this, groupName, settings.getViewMode(), embedded, settings.isShowRaceDetails(), settings.isCanReplayDuringLiveRaces(), settings.isShowMapControls()); groupAndFeedbackPanel.add(groupPanel); diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CLUBSWAN50.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CLUBSWAN50.png new file mode 100755 index 00000000000..e6b41978640 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/CLUBSWAN50.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_JUNIOR.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_JUNIOR.png new file mode 100755 index 00000000000..ba7f9d8dc00 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/FLYING_JUNIOR.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VARIANTA.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VARIANTA.png new file mode 100755 index 00000000000..b7f8f803b2c Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VARIANTA.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VAURIEN.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VAURIEN.png new file mode 100644 index 00000000000..9e06221b7de Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/boatclass/VAURIEN.png differ diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java deleted file mode 100644 index d7c236fe87f..00000000000 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreGPSFixTrackTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.sap.sailing.mongodb.test; - -import static junit.framework.Assert.assertTrue; -import static org.junit.Assert.assertEquals; - -import java.net.UnknownHostException; - -import org.junit.After; -import org.junit.Test; - -import com.mongodb.DB; -import com.mongodb.MongoException; -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl; -import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.DeviceIdentifier; -import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; -import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl; -import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; -import com.sap.sailing.domain.persistence.DomainObjectFactory; -import com.sap.sailing.domain.persistence.MongoObjectFactory; -import com.sap.sailing.domain.persistence.PersistenceFactory; -import com.sap.sailing.domain.persistence.impl.CollectionNames; -import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl; -import com.sap.sailing.domain.racelog.impl.GPSFixStoreImpl; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; -import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; -import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; -import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; -import com.sap.sse.common.NoCorrespondingServiceRegisteredException; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.TypeBasedServiceFinderFactory; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.DegreeBearingImpl; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class StoreGPSFixTrackTest extends AbstractMongoDBTest { - public StoreGPSFixTrackTest() throws UnknownHostException, MongoException { - super(); - } - - @After - public void dropCollection() { - DB db = getMongoService().getDB(); - db.getCollection(CollectionNames.GPS_FIXES.name()).drop(); - } - - @Test - public void testStoreAndLoadFixes() throws TransformationException, NoCorrespondingServiceRegisteredException { - TypeBasedServiceFinderFactory factory = new MockSmartphoneImeiServiceFinderFactory(); - AbstractLogEventAuthor author = new LogEventAuthorImpl("author", 0); - Competitor comp = DomainFactory.INSTANCE.getOrCreateCompetitor("comp", "comp", "c", null, null, null, null, - /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); - Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); - MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getMongoObjectFactory(getMongoService(), factory); - DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE, factory); - GPSFixStore store = new GPSFixStoreImpl(new MongoSensorFixStoreImpl(mongoObjectFactory, domainObjectFactory, factory)); - - TimePoint time0 = new MillisecondsTimePoint(0); - TimePoint time1 = new MillisecondsTimePoint(1); - TimePoint time2 = new MillisecondsTimePoint(2); - - GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(30, 40), time0, - new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0))); - GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(30, 40), time1, - new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0))); - //lies outside of mapping range - GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(30, 40), time2, - new KnotSpeedWithBearingImpl(0, new DegreeBearingImpl(0))); - DeviceIdentifier device1 = new SmartphoneImeiIdentifier("a"); - store.storeFix(device1, fix1); - store.storeFix(device1, fix2); - store.storeFix(device1, fix3); - - GPSFix fix4 = new GPSFixImpl(new DegreePosition(40, 50), time0); - DeviceIdentifier device2 = new SmartphoneImeiIdentifier("b"); - store.storeFix(device2, fix4); - - RegattaLog regattaLog = new RegattaLogImpl("racelog"); - regattaLog.add(new RegattaLogDeviceCompetitorMappingEventImpl(time0, author, comp, device1, time0, time1)); - regattaLog.add(new RegattaLogDeviceMarkMappingEventImpl(time0, author, mark, device2, time0, time1)); - - DynamicGPSFixMovingTrackImpl track1 = new DynamicGPSFixMovingTrackImpl<>(comp, 0); - store.loadCompetitorTrack(track1, regattaLog, comp); - DynamicGPSFixTrackImpl track2 = new DynamicGPSFixTrackImpl<>(mark, 0); - store.loadMarkTrack(track2, regattaLog, mark); - track1.lockForRead(); - assertEquals(2, Util.size(track1.getRawFixes())); - assertTrue(track1.getFirstRawFix() instanceof GPSFixMoving); - track1.unlockAfterRead(); - track2.lockForRead(); - assertEquals(1, Util.size(track2.getRawFixes())); - track2.unlockAfterRead(); - } -} diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java index 6e568a81c19..640f9d915f8 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingLeaderboards.java @@ -68,9 +68,9 @@ public class TestStoringAndRetrievingLeaderboards extends AbstractMongoDBTest { r3.setFactor(2.5); new MongoObjectFactoryImpl(db).storeLeaderboard(leaderboard); Leaderboard loadedLeaderboard = new DomainObjectFactoryImpl(db, DomainFactory.INSTANCE).loadLeaderboard(leaderboardName, /* regattaRegistry */ null, /* leaderboardRegistry */ null); - assertEquals(1.0, loadedLeaderboard.getRaceColumnByName("R1").getFactor(), 0.000000001); - assertEquals(1.5, loadedLeaderboard.getRaceColumnByName("R2").getFactor(), 0.000000001); - assertEquals(2.5, loadedLeaderboard.getRaceColumnByName("R3").getFactor(), 0.000000001); + assertEquals(1.0, loadedLeaderboard.getScoringScheme().getScoreFactor(loadedLeaderboard.getRaceColumnByName("R1")), 0.000000001); + assertEquals(1.5, loadedLeaderboard.getScoringScheme().getScoreFactor(loadedLeaderboard.getRaceColumnByName("R2")), 0.000000001); + assertEquals(2.5, loadedLeaderboard.getScoringScheme().getScoreFactor(loadedLeaderboard.getRaceColumnByName("R3")), 0.000000001); } @Test diff --git a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java index 0ebfeff56d2..5ec38c3b121 100644 --- a/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java +++ b/java/com.sap.sailing.polars.test/src/com/sap/sailing/polars/jaxrs/api/test/PolarDataResourceTest.java @@ -61,7 +61,7 @@ public class PolarDataResourceTest { assertThat(polarService.getSpeedRegressionsPerAngle().size(), is(68)); assertThat(polarService.getCubicRegressionsPerCourse().size(), is(4)); - assertThat(polarService.getFixCointPerBoatClass().get(boatClass), is(9330L)); + assertThat(polarService.getFixCountPerBoatClass().get(boatClass), is(9330L)); // presuming that if downwind functions & regression collections' size are correct then any other thing is // imported correctly assertThat(polarService.getAngleRegressionFunction(boatClass, LegType.DOWNWIND), is(angleDownwindFunction)); diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 56b91be5abc..a6b97c92076 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -177,7 +177,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT return result; } - private SpeedWithBearingWithConfidence getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart, + @Override + public SpeedWithBearingWithConfidence getClosestTwaTws(ManeuverType type, Speed speedAtManeuverStart, double courseChangeDeg, BoatClass boatClass) { assert type == ManeuverType.TACK || type == ManeuverType.JIBE; double minDiff = Double.MAX_VALUE; @@ -186,8 +187,9 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT boatClass, speedAtManeuverStart, type == ManeuverType.TACK ? LegType.UPWIND : LegType.DOWNWIND, type == ManeuverType.TACK ? courseChangeDeg >= 0 ? Tack.PORT : Tack.STARBOARD : courseChangeDeg >= 0 ? Tack.STARBOARD : Tack.PORT)) { - double diff = Math.abs(trueWindSpeedAndAngle.getObject().getBearing().getDegrees() * 2) - - Math.abs(courseChangeDeg); + double targetManeuverAngle = getManeuverAngleInDegreesFromTwa( + trueWindSpeedAndAngle.getObject().getBearing().getDegrees(), type); + double diff = Math.abs(targetManeuverAngle) - Math.abs(courseChangeDeg); if (diff < minDiff) { minDiff = diff; closestTwsTwa = trueWindSpeedAndAngle; @@ -235,11 +237,21 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT } SpeedWithBearingWithConfidence speed = polarDataMiner.getAverageSpeedAndCourseOverGround(boatClass, windSpeed, legType); - Bearing bearing = new DegreeBearingImpl(speed.getObject().getBearing().getDegrees() * 2); + Bearing bearing = new DegreeBearingImpl(getManeuverAngleInDegreesFromTwa(speed.getObject().getBearing().getDegrees(), maneuverType)); BearingWithConfidence bearingWithConfidence = new BearingWithConfidenceImpl(bearing, speed.getConfidence(), null); return bearingWithConfidence; } + + public double getManeuverAngleInDegreesFromTwa(double twa, ManeuverType maneuverType) { + if (maneuverType == ManeuverType.TACK) { + return Math.abs(twa) * 2; + } + if (maneuverType == ManeuverType.JIBE) { + return (180 - Math.abs(twa)) * 2; + } + throw new IllegalArgumentException("ManeuverType needs to be tack or jibe."); + } @Override public void insertExistingFixes(TrackedRace trackedRace) { @@ -388,7 +400,8 @@ public class PolarDataServiceImpl implements ReplicablePolarService, ClearStateT return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getRegressionsImpl(); } - public Map getFixCointPerBoatClass() { + @Override + public Map getFixCountPerBoatClass() { return polarDataMiner.getSpeedRegressionPerAngleClusterProcessor().getFixCountPerBoatClass(); } diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index 4aec93bf628..258bc9a4ef9 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -107,8 +107,7 @@ public class AngleAndSpeedRegression implements Serializable { boolean angleFound; try { angle = angleRegression.getOrCreatePolynomialFunction().value(windSpeedCandidateInKnots); - if ((tack == Tack.PORT && legType == LegType.UPWIND) - || (tack == Tack.STARBOARD && legType == LegType.DOWNWIND)) { + if (tack == Tack.PORT) { angle = -angle; } angleFound = true; diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrackImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrackImpl.java index d65ca72cdaf..54188f2d1d0 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrackImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/windestimation/ManeuverBasedWindEstimationTrackImpl.java @@ -181,7 +181,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl { private final double maneuverAngleDeg; private final SpeedWithBearing speedAtManeuverStart; private final Bearing middleManeuverCourse; - private final Distance maneuverLoss; + private final Distance maneuverLossDistanceLost; private Pair>[] likelihoodAndTWSBasedOnSpeedAndAngleCache; private ScalableBearingAndScalableDouble scalableMiddleManeuverCourseAndManeuverAngleDegCache; @@ -195,7 +195,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl { this.speedAtManeuverStart = maneuver.getSpeedWithBearingBefore(); this.middleManeuverCourse = maneuver.getSpeedWithBearingBefore().getBearing() .middle(maneuver.getSpeedWithBearingAfter().getBearing()); - this.maneuverLoss = maneuver.getManeuverLoss(); + this.maneuverLossDistanceLost = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getProjectedDistanceLost(); @SuppressWarnings("unchecked") Pair>[] myLikelihoodAndTWSBasedOnSpeedAndAngleCache = (Pair>[]) new Pair[ManeuverType .values().length]; @@ -269,8 +269,8 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl { return result; } - public Distance getManeuverLoss() { - return maneuverLoss; + public Distance getManeuverLossDistanceLost() { + return maneuverLossDistanceLost; } /** @@ -322,7 +322,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl { result.append("\t"); result.append(mc.getMiddleManeuverCourse().getDegrees()); result.append("\t"); - result.append(mc.getManeuverLoss() == null ? 0.0 : mc.getManeuverLoss().getMeters()); + result.append(mc.getManeuverLossDistanceLost() == null ? 0.0 : mc.getManeuverLossDistanceLost().getMeters()); result.append("\t"); result.append(mc.getLikelihoodAndTWSBasedOnSpeedAndAngle(ManeuverType.TACK).getA()); result.append("\t"); diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java index ff856f93adb..a0bae3b1e50 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayLeaderboardView.java @@ -3,21 +3,16 @@ package com.sap.sailing.selenium.pages.autoplay; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import com.sap.sailing.selenium.core.BySeleniumId; -import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.PageArea; import com.sap.sailing.selenium.pages.leaderboard.LeaderboardTablePO; public class AutoPlayLeaderboardView extends PageArea { - @FindBy(how = BySeleniumId.class, using = "LeaderboardCellTable") - private WebElement leaderboard; - public AutoPlayLeaderboardView(WebDriver driver, WebElement element) { super(driver, element); } public LeaderboardTablePO getLeaderBoard() { - return new LeaderboardTablePO(this.driver, leaderboard); + return new LeaderboardTablePO(this.driver, waitForElementBySeleniumId(driver, "LeaderboardCellTable", 20)); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java index bb1dbee427e..f36ebdfeb09 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/autoplay/AutoPlayUpcomingView.java @@ -1,22 +1,49 @@ package com.sap.sailing.selenium.pages.autoplay; +import java.util.List; + +import org.openqa.selenium.StaleElementReferenceException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; -import com.sap.sailing.selenium.core.BySeleniumId; -import com.sap.sailing.selenium.core.FindBy; +import com.google.common.base.Function; import com.sap.sailing.selenium.pages.PageArea; public class AutoPlayUpcomingView extends PageArea { - @FindBy(how = BySeleniumId.class, using = "upComingDataLabel") - private WebElement upComingDataLabel; + /** + * The upcoming view alternates with the leaderboard view. If the leaderboard is shown we need to wait a little bit + * longer to see the upcoming view again. + */ + private static final int TIMEOUT_FOR_UPCOMING_VIEW = 300; public AutoPlayUpcomingView(WebDriver driver, WebElement element) { super(driver, element); } public String getText() { - return upComingDataLabel.getText(); + return new WebDriverWait(driver, TIMEOUT_FOR_UPCOMING_VIEW).until(new Function() { + @Override + public String apply(WebDriver t) { + try { + final List elements = findElementsBySeleniumId("upComingDataLabel"); + final String result; + if (!elements.isEmpty()) { + final WebElement upComingDataLabel = elements.get(0); + final String currentRawText = upComingDataLabel.getText(); + result = currentRawText == null || currentRawText.trim().isEmpty() ? null : currentRawText; + } else { + result = null; + } + return result; + } catch (StaleElementReferenceException e) { + // AutoPlay might switch the views while calling getText + // While the element is out of the viewport you can't read the text + // In this case it needs to be checked in the next loop again + return null; + } + } + }); } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java index 2528f99169a..881004db2f1 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/leaderboard/LeaderboardTablePO.java @@ -7,7 +7,9 @@ import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.WebDriverWait; +import com.google.common.base.Predicate; import com.sap.sailing.selenium.pages.common.CSSHelper; import com.sap.sailing.selenium.pages.gwt.CellTablePO; @@ -193,4 +195,13 @@ public class LeaderboardTablePO extends CellTablePO { private WebElement getExpansionButton(WebElement header) { return header.findElement(By.xpath(EXPANSION_BUTTON_XPATH)); } + + public void waitForLeaderboardToHaveData() { + new WebDriverWait(driver, 30).until(new Predicate() { + @Override + public boolean apply(WebDriver driver) { + return !getEntries().isEmpty(); + } + }); + } } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java index f10f3245714..52db4e94fde 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/autoplay/TestAutoPlay.java @@ -94,12 +94,6 @@ public class TestAutoPlay extends AbstractSeleniumTest { assertTrue("Url does not contain proper type " + url, url.contains("autoplayType=SIXTYINCH")); assertTrue("Url does not contain proper name " + url, url.contains("name=BMW+Cup+(J80)")); AutoPlayUpcomingView autoplayPage = page.goToAutoPlaySixtyInchUrl(getWebDriver(), url); - // give some extra time to load the leaderboard and finish the animation - try { - Thread.sleep(15000); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } String nextUpText = autoplayPage.getText(); assertEquals("There are no further races starting soon", nextUpText); } @@ -123,13 +117,8 @@ public class TestAutoPlay extends AbstractSeleniumTest { assertTrue("Url does not contain proper title " + url, url.contains("lbwh.saph.title=Leaderboard%3A+BMW+Cup+(J80)")); assertTrue("Url does not contain proper name " + url, url.contains("name=BMW+Cup+(J80)")); AutoPlayLeaderboardView autoplayPage = page.goToAutoPlayClassicUrl(getWebDriver(), url); - // give some extra time to load the leaderboard and finish the animation - try { - Thread.sleep(15000); - } catch (InterruptedException e1) { - e1.printStackTrace(); - } LeaderboardTablePO leaderBoard = autoplayPage.getLeaderBoard(); + leaderBoard.waitForLeaderboardToHaveData(); List races = leaderBoard.getRaceNames(); Assert.assertTrue(races.contains("R1")); Assert.assertTrue(races.contains("R2")); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/RegattaConfigurationJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/RegattaConfigurationJsonDeserializer.java index 441db44ff05..c8522a4c2d7 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/RegattaConfigurationJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/impl/RegattaConfigurationJsonDeserializer.java @@ -15,9 +15,10 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.serialization.impl.RegattaConfigurationJsonSerializer; +import com.sap.sse.common.impl.MillisecondsDurationImpl; public class RegattaConfigurationJsonDeserializer implements JsonDeserializer { - + public static RegattaConfigurationJsonDeserializer create() { return new RegattaConfigurationJsonDeserializer(RRS26ConfigurationJsonDeserializer.create(), SWCStartConfigurationJsonDeserializer.create(), GateStartConfigurationJsonDeserializer.create(), ESSConfigurationJsonDeserializer.create(), @@ -30,8 +31,8 @@ public class RegattaConfigurationJsonDeserializer implements JsonDeserializer protected final JsonDeserializer fixedMarkPassingEventDeserializer; protected final JsonDeserializer suppressedMarkPassingsDeserializer; protected final JsonDeserializer startOfTrackingDeserializer; - protected final JsonDeserializer endOfTrackingDeserializer; protected final JsonDeserializer useCompetitorsFromRaceLogDeserializer; + protected final JsonDeserializer endOfTrackingDeserializer; public RaceLogEventDeserializer(JsonDeserializer flagEventDeserializer, JsonDeserializer startTimeEventDeserializer, @@ -208,7 +208,7 @@ public class RaceLogEventDeserializer implements JsonDeserializer } else if (type.equals(RaceLogEndOfTrackingEventSerializer.VALUE_CLASS)){ return endOfTrackingDeserializer; } else if (type.equals(RaceLogUseCompetitorsFromRaceLogEventSerializer.VALUE_CLASS)){ - return endOfTrackingDeserializer; + return useCompetitorsFromRaceLogDeserializer; } throw new JsonDeserializationException(String.format("There is no deserializer defined for event type %s.", diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/RegattaConfigurationJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/RegattaConfigurationJsonSerializer.java index dd468958a2a..36d324328ff 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/RegattaConfigurationJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/RegattaConfigurationJsonSerializer.java @@ -14,15 +14,16 @@ public class RegattaConfigurationJsonSerializer implements JsonSerializer rrs26Serializer; private final JsonSerializer swcStartSerializer; private final JsonSerializer gateStartSerializer; @@ -47,9 +48,15 @@ public class RegattaConfigurationJsonSerializer implements JsonSerializer, Rac private final JsonSerializer suppressedMarkPassingsEventSerializer; private final JsonSerializer dependentStartTimeEventSerializer; private final JsonSerializer startOfTrackingEventSerializer; - private final JsonSerializer endOfTrackingEventSerializer; private final JsonSerializer useCompetitorsFromRaceLogEventSerializer; + private final JsonSerializer endOfTrackingEventSerializer; private JsonSerializer chosenSerializer; @@ -132,8 +132,8 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac JsonSerializer suppressedMarkPassingsSerializer, JsonSerializer dependentStartTimeEventSerializer, JsonSerializer startOfTrackingEventSerializer, - JsonSerializer endOfTrackingEventSerializer, - JsonSerializer useCompetitorsFromRaceLogEventSerializer) { + JsonSerializer useCompetitorsFromRaceLogEventSerializer, + JsonSerializer endOfTrackingEventSerializer) { this.flagEventSerializer = flagEventSerializer; this.startTimeSerializer = startTimeSerializer; this.raceStatusSerializer = raceStatusSerializer; diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/deserialization/test/racelog/RaceLogEventDeserializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/deserialization/test/racelog/RaceLogEventDeserializerTest.java new file mode 100644 index 00000000000..1a9a1138fe2 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/deserialization/test/racelog/RaceLogEventDeserializerTest.java @@ -0,0 +1,129 @@ +package com.sap.sailing.server.gateway.deserialization.test.racelog; + +import static org.junit.Assert.assertEquals; +import java.util.UUID; + +import org.json.simple.JSONObject; +import org.junit.Test; +import org.mockito.Mockito; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFlagEventImpl; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.impl.DynamicCompetitor; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.CompetitorJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogEndOfTrackingEventDeserializer; +import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogEventDeserializer; +import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogFlagEventDeserializer; +import com.sap.sailing.server.gateway.deserialization.racelog.impl.RaceLogUseCompetitorsFromRaceLogEventDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogEventSerializer; +import com.sap.sse.common.TimePoint; + +public class RaceLogEventDeserializerTest { + + JsonDeserializer competitorDeserializer = CompetitorJsonDeserializer.create(DomainFactory.INSTANCE); + + protected RaceLogFlagEventDeserializer mockitoRaceLogFlagEventDeserializer = Mockito.spy(new RaceLogFlagEventDeserializer(competitorDeserializer)); + protected RaceLogUseCompetitorsFromRaceLogEventDeserializer mockitoRaceLogUseCompetitorsFromRaceLogEventDeserializer = Mockito.spy(new RaceLogUseCompetitorsFromRaceLogEventDeserializer(competitorDeserializer)); + protected RaceLogEndOfTrackingEventDeserializer mockitoRaceLogEndOfTrackingEventDeserializer = Mockito.spy(new RaceLogEndOfTrackingEventDeserializer(competitorDeserializer)); + + public class InnerRaceLogEventDeserializer extends RaceLogEventDeserializer{ + + + public InnerRaceLogEventDeserializer() { + super(mockitoRaceLogFlagEventDeserializer, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, + mockitoRaceLogUseCompetitorsFromRaceLogEventDeserializer, + mockitoRaceLogEndOfTrackingEventDeserializer); + } + } + + //Used for creating RaceLogEvents which get serialized, then deserialized and afterwards compared + private AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); + private TimePoint timePoint = TimePoint.BeginningOfTime; + private TimePoint timePoint2 = TimePoint.EndOfTime; + private InnerRaceLogEventDeserializer deserializer = new InnerRaceLogEventDeserializer(); + + + @Test + public void testSerializationAndDeserializationForRaceLogFlagEvent() throws Exception{ + RaceLogFlagEventImpl originalEvent = new RaceLogFlagEventImpl(timePoint, author, 0, Flags.BLACK, Flags.ESSONE, false); + RaceLogEventSerializer serializer = (RaceLogEventSerializer) RaceLogEventSerializer.create(CompetitorJsonSerializer.create()); + JSONObject object = serializer.serialize(originalEvent); + + RaceLogEvent raceLogEvent = deserializer.deserialize(object); + + //assert correct deserializer was used + Mockito.verify(mockitoRaceLogFlagEventDeserializer).deserialize(object); + + RaceLogFlagEventImpl newEvent = (RaceLogFlagEventImpl) raceLogEvent; + + //assert raceLogEvent has correct values + assertEquals(originalEvent.getTimePoint().toString(), newEvent.getTimePoint().toString()); + assertEquals(originalEvent.getAuthor().toString(), newEvent.getAuthor().toString()); + assertEquals(originalEvent.getPassId(), newEvent.getPassId()); + assertEquals(originalEvent.getClass(), newEvent.getClass()); + assertEquals(originalEvent.getId(), newEvent.getId()); + assertEquals(originalEvent.getShortInfo(), newEvent.getShortInfo()); + + assertEquals(originalEvent.getLowerFlag(), newEvent.getLowerFlag()); + assertEquals(originalEvent.getUpperFlag(), newEvent.getUpperFlag()); + assertEquals(originalEvent.getCreatedAt(), newEvent.getCreatedAt()); + } + + @Test + public void testSerializationAndDeserializationForRaceLogEndOfTrackingEvent() throws Exception{ + RaceLogEndOfTrackingEventImpl originalEvent = new RaceLogEndOfTrackingEventImpl(timePoint, timePoint2, author, UUID.randomUUID(), 3); + RaceLogEventSerializer serializer = (RaceLogEventSerializer) RaceLogEventSerializer.create(CompetitorJsonSerializer.create()); + JSONObject object = serializer.serialize(originalEvent); + + RaceLogEvent raceLogEvent = deserializer.deserialize(object); + + //assert correct deserializer was used + Mockito.verify(mockitoRaceLogEndOfTrackingEventDeserializer).deserialize(object); + + RaceLogEndOfTrackingEventImpl newEvent = (RaceLogEndOfTrackingEventImpl) raceLogEvent; + + //assert raceLogEvent has correct value + assertEquals(originalEvent.getTimePoint().toString(), newEvent.getTimePoint().toString()); + assertEquals(originalEvent.getAuthor().toString(), newEvent.getAuthor().toString()); + assertEquals(originalEvent.getPassId(), newEvent.getPassId()); + assertEquals(originalEvent.getClass(), newEvent.getClass()); + assertEquals(originalEvent.getId(), newEvent.getId()); + assertEquals(originalEvent.getShortInfo(), newEvent.getShortInfo()); + + assertEquals(originalEvent.getCreatedAt(), newEvent.getCreatedAt()); + } + + @Test + public void testSerializationAndDeserializationForRaceLogUseCompetitorsFromRaceLogEvent() throws Exception{ + RaceLogUseCompetitorsFromRaceLogEventImpl originalEvent = new RaceLogUseCompetitorsFromRaceLogEventImpl(timePoint, author, timePoint2, UUID.randomUUID(), 3); + RaceLogEventSerializer serializer = (RaceLogEventSerializer) RaceLogEventSerializer.create(CompetitorJsonSerializer.create()); + JSONObject object = serializer.serialize(originalEvent); + + RaceLogEvent raceLogEvent = deserializer.deserialize(object); + + //assert correct deserializer was used + Mockito.verify(mockitoRaceLogUseCompetitorsFromRaceLogEventDeserializer).deserialize(object); + + RaceLogUseCompetitorsFromRaceLogEventImpl newEvent = (RaceLogUseCompetitorsFromRaceLogEventImpl) raceLogEvent; + + //assert raceLogEvent has correct value + assertEquals(originalEvent.getTimePoint().toString(), newEvent.getTimePoint().toString()); + assertEquals(originalEvent.getAuthor().toString(), newEvent.getAuthor().toString()); + assertEquals(originalEvent.getPassId(), newEvent.getPassId()); + assertEquals(originalEvent.getClass(), newEvent.getClass()); + assertEquals(originalEvent.getId(), newEvent.getId()); + assertEquals(originalEvent.getShortInfo(), newEvent.getShortInfo()); + + assertEquals(originalEvent.getCreatedAt(), newEvent.getCreatedAt()); + + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java index 779585de9d7..fa01070bb1b 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/EstimationDataSerializationDeserializationTest.java @@ -28,14 +28,14 @@ import com.sap.sailing.server.gateway.deserialization.impl.CompleteManeuverCurve import com.sap.sailing.server.gateway.deserialization.impl.DetailedBoatClassJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.ManeuverMainCurveWithEstimationDataJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.ManeuverWindJsonDeserializer; import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer; import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.PositionJsonSerializer; -import com.sap.sailing.server.gateway.serialization.impl.WindJsonSerializer; import com.sap.sse.common.Bearing; import com.sap.sse.common.Distance; import com.sap.sse.common.Duration; @@ -120,34 +120,40 @@ public class EstimationDataSerializationDeserializationTest { longestIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix, intervalBetweenFirstFixOfCurveAndPreviousFix); - MillisecondsTimePoint windTimePoint = new MillisecondsTimePoint(dateFormat.parse("06/23/2011-15:28:25")); SpeedWithBearing windSpeedWithBearing = new KnotSpeedWithBearingImpl(2, new DegreeBearingImpl(340)); - DegreePosition windPosition = new DegreePosition(54.325246, 10.148556); - Wind wind = new WindImpl(windPosition, windTimePoint, windSpeedWithBearing); - DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556); + Wind wind = new WindImpl(maneuverPosition, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing); int jibingCount = 203; int tackingCount = 12345; boolean maneuverStartsByRunningAwayFromWind = true; Bearing relativeBearingToNextMarkBeforeManeuver = new DegreeBearingImpl(202.23); Bearing relativeBearingToNextMarkAfterManeuver = new DegreeBearingImpl(10.01); boolean markPassing = true; + Distance closestDistanceToMark = new MeterDistance(3.0); + Double deviationFromTargetTackAngle = 23.30; + Double deviationFromTargetJibeAngle = 22.30; CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl( maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver, - relativeBearingToNextMarkAfterManeuver, markPassing); + relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, + deviationFromTargetTackAngle, deviationFromTargetJibeAngle); CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), - new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer()); + new ManeuverWindJsonSerializer(), new PositionJsonSerializer()); JSONObject json = serializer.serialize(toSerialize); CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer( new ManeuverMainCurveWithEstimationDataJsonDeserializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(), - new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer()); + new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer()); CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json); + assertEquals(deviationFromTargetJibeAngle, + deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); + assertEquals(deviationFromTargetTackAngle, + deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); + assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark()); assertEquals(maneuverPosition, deserialized.getPosition()); assertEquals(tackingCount, deserialized.getTackingCount()); assertEquals(jibingCount, deserialized.getJibingCount()); @@ -156,8 +162,8 @@ public class EstimationDataSerializationDeserializationTest { assertEquals(relativeBearingToNextMarkBeforeManeuver, deserialized.getRelativeBearingToNextMarkBeforeManeuver()); assertEquals(relativeBearingToNextMarkAfterManeuver, deserialized.getRelativeBearingToNextMarkAfterManeuver()); - assertEquals(windTimePoint, deserialized.getWind().getTimePoint()); - assertEquals(windPosition, deserialized.getWind().getPosition()); + assertEquals(wind.getTimePoint(), deserialized.getWind().getTimePoint()); + assertEquals(wind.getPosition(), deserialized.getWind().getPosition()); assertEquals(windSpeedWithBearing.getBearing(), deserialized.getWind().getBearing()); assertEquals(windSpeedWithBearing.getMetersPerSecond(), deserialized.getWind().getMetersPerSecond(), DELTA); @@ -303,22 +309,31 @@ public class EstimationDataSerializationDeserializationTest { Bearing relativeBearingToNextMarkAfterManeuver = null; boolean markPassing = false; DegreePosition maneuverPosition = new DegreePosition(50.325246, 11.148556); + Distance closestDistanceToMark = null; + Double deviationFromTargetTackAngle = null; + Double deviationFromTargetJibeAngle = null; CompleteManeuverCurveWithEstimationData toSerialize = new CompleteManeuverCurveWithEstimationDataImpl( maneuverPosition, mainCurve, curve, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, relativeBearingToNextMarkBeforeManeuver, - relativeBearingToNextMarkAfterManeuver, markPassing); + relativeBearingToNextMarkAfterManeuver, markPassing, closestDistanceToMark, + deviationFromTargetTackAngle, deviationFromTargetJibeAngle); CompleteManeuverCurveWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurveWithEstimationDataJsonSerializer( new ManeuverMainCurveWithEstimationDataJsonSerializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), - new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer()); + new ManeuverWindJsonSerializer(), new PositionJsonSerializer()); JSONObject json = serializer.serialize(toSerialize); CompleteManeuverCurveWithEstimationDataJsonDeserializer deserializer = new CompleteManeuverCurveWithEstimationDataJsonDeserializer( new ManeuverMainCurveWithEstimationDataJsonDeserializer(), new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer(), - new WindJsonDeserializer(new PositionJsonDeserializer()), new PositionJsonDeserializer()); + new ManeuverWindJsonDeserializer(), new PositionJsonDeserializer()); CompleteManeuverCurveWithEstimationData deserialized = deserializer.deserialize(json); + assertEquals(deviationFromTargetJibeAngle, + deserialized.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); + assertEquals(deviationFromTargetTackAngle, + deserialized.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); + assertEquals(closestDistanceToMark, deserialized.getDistanceToClosestMark()); assertEquals(maneuverPosition, deserialized.getPosition()); assertEquals(tackingCount, deserialized.getTackingCount()); assertEquals(jibingCount, deserialized.getJibingCount()); diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java index 6c7dfe3c4b0..886f0e3c745 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/CompleteManeuverCurveWithEstimationDataJsonDeserializer.java @@ -3,7 +3,10 @@ package com.sap.sailing.server.gateway.deserialization.impl; import org.json.simple.JSONObject; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.impl.WindImpl; import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationData; import com.sap.sailing.domain.maneuverdetection.ManeuverMainCurveWithEstimationData; @@ -12,6 +15,7 @@ import com.sap.sailing.server.gateway.deserialization.JsonDeserializationExcepti import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer; import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; import com.sap.sse.common.impl.DegreeBearingImpl; /** @@ -24,13 +28,13 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer private final ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer; private final ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer; - private final WindJsonDeserializer windDeserializer; + private final ManeuverWindJsonDeserializer windDeserializer; private final PositionJsonDeserializer positionDeserializer; public CompleteManeuverCurveWithEstimationDataJsonDeserializer( ManeuverMainCurveWithEstimationDataJsonDeserializer mainCurveDeserializer, ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonDeserializer curveWithUnstableCourseAndSpeedDeserializer, - WindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) { + ManeuverWindJsonDeserializer windDeserializer, PositionJsonDeserializer positionDeserializer) { this.mainCurveDeserializer = mainCurveDeserializer; this.curveWithUnstableCourseAndSpeedDeserializer = curveWithUnstableCourseAndSpeedDeserializer; this.windDeserializer = windDeserializer; @@ -48,7 +52,9 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer .deserialize((JSONObject) object.get( CompleteManeuverCurveWithEstimationDataJsonSerializer.CURVE_WITH_UNSTABLE_COURSE_AND_SPEED)); JSONObject windJson = (JSONObject) object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.WIND); - Wind wind = windJson == null ? null : windDeserializer.deserialize(windJson); + SpeedWithBearing windSpeedWithBearing = windJson == null ? null : windDeserializer.deserialize(windJson); + Wind wind = windSpeedWithBearing == null ? null + : new WindImpl(position, mainCurve.getTimePointOfMaxTurningRate(), windSpeedWithBearing); Integer tackingCount = getInteger( object.get(CompleteManeuverCurveWithEstimationDataJsonSerializer.TACKING_COUNT)); Integer jibingCount = getInteger( @@ -59,16 +65,28 @@ public class CompleteManeuverCurveWithEstimationDataJsonDeserializer CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER); Double relativeBearingToNextMarkAfterManeuver = (Double) object.get( CompleteManeuverCurveWithEstimationDataJsonSerializer.RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER); + Double closestDistanceToMarkInMeters = (Double) object + .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.CLOSEST_DISTANCE_TO_MARK); + Double deviationFromTargetTackAngle = (Double) object + .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_TACK_ANGLE); + Double deviationFromTargetJibeAngle = (Double) object + .get(CompleteManeuverCurveWithEstimationDataJsonSerializer.DEVIATION_FROM_TARGET_JIBE_ANGLE); return new CompleteManeuverCurveWithEstimationDataImpl(position, mainCurve, curveWithUnstableCourseAndSpeed, wind, tackingCount, jibingCount, maneuverStartsByRunningAwayFromWind, convertBearing(relativeBearingToNextMarkBeforeManeuver), - convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing); + convertBearing(relativeBearingToNextMarkAfterManeuver), markPassing, + convertDistance(closestDistanceToMarkInMeters), deviationFromTargetTackAngle, + deviationFromTargetJibeAngle); } private Bearing convertBearing(Double degrees) { return degrees == null ? null : new DegreeBearingImpl(degrees); } + private Distance convertDistance(Double meters) { + return meters == null ? null : new MeterDistance(meters); + } + public static Integer getInteger(Object object) { if (object == null) { return null; diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java new file mode 100644 index 00000000000..ab20cd8fee8 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/ManeuverWindJsonDeserializer.java @@ -0,0 +1,29 @@ +package com.sap.sailing.server.gateway.deserialization.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.impl.DegreeBearingImpl; + +/** + * + * @author Vladislav Chumak (D069712) + * + */ +public class ManeuverWindJsonDeserializer implements JsonDeserializer { + + public SpeedWithBearing deserialize(JSONObject object) throws JsonDeserializationException { + + Double directionInTrueDegrees = (Double) object.get(ManeuverWindJsonSerializer.DIRECTION_IN_TRUE_DEGREES); + Double speedInKnots = (Double) object.get(ManeuverWindJsonSerializer.SPEED_IN_KNOTS); + Bearing degreeBearing = new DegreeBearingImpl(directionInTrueDegrees); + SpeedWithBearing speedBearing = new KnotSpeedWithBearingImpl(speedInKnots, degreeBearing); + return speedBearing; + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackElementsJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackElementsJsonSerializer.java new file mode 100644 index 00000000000..a1f57a3e224 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackElementsJsonSerializer.java @@ -0,0 +1,20 @@ +package com.sap.sailing.server.gateway.serialization.impl; + +import org.json.simple.JSONArray; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.TimePoint; + +/** + * + * @author Vladislav Chumak (D069712) + * @see CompetitorTrackWithEstimationDataJsonSerializer + */ +public interface CompetitorTrackElementsJsonSerializer { + + JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to, + TrackTimeInfo trackTimeInfo); + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java new file mode 100644 index 00000000000..eb2089ea0d5 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorTrackWithEstimationDataJsonSerializer.java @@ -0,0 +1,144 @@ +package com.sap.sailing.server.gateway.serialization.impl; + +import java.util.NavigableSet; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo; +import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsDurationImpl; + +/** + * + * @author Vladislav Chumak (D069712) + * + */ +public class CompetitorTrackWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer { + public static final String elements = "elements"; + public static final String BOAT_CLASS = "boatClass"; + public static final String COMPETITOR_NAME = "competitorName"; + public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds"; + public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters"; + public static final String START_TIME_POINT = "startUnixTime"; + public static final String END_TIME_POINT = "endUnixTime"; + public static final String FIXES_COUNT_FOR_POLARS = "fixesCountForPolars"; + public static final String MARK_PASSINGS_COUNT = "markPassingsCount"; + public static final String WAYPOINTS_COUNT = "waypointsCount"; + + private final BoatClassJsonSerializer boatClassJsonSerializer; + private final CompetitorTrackElementsJsonSerializer elementsJsonSerializer; + private final PolarDataService polarDataService; + private final Integer startBeforeStartLineInSeconds; + private final Integer endBeforeStartLineInSeconds; + private final Integer startAfterFinishLineInSeconds; + private final Integer endAfterFinishLineInSeconds; + + public CompetitorTrackWithEstimationDataJsonSerializer(PolarDataService polarDataService, + BoatClassJsonSerializer boatClassJsonSerializer, + CompetitorTrackElementsJsonSerializer elementsJsonSerializer, Integer startBeforeStartLineInSeconds, + Integer endBeforeStartLineInSeconds, Integer startAfterFinishLineInSeconds, + Integer endAfterFinishLineInSeconds) { + this.polarDataService = polarDataService; + this.boatClassJsonSerializer = boatClassJsonSerializer; + this.elementsJsonSerializer = elementsJsonSerializer; + this.startBeforeStartLineInSeconds = startBeforeStartLineInSeconds; + this.endBeforeStartLineInSeconds = endBeforeStartLineInSeconds; + this.startAfterFinishLineInSeconds = startAfterFinishLineInSeconds; + this.endAfterFinishLineInSeconds = endAfterFinishLineInSeconds; + } + + @Override + public JSONObject serialize(TrackedRace trackedRace) { + final JSONObject result = new JSONObject(); + JSONArray byCompetitorJson = new JSONArray(); + result.put(BYCOMPETITOR, byCompetitorJson); + for (Competitor competitor : trackedRace.getRace().getCompetitors()) { + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo(); + TimePoint from = null; + TimePoint to = null; + if (startBeforeStartLineInSeconds != null) { + from = trackTimeInfo.getTrackStartTimePoint() + .minus(new MillisecondsDurationImpl(startBeforeStartLineInSeconds * 1000L)); + } else if (startAfterFinishLineInSeconds != null) { + from = trackTimeInfo.getTrackEndTimePoint() + .plus(new MillisecondsDurationImpl(startAfterFinishLineInSeconds * 1000L)); + } else { + from = trackTimeInfo.getTrackStartTimePoint(); + } + if (endAfterFinishLineInSeconds != null) { + to = trackTimeInfo.getTrackEndTimePoint() + .plus(new MillisecondsDurationImpl(endAfterFinishLineInSeconds * 1000L)); + } else if (endBeforeStartLineInSeconds != null) { + to = trackTimeInfo.getTrackStartTimePoint() + .minus(new MillisecondsDurationImpl(endBeforeStartLineInSeconds * 1000L)); + } else { + to = trackTimeInfo.getTrackEndTimePoint(); + } + if (trackTimeInfo != null) { + final JSONObject forCompetitorJson = new JSONObject(); + byCompetitorJson.add(forCompetitorJson); + forCompetitorJson.put(COMPETITOR_NAME, competitor.getName()); + forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer + .serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass())); + forCompetitorJson.put(FIXES_COUNT_FOR_POLARS, getFixesCountForPolars(trackedRace, competitor)); + Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor) + .getAverageIntervalBetweenFixes(); + forCompetitorJson.put(AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS, + averageIntervalBetweenFixes == null ? 0 : averageIntervalBetweenFixes.asSeconds()); + GPSFixTrack track = trackedRace.getTrack(competitor); + Double distanceTravelledInMeters = null; + if (trackTimeInfo.getTrackStartTimePoint() != null && trackTimeInfo.getTrackEndTimePoint() != null) { + distanceTravelledInMeters = track.getDistanceTraveled(trackTimeInfo.getTrackStartTimePoint(), + trackTimeInfo.getTrackEndTimePoint()).getMeters(); + } + forCompetitorJson.put(DISTANCE_TRAVELLED_IN_METERS, distanceTravelledInMeters); + forCompetitorJson.put(START_TIME_POINT, trackTimeInfo.getTrackStartTimePoint() == null ? null + : trackTimeInfo.getTrackStartTimePoint().asMillis()); + forCompetitorJson.put(END_TIME_POINT, trackTimeInfo.getTrackEndTimePoint() == null ? null + : trackTimeInfo.getTrackEndTimePoint().asMillis()); + forCompetitorJson.put(MARK_PASSINGS_COUNT, getMarkPassingsCount(trackedRace, competitor)); + forCompetitorJson.put(WAYPOINTS_COUNT, getWaypointsCount(trackedRace)); + forCompetitorJson.put(elements, + elementsJsonSerializer.serialize(trackedRace, competitor, from, to, trackTimeInfo)); + } + } + return result; + } + + private int getMarkPassingsCount(TrackedRace trackedRace, Competitor competitor) { + int markPassingsCount = 0; + NavigableSet markPassings = trackedRace.getMarkPassings(competitor, false); + trackedRace.lockForRead(markPassings); + try { + markPassingsCount = Util.size(markPassings); + } finally { + trackedRace.unlockAfterRead(markPassings); + } + return markPassingsCount; + } + + private long getFixesCountForPolars(TrackedRace trackedRace, Competitor competitor) { + BoatClass boatClass = trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass(); + Long fixesCountForBoatPolars = polarDataService.getFixCountPerBoatClass().get(boatClass); + return fixesCountForBoatPolars == null ? 0L : fixesCountForBoatPolars; + } + + private int getWaypointsCount(TrackedRace trackedRace) { + Iterable waypoints = trackedRace.getRace().getCourse().getWaypoints(); + return Util.size(waypoints); + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java index c904b298a51..e1b807cf388 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurveWithEstimationDataJsonSerializer.java @@ -23,16 +23,19 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer public static final String MANEUVER_STARTS_BY_RUNNING_AWAY_FROM_WIND = "maneuverStartsByRunningAwayFromWind"; public static final String RELATIVE_BEARING_TO_NEXT_MARK_BEFORE_MANEUVER = "relativeBearingToNextMarkBeforeManeuver"; public static final String RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER = "relativeBearingToNextMarkAfterManeuver"; + public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters"; + public static final String DEVIATION_FROM_TARGET_TACK_ANGLE = "deviationFromTargetTackAngleInDegrees"; + public static final String DEVIATION_FROM_TARGET_JIBE_ANGLE = "deviationFromTargetJibeAngleInDegrees"; private final ManeuverCurveBoundariesJsonSerializer mainCurveSerializer; private final ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer; - private final WindJsonSerializer windSerializer; + private final ManeuverWindJsonSerializer windSerializer; private final PositionJsonSerializer positionSerializer; public CompleteManeuverCurveWithEstimationDataJsonSerializer( ManeuverCurveBoundariesJsonSerializer mainCurveSerializer, ManeuverCurveBoundariesJsonSerializer curveWithUnstableCourseAndSpeedSerializer, - WindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) { + ManeuverWindJsonSerializer windSerializer, PositionJsonSerializer positionSerializer) { this.mainCurveSerializer = mainCurveSerializer; this.curveWithUnstableCourseAndSpeedSerializer = curveWithUnstableCourseAndSpeedSerializer; this.windSerializer = windSerializer; @@ -59,6 +62,12 @@ public class CompleteManeuverCurveWithEstimationDataJsonSerializer result.put(RELATIVE_BEARING_TO_NEXT_MARK_AFTER_MANEUVER, maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver() == null ? null : maneuverWithEstimationData.getRelativeBearingToNextMarkAfterManeuver().getDegrees()); + result.put(CLOSEST_DISTANCE_TO_MARK, maneuverWithEstimationData.getDistanceToClosestMark() == null ? null + : maneuverWithEstimationData.getDistanceToClosestMark().getMeters()); + result.put(DEVIATION_FROM_TARGET_TACK_ANGLE, + maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetTackAngleInDegrees()); + result.put(DEVIATION_FROM_TARGET_JIBE_ANGLE, + maneuverWithEstimationData.getDeviationOfManeuverAngleFromTargetJibeAngleInDegrees()); return result; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java index 9a0def79beb..01b1e4ac610 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/CompleteManeuverCurvesWithEstimationDataJsonSerializer.java @@ -1,95 +1,75 @@ package com.sap.sailing.server.gateway.serialization.impl; +import java.util.List; +import java.util.stream.Collectors; + import org.json.simple.JSONArray; -import org.json.simple.JSONObject; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.maneuverdetection.CompleteManeuverCurveWithEstimationData; -import com.sap.sailing.domain.maneuverdetection.ManeuverDetector; +import com.sap.sailing.domain.maneuverdetection.ManeuverDetectorWithEstimationDataSupport; import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverSpot; import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo; +import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.tracking.CompleteManeuverCurve; -import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; /** * * @author Vladislav Chumak (D069712) * */ -public class CompleteManeuverCurvesWithEstimationDataJsonSerializer extends AbstractTrackedRaceDataJsonSerializer { - public static final String MANEUVER_CURVES = "maneuverCurves"; - public static final String BOAT_CLASS = "boatClass"; - public static final String COMPETITOR_NAME = "competitorName"; - public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds"; - public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters"; - public static final String START_TIME_POINT = "startUnixTime"; - public static final String END_TIME_POINT = "endUnixTime"; - - private final BoatClassJsonSerializer boatClassJsonSerializer; +public class CompleteManeuverCurvesWithEstimationDataJsonSerializer implements CompetitorTrackElementsJsonSerializer { + private final PolarDataService polarDataService; private final CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer; - public CompleteManeuverCurvesWithEstimationDataJsonSerializer(BoatClassJsonSerializer boatClassJsonSerializer, + public CompleteManeuverCurvesWithEstimationDataJsonSerializer(PolarDataService polarDataService, CompleteManeuverCurveWithEstimationDataJsonSerializer maneuverWithEstimationDataJsonSerializer) { - this.boatClassJsonSerializer = boatClassJsonSerializer; + this.polarDataService = polarDataService; this.maneuverWithEstimationDataJsonSerializer = maneuverWithEstimationDataJsonSerializer; } @Override - public JSONObject serialize(TrackedRace trackedRace) { - final JSONObject result = new JSONObject(); - JSONArray byCompetitorJson = new JSONArray(); - result.put(BYCOMPETITOR, byCompetitorJson); - for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); - TrackTimeInfo trackTimeInfo = maneuverDetector.getTrackTimeInfo(); - if (trackTimeInfo != null) { - final JSONObject forCompetitorJson = new JSONObject(); - byCompetitorJson.add(forCompetitorJson); - forCompetitorJson.put(COMPETITOR_NAME, competitor.getName()); - forCompetitorJson.put(BOAT_CLASS, boatClassJsonSerializer - .serialize(trackedRace.getRace().getBoatOfCompetitor(competitor).getBoatClass())); - final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray(); - for (CompleteManeuverCurveWithEstimationData maneuver : getCompleteManeuverCurvesWithEstimationData( - trackedRace, competitor)) { - completeManeuverCurvesWithEstimationData - .add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver)); - } - forCompetitorJson.put(MANEUVER_CURVES, completeManeuverCurvesWithEstimationData); - Duration averageIntervalBetweenFixes = trackedRace.getTrack(competitor) - .getAverageIntervalBetweenFixes(); - forCompetitorJson.put(AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS, - averageIntervalBetweenFixes == null ? 0 : averageIntervalBetweenFixes.asSeconds()); - GPSFixTrack track = trackedRace.getTrack(competitor); - Double distanceTravelledInMeters = null; - if (trackTimeInfo.getTrackStartTimePoint() != null && trackTimeInfo.getTrackEndTimePoint() != null) { - distanceTravelledInMeters = track.getDistanceTraveled(trackTimeInfo.getTrackStartTimePoint(), - trackTimeInfo.getTrackEndTimePoint()).getMeters(); - } - forCompetitorJson.put(DISTANCE_TRAVELLED_IN_METERS, distanceTravelledInMeters); - forCompetitorJson.put(START_TIME_POINT, trackTimeInfo.getTrackStartTimePoint() == null ? null - : trackTimeInfo.getTrackStartTimePoint().asMillis()); - forCompetitorJson.put(END_TIME_POINT, trackTimeInfo.getTrackEndTimePoint() == null ? null - : trackTimeInfo.getTrackEndTimePoint().asMillis()); - } + public JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to, + TrackTimeInfo trackTimeInfo) { + final JSONArray completeManeuverCurvesWithEstimationData = new JSONArray(); + Iterable completeManeuvers = trackTimeInfo.getTrackStartTimePoint() + .equals(from) && trackTimeInfo.getTrackEndTimePoint().equals(to) + ? getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor) + : getCompleteManeuverCurvesWithEstimationData(trackedRace, competitor, from, to); + for (CompleteManeuverCurveWithEstimationData maneuver : completeManeuvers) { + completeManeuverCurvesWithEstimationData.add(maneuverWithEstimationDataJsonSerializer.serialize(maneuver)); } - return result; + return completeManeuverCurvesWithEstimationData; + } + + private Iterable getCompleteManeuverCurvesWithEstimationData( + TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to) { + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + List maneuverSpots = maneuverDetector.detectManeuvers(from, to); + List maneuverCurves = maneuverSpots.stream() + .map(maneuverSpot -> maneuverSpot.getManeuverCurve()).collect(Collectors.toList()); + ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + maneuverDetector, polarDataService); + Iterable maneuversWithEstimationData = maneuverDetectorWithEstimationData + .getCompleteManeuverCurvesWithEstimationData(maneuverCurves); + return maneuversWithEstimationData; } private Iterable getCompleteManeuverCurvesWithEstimationData( TrackedRace trackedRace, Competitor competitor) { Iterable maneuvers = trackedRace.getManeuvers(competitor, false); - ManeuverDetector maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); - Iterable maneuversWithEstimationData = null; - try { - Iterable maneuverCurves = maneuverDetector.getCompleteManeuverCurves(maneuvers); - maneuversWithEstimationData = maneuverDetector.getCompleteManeuverCurvesWithEstimationData(maneuverCurves); - } catch (Exception e) { - e.printStackTrace(); - } + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + ManeuverDetectorWithEstimationDataSupport maneuverDetectorWithEstimationData = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + maneuverDetector, polarDataService); + Iterable maneuverCurves = maneuverDetectorWithEstimationData + .getCompleteManeuverCurves(maneuvers); + Iterable maneuversWithEstimationData = maneuverDetectorWithEstimationData + .getCompleteManeuverCurvesWithEstimationData(maneuverCurves); return maneuversWithEstimationData; } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java new file mode 100644 index 00000000000..a2148acf2fe --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/GpsFixesWithEstimationDataJsonSerializer.java @@ -0,0 +1,90 @@ +package com.sap.sailing.server.gateway.serialization.impl; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorWithEstimationDataSupportDecoratorImpl; +import com.sap.sailing.domain.maneuverdetection.impl.TrackTimeInfo; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Bearing; +import com.sap.sse.common.Distance; +import com.sap.sse.common.TimePoint; + +/** + * + * @author Vladislav Chumak (D069712) + * + */ +public class GpsFixesWithEstimationDataJsonSerializer implements CompetitorTrackElementsJsonSerializer { + public static final String GPS_FIXES = "gpsFixes"; + public static final String BOAT_CLASS = "boatClass"; + public static final String COMPETITOR_NAME = "competitorName"; + public static final String AVG_INTERVAL_BETWEEN_FIXES_IN_SECONDS = "avgIntervalBetweenFixesInSeconds"; + public static final String DISTANCE_TRAVELLED_IN_METERS = "distanceTravelledInMeters"; + public static final String START_TIME_POINT = "startUnixTime"; + public static final String END_TIME_POINT = "endUnixTime"; + public static final String WIND = "wind"; + public static final String RELATIVE_BEARING_TO_NEXT_MARK = "relativeBearingToNextMark"; + public static final String CLOSEST_DISTANCE_TO_MARK = "closestDistanceToMarkInMeters"; + + private final GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer; + private final boolean addWind; + private final boolean addNextWaypoint; + private final ManeuverWindJsonSerializer windJsonSerializer; + private final Boolean smoothFixes; + + public GpsFixesWithEstimationDataJsonSerializer(GPSFixMovingJsonSerializer gpsFixMovingJsonSerializer, + ManeuverWindJsonSerializer windJsonSerializer, boolean addWind, boolean addNextWaypoint, + Boolean smoothFixes) { + this.gpsFixMovingJsonSerializer = gpsFixMovingJsonSerializer; + this.windJsonSerializer = windJsonSerializer; + this.addWind = addWind; + this.addNextWaypoint = addNextWaypoint; + this.smoothFixes = smoothFixes; + } + + @Override + public JSONArray serialize(TrackedRace trackedRace, Competitor competitor, TimePoint from, TimePoint to, + TrackTimeInfo trackTimeInfo) { + final JSONArray gpsFixesWithEstimationData = new JSONArray(); + ManeuverDetectorImpl maneuverDetector = new ManeuverDetectorImpl(trackedRace, competitor); + ManeuverDetectorWithEstimationDataSupportDecoratorImpl estimationDataSupportDecoratorImpl = new ManeuverDetectorWithEstimationDataSupportDecoratorImpl( + maneuverDetector, null); + GPSFixTrack track = trackedRace.getTrack(competitor); + track.lockForRead(); + try { + for (GPSFixMoving gpsFix : track.getFixes(from, true, to, true)) { + JSONObject serializedGpsFix = gpsFixMovingJsonSerializer.serialize(gpsFix); + if (addWind) { + Wind wind = trackedRace.getWind(gpsFix.getPosition(), gpsFix.getTimePoint()); + JSONObject serializedWind = wind == null ? null : windJsonSerializer.serialize(wind); + serializedGpsFix.put(WIND, serializedWind); + } + if (addNextWaypoint) { + Distance closestDistanceToMark = estimationDataSupportDecoratorImpl + .getClosestDistanceToMark(gpsFix.getTimePoint()); + SpeedWithBearing speedWithBearing = smoothFixes ? track.getEstimatedSpeed(gpsFix.getTimePoint()) + : gpsFix.getSpeed(); + Bearing relativeBearingToNextMark = speedWithBearing == null ? null + : estimationDataSupportDecoratorImpl.getRelativeBearingToNextMark(gpsFix.getTimePoint(), + speedWithBearing.getBearing()); + serializedGpsFix.put(CLOSEST_DISTANCE_TO_MARK, + closestDistanceToMark == null ? null : closestDistanceToMark.getMeters()); + serializedGpsFix.put(RELATIVE_BEARING_TO_NEXT_MARK, + relativeBearingToNextMark == null ? null : relativeBearingToNextMark.getDegrees()); + } + gpsFixesWithEstimationData.add(serializedGpsFix); + } + } finally { + track.unlockAfterRead(); + } + return gpsFixesWithEstimationData; + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuverJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuverJsonSerializer.java index d5ddb39c175..9020e2894cc 100755 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuverJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/ManeuverJsonSerializer.java @@ -44,7 +44,7 @@ public class ManeuverJsonSerializer implements JsonSerializer { : maneuver.getSpeedWithBearingAfter().getBearing().getDegrees()); result.put(DIRECTION_CHANGE_IN_DEGREES, maneuver.getDirectionChangeInDegrees()); result.put(MANEUVER_LOSS, - maneuver.getManeuverLoss() == null ? null : distanceSerializer.serialize(maneuver.getManeuverLoss())); + maneuver.getManeuverLoss() == null ? null : distanceSerializer.serialize(maneuver.getManeuverLoss().getProjectedDistanceLost())); result.put(POSITION_AND_TIME, gpsFixSerializer.serialize(maneuver)); result.put(MAX_TURNING_RATE_IN_DEGREES_PER_SECOND, maneuver.getMaxTurningRateInDegreesPerSecond()); result.put(AVG_TURNING_RATE_IN_DEGREES_PER_SECOND, maneuver.getAvgTurningRateInDegreesPerSecond()); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 309a8f81972..cbe6e0dea44 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -3,6 +3,7 @@ package com.sap.sailing.server.gateway.impl; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; +import java.nio.file.Paths; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,24 +50,30 @@ public class FileUploadServlet extends AbstractFileUploadServlet { for (FileItem fileItem : fileItems) { final JSONObject result = new JSONObject(); final String fileExtension; + final String fileName = Paths.get(fileItem.getName()).getFileName().toString(); final String fileType = fileItem.getContentType(); if (fileType.equals("image/jpeg")) { fileExtension = ".jpg"; } else if (fileType.equals("image/png")) { fileExtension = ".png"; } else { - fileExtension = ""; + int lastDot = fileName.lastIndexOf("."); + if (lastDot > 0) { + fileExtension = fileName.substring(lastDot); + } else { + fileExtension = ""; + } } try { if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { final String errorMessage = "Image is larger than " + MAX_SIZE_IN_MB + "MB"; - logger.warning("Ignoring file storage request because file "+fileItem.getName()+" is larger than "+MAX_SIZE_IN_MB+"MB"); + logger.warning("Ignoring file storage request because file "+fileName+" is larger than "+MAX_SIZE_IN_MB+"MB"); result.put("status", Status.INTERNAL_SERVER_ERROR.name()); result.put("message", errorMessage); } else { final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); - result.put(JSON_FILE_NAME, fileItem.getName()); + result.put(JSON_FILE_NAME, fileName); result.put(JSON_FILE_URI, fileUri.toString()); } } catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java index 5909486602d..3d719143ebe 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/rc/RaceGroupFactory.java @@ -31,6 +31,7 @@ import com.sap.sailing.domain.common.TargetTimeInfo; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; @@ -74,8 +75,9 @@ public class RaceGroupFactory { Map> seriesToRaceColumns = getSeriesToRaceColumns(leaderboard); Collection seriesWithRows = new ArrayList<>(); for (Series series : getSeriesIterable(leaderboard, seriesToRaceColumns)) { - seriesWithRows.add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), series.isFleetsCanRunInParallel(), getRows(series, - seriesToRaceColumns.get(series)))); + seriesWithRows + .add(new SeriesWithRowsImpl(series.getName(), series.isMedal(), series.isFleetsCanRunInParallel(), + getRows(series, seriesToRaceColumns.get(series), leaderboard.getScoringScheme()))); } return seriesWithRows; } @@ -89,14 +91,16 @@ public class RaceGroupFactory { } /** - * @param raceColumns must be provided in the same order in which they appear in the series / leaderboard + * @param raceColumns + * must be provided in the same order in which they appear in the series / leaderboard */ - private Collection getRows(Series series, List raceColumns) { + private Collection getRows(Series series, List raceColumns, ScoringScheme scoringScheme) { Collection rows = new ArrayList<>(); for (Fleet fleet : series.getFleets()) { // We are taking the fleet name because there might be several "default fleet" // objects when TrackedRaces are linked onto this Leaderboard - rows.add(new RaceRowImpl(fleet, getCells(fleet.getName(), raceColumns, isFirstRaceColumnVirtual(series)))); + rows.add(new RaceRowImpl(fleet, + getCells(fleet.getName(), raceColumns, isFirstRaceColumnVirtual(series), scoringScheme))); } return rows; } @@ -111,7 +115,7 @@ public class RaceGroupFactory { * {@link TrackedRace#getEstimatedTimeToComplete(TimePoint) estimation} for the current point in time, when the * request is being made, using this time point to determine the wind speed and direction for each leg. */ - private List getCells(String fleetName, List raceColumns, boolean isFirstRaceColumnVirtual) { + private List getCells(String fleetName, List raceColumns, boolean isFirstRaceColumnVirtual, ScoringScheme scoringScheme) { boolean skippedFirst = false; List cells = new ArrayList<>(); if (raceColumns != null) { @@ -131,7 +135,8 @@ public class RaceGroupFactory { targetTime = null; } cells.add(new RaceCellImpl(raceColumn.getName(), raceColumn.getRaceLog(fleet), - raceColumn.getFactor(), raceColumn.getExplicitFactor(), zeroBasedIndexOfRaceInFleet++, targetTime)); + scoringScheme.getScoreFactor(raceColumn), raceColumn.getExplicitFactor(), + zeroBasedIndexOfRaceInFleet++, targetTime)); } } } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index 499aa5a1433..f7a2db37a18 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -85,6 +85,7 @@ import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants; import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.sharding.ShardingType; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl; @@ -95,6 +96,7 @@ import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl; import com.sap.sailing.domain.regattalike.HasRegattaLike; import com.sap.sailing.domain.regattalike.IsRegattaLike; import com.sap.sailing.domain.regattalike.LeaderboardThatHasRegattaLike; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceHandle; @@ -148,29 +150,35 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { public Response getLeaderboard(@PathParam("name") String leaderboardName, @DefaultValue("Live") @QueryParam("resultState") ResultStates resultState, @QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) { - Response response; - TimePoint requestTimePoint = MillisecondsTimePoint.now(); - Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); - if (leaderboard == null) { - response = Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") - .type(MediaType.TEXT_PLAIN).build(); - } else { - try { - TimePoint timePoint = calculateTimePointForResultState(leaderboard, resultState); - JSONObject jsonLeaderboard; - jsonLeaderboard = getLeaderboardJson(resultState, maxCompetitorsCount, requestTimePoint, leaderboard, timePoint, - /* race column names */ null, /* race detail names */ null); - StringWriter sw = new StringWriter(); - jsonLeaderboard.writeJSONString(sw); - String json = sw.getBuffer().toString(); - response = Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); - } catch (NoWindException | InterruptedException | ExecutionException | IOException e) { - response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) + ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboardName); + + try { + Response response; + TimePoint requestTimePoint = MillisecondsTimePoint.now(); + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + if (leaderboard == null) { + response = Response.status(Status.NOT_FOUND) + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") .type(MediaType.TEXT_PLAIN).build(); + } else { + try { + TimePoint timePoint = calculateTimePointForResultState(leaderboard, resultState); + JSONObject jsonLeaderboard; + jsonLeaderboard = getLeaderboardJson(resultState, maxCompetitorsCount, requestTimePoint, leaderboard, timePoint, + /* race column names */ null, /* race detail names */ null); + StringWriter sw = new StringWriter(); + jsonLeaderboard.writeJSONString(sw); + String json = sw.getBuffer().toString(); + response = Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); + } catch (NoWindException | InterruptedException | ExecutionException | IOException e) { + response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) + .type(MediaType.TEXT_PLAIN).build(); + } } + return response; + } finally { + ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME); } - return response; } @Override @@ -186,6 +194,7 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { writeCommonLeaderboardData(jsonLeaderboard, leaderboard, resultState, leaderboardDTO.getTimePoint(), maxCompetitorsCount); JSONArray jsonCompetitorEntries = new JSONArray(); jsonLeaderboard.put("competitors", jsonCompetitorEntries); + jsonLeaderboard.put("ShardingLeaderboardName", ShardingType.LEADERBOARDNAME.encodeIfNeeded(leaderboard.getName())); int counter = 1; for (CompetitorDTO competitor : leaderboardDTO.competitors) { LeaderboardRowDTO leaderboardRowDTO = leaderboardDTO.rows.get(competitor); @@ -1191,7 +1200,7 @@ public class LeaderboardsResource extends AbstractLeaderboardsResource { raceColumnsAsJson.add(raceColumnAsJson); raceColumnAsJson.put(RaceColumnConstants.RACE_COLUMN_NAME, rc.getName()); raceColumnAsJson.put(RaceColumnConstants.EXPLICIT_FACTOR, rc.getExplicitFactor()); - raceColumnAsJson.put(RaceColumnConstants.FACTOR, rc.getFactor()); + raceColumnAsJson.put(RaceColumnConstants.FACTOR, leaderboard.getScoringScheme().getScoreFactor(rc)); } return json; } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java index 2cdbb1647de..52a30c52b42 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResourceV2.java @@ -37,7 +37,9 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.LegEntryDTO; +import com.sap.sailing.domain.common.sharding.ShardingType; import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.sharding.ShardingContext; import com.sap.sse.InvalidDateException; import com.sap.sse.common.Distance; import com.sap.sse.common.Duration; @@ -52,40 +54,46 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource { public Response getLeaderboard(@PathParam("name") String leaderboardName, @DefaultValue("Live") @QueryParam("resultState") ResultStates resultState, @QueryParam("columnNames") final List raceColumnNames, - @QueryParam("raceDetails") final List raceDetails, + @QueryParam("raceDetails") final List raceDetails, @QueryParam("time") String time, @QueryParam("timeasmillis") Long timeasmillis, @QueryParam("maxCompetitorsCount") Integer maxCompetitorsCount) { - Response response; - Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); - if (leaderboard == null) { - response = Response.status(Status.NOT_FOUND) - .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") - .type(MediaType.TEXT_PLAIN).build(); - } else { - try { - TimePoint timePoint; + ShardingContext.setShardingConstraint(ShardingType.LEADERBOARDNAME, leaderboardName); + + try { + Response response; + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + if (leaderboard == null) { + response = Response.status(Status.NOT_FOUND) + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") + .type(MediaType.TEXT_PLAIN).build(); + } else { try { - timePoint = parseTimePoint(time, timeasmillis, calculateTimePointForResultState(leaderboard, resultState)); - } catch (InvalidDateException e1) { - return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the time.") + TimePoint timePoint; + try { + timePoint = parseTimePoint(time, timeasmillis, calculateTimePointForResultState(leaderboard, resultState)); + } catch (InvalidDateException e1) { + return Response.status(Status.INTERNAL_SERVER_ERROR).entity("Could not parse the time.") + .type(MediaType.TEXT_PLAIN).build(); + } + JSONObject jsonLeaderboard; + if (timePoint != null || resultState == ResultStates.Live) { + jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount, raceColumnNames, raceDetails); + } else { + jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, maxCompetitorsCount); + } + StringWriter sw = new StringWriter(); + jsonLeaderboard.writeJSONString(sw); + String json = sw.getBuffer().toString(); + response = Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); + } catch (NoWindException | InterruptedException | ExecutionException | IOException e) { + response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) .type(MediaType.TEXT_PLAIN).build(); } - JSONObject jsonLeaderboard; - if (timePoint != null || resultState == ResultStates.Live) { - jsonLeaderboard = getLeaderboardJson(leaderboard, timePoint, resultState, maxCompetitorsCount, raceColumnNames, raceDetails); - } else { - jsonLeaderboard = createEmptyLeaderboardJson(leaderboard, resultState, maxCompetitorsCount); - } - StringWriter sw = new StringWriter(); - jsonLeaderboard.writeJSONString(sw); - String json = sw.getBuffer().toString(); - response = Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); - } catch (NoWindException | InterruptedException | ExecutionException | IOException e) { - response = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()) - .type(MediaType.TEXT_PLAIN).build(); } + return response; + } finally { + ShardingContext.clearShardingConstraint(ShardingType.LEADERBOARDNAME); } - return response; } @Override @@ -122,6 +130,7 @@ public class LeaderboardsResourceV2 extends AbstractLeaderboardsResource { } JSONArray jsonCompetitorEntries = new JSONArray(); jsonLeaderboard.put("competitors", jsonCompetitorEntries); + jsonLeaderboard.put("ShardingLeaderboardName", ShardingType.LEADERBOARDNAME.encodeIfNeeded(leaderboard.getName())); int competitorCounter = 1; // Remark: leaderboardDTO.competitors are ordered by total rank for (CompetitorDTO competitor : leaderboardDTO.competitors) { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 4253ba61fd2..0bfa8f51d13 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -77,6 +77,7 @@ import com.sap.sailing.server.gateway.serialization.impl.BoatJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ColorJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CompetitorAndBoatJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.CompetitorTrackWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurveWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.CompleteManeuverCurvesWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.DefaultWindTrackJsonSerializer; @@ -84,9 +85,12 @@ import com.sap.sailing.server.gateway.serialization.impl.DetailedBoatClassJsonSe import com.sap.sailing.server.gateway.serialization.impl.DistanceJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.FleetJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.GPSFixJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GPSFixMovingJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.GpsFixesWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuverJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuverMainCurveWithEstimationDataJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.ManeuverWindJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.ManeuversJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.MarkPassingsJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.NationalityJsonSerializer; @@ -1027,7 +1031,15 @@ public class RegattasResource extends AbstractSailingServerResource { @Produces("application/json;charset=UTF-8") @Path("{regattaname}/races/{racename}/completeManeuverCurvesWithEstimationData") public Response getCompleteManeuverCurvesWithEstimationData(@PathParam("regattaname") String regattaName, - @PathParam("racename") String raceName) { + @PathParam("racename") String raceName, + @QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer startBeforeStartLineInSeconds, + @QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer endBeforeStartLineInSeconds, + @QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer startAfterFinishLineInSeconds, + @QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer endAfterFinishLineInSeconds) { Response response; Regatta regatta = findRegattaByName(regattaName); if (regatta == null) { @@ -1042,12 +1054,17 @@ public class RegattasResource extends AbstractSailingServerResource { .type(MediaType.TEXT_PLAIN).build(); } else { TrackedRace trackedRace = findTrackedRace(regattaName, raceName); - CompleteManeuverCurvesWithEstimationDataJsonSerializer serializer = new CompleteManeuverCurvesWithEstimationDataJsonSerializer( - new DetailedBoatClassJsonSerializer(), - new CompleteManeuverCurveWithEstimationDataJsonSerializer( - new ManeuverMainCurveWithEstimationDataJsonSerializer(), - new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), - new WindJsonSerializer(new PositionJsonSerializer()), new PositionJsonSerializer())); + CompetitorTrackWithEstimationDataJsonSerializer serializer = new CompetitorTrackWithEstimationDataJsonSerializer( + getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(), + new CompleteManeuverCurvesWithEstimationDataJsonSerializer(getService().getPolarDataService(), + new CompleteManeuverCurveWithEstimationDataJsonSerializer( + new ManeuverMainCurveWithEstimationDataJsonSerializer(), + new ManeuverCurveWithUnstableCourseAndSpeedWithEstimationDataJsonSerializer(), + new ManeuverWindJsonSerializer(), new PositionJsonSerializer())), + getNullableValueFromDefault(startBeforeStartLineInSeconds), + getNullableValueFromDefault(endBeforeStartLineInSeconds), + getNullableValueFromDefault(startAfterFinishLineInSeconds), + getNullableValueFromDefault(endAfterFinishLineInSeconds)); JSONObject jsonMarkPassings = serializer.serialize(trackedRace); String json = jsonMarkPassings.toJSONString(); return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); @@ -1056,6 +1073,55 @@ public class RegattasResource extends AbstractSailingServerResource { return response; } + @GET + @Produces("application/json;charset=UTF-8") + @Path("{regattaname}/races/{racename}/gpsFixesWithEstimationData") + public Response getGpsFixesWithEstimationData(@PathParam("regattaname") String regattaName, + @PathParam("racename") String raceName, @QueryParam("addWind") @DefaultValue("true") Boolean addWind, + @QueryParam("addNextWaypoint") @DefaultValue("true") Boolean addNextWaypoint, + @QueryParam("smoothFixes") @DefaultValue("true") Boolean smoothFixes, + @QueryParam("startBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer startBeforeStartLineInSeconds, + @QueryParam("endBeforeStartLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer endBeforeStartLineInSeconds, + @QueryParam("startAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer startAfterFinishLineInSeconds, + @QueryParam("endAfterFinishLineInSeconds") @DefaultValue(Integer.MIN_VALUE + + "") Integer endAfterFinishLineInSeconds) { + Response response; + Regatta regatta = findRegattaByName(regattaName); + if (regatta == null) { + response = Response.status(Status.NOT_FOUND) + .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.") + .type(MediaType.TEXT_PLAIN).build(); + } else { + RaceDefinition race = findRaceByName(regatta, raceName); + if (race == null) { + response = Response.status(Status.NOT_FOUND) + .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.") + .type(MediaType.TEXT_PLAIN).build(); + } else { + TrackedRace trackedRace = findTrackedRace(regattaName, raceName); + CompetitorTrackWithEstimationDataJsonSerializer serializer = new CompetitorTrackWithEstimationDataJsonSerializer( + getService().getPolarDataService(), new DetailedBoatClassJsonSerializer(), + new GpsFixesWithEstimationDataJsonSerializer(new GPSFixMovingJsonSerializer(), + new ManeuverWindJsonSerializer(), addWind, addNextWaypoint, smoothFixes), + getNullableValueFromDefault(startBeforeStartLineInSeconds), + getNullableValueFromDefault(endBeforeStartLineInSeconds), + getNullableValueFromDefault(startAfterFinishLineInSeconds), + getNullableValueFromDefault(endAfterFinishLineInSeconds)); + JSONObject jsonMarkPassings = serializer.serialize(trackedRace); + String json = jsonMarkPassings.toJSONString(); + return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); + } + } + return response; + } + + private Integer getNullableValueFromDefault(Integer value) { + return Integer.MIN_VALUE == value ? null : value; + } + @GET @Produces("application/json;charset=UTF-8") @Path("{regattaname}/races") diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java index 8f89926dc16..bc9b3b15e35 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RestApiApplication.java @@ -28,6 +28,7 @@ public class RestApiApplication extends Application { classes.add(PingResource.class); classes.add(TrackedRaceListResource.class); classes.add(StatisticsResource.class); + classes.add(WindResource.class); // Exception Mappers classes.add(ShiroAuthorizationExceptionTo401ResponseMapper.class); diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/WindResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/WindResource.java new file mode 100644 index 00000000000..1a6a3c1c881 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/WindResource.java @@ -0,0 +1,79 @@ +package com.sap.sailing.server.gateway.jaxrs.api; + +import javax.ws.rs.Consumes; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.shiro.SecurityUtils; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.Permission.Mode; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.Helpers; +import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeserializer; +import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer; +import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; + +@Path("/v1/wind") +public class WindResource extends AbstractSailingServerResource { + private final JsonDeserializer deserializer = new WindJsonDeserializer(new PositionJsonDeserializer()); + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Path("putWind") + public Response putWind(String json) throws ParseException, JsonDeserializationException { + SecurityUtils.getSubject().checkPermission(Permission.TRACKED_RACE.getStringPermission(Mode.UPDATE)); + + Object requestBody = JSONValue.parseWithException(json); + JSONObject requestObject = Helpers.toJSONObjectSafe(requestBody); + JSONArray windDatas = (JSONArray) requestObject.get("windData"); + + JSONArray regattaNamesAndRaceNames = (JSONArray) requestObject.get("regattaNamesAndRaceNames"); + + WindSourceType windSourceType = WindSourceType.valueOf((String) requestObject.get("windSourceType")); + JSONArray answer = new JSONArray(); + String windSourceId = (String) requestObject.get("windSourceId"); + for (Object regattaNameAndRaceName : regattaNamesAndRaceNames) { + final JSONObject regattaNameAndRaceNameObject = Helpers.toJSONObjectSafe(regattaNameAndRaceName); + String regattaName = (String) regattaNameAndRaceNameObject.get("regattaName"); + String raceName = (String) regattaNameAndRaceNameObject.get("raceName"); + RegattaNameAndRaceName identifier = new RegattaNameAndRaceName(regattaName, raceName); + JSONObject answerForRace = new JSONObject(); + answerForRace.put("regattaNameAndRaceName", regattaNameAndRaceName); + if (windSourceType == WindSourceType.EXPEDITION || windSourceType == WindSourceType.WEB) { + DynamicTrackedRace trackedRace = getService().getTrackedRace(identifier); + WindSource windsource = new WindSourceWithAdditionalID(windSourceType, windSourceId); + + if (trackedRace != null) { + JSONArray subAnswer = new JSONArray(); + for (int i = 0; i < windDatas.size(); i++) { + JSONObject windData = Helpers.toJSONObjectSafe(windDatas.get(i)); + Wind data = deserializer.deserialize(windData); + boolean success = trackedRace.recordWind(data, windsource); + subAnswer.add(i, success); + } + answerForRace.put("answer", subAnswer); + } else { + answerForRace.put("answer", "Could not resolve traced race"); + } + } else { + answerForRace.put("answer", "Only Windsourcetypes expedition or web are allowed"); + } + answer.add(answerForRace); + } + return Response.ok(answer.toJSONString()).build(); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index b8fea5fafba..2b8218015f4 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -249,6 +249,11 @@ name to make sure you get to the event you're interested in. /api/v1/boats Obtains information about a single boat + + + /api/v1/wind/putWind + Allows to add wind values to 1-x races, similar to AdminConsole +
      diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html new file mode 100644 index 00000000000..0eb95d18407 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/putWind.html @@ -0,0 +1,42 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

      SAP Sailing Analytics Webservices API Version 1.0

      +

      URL: /api/v1/wind/putWind

      + +Description: +

      Gets the details of a competitor

      +
      + + + + + + + + + + + + + + + + + +
      Webservice Type:REST
      Output format:Json
      Mandatory parameters:windData, regattaName, raceName, windSourceType, windSourceId
      Examples: + Request:
      {"windData":[{"position":{"latitude_deg":1,"longitude_deg":1},"timepoint":1234,"direction":120,"speedinknots":60}],"regattaNamesAndRaceNames":[{"regattaName":"ESS 2016 Cardiff","raceName":"Race 1"}],"windSourceType":"WEB","windSourceId":"tracker02"} +
      + Answer:
      [{"regattaNameAndRaceName":{"regattaName":"ESS 2016 Cardiff","raceName":"Race 1"},"answer":[true]}] +
      +
      +Back to Web Service Overview + + diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithGPSFixStoreContentsReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithSensorFixStoreContentsReplicationTest.java similarity index 97% rename from java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithGPSFixStoreContentsReplicationTest.java rename to java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithSensorFixStoreContentsReplicationTest.java index a20a4938f18..1c119401b33 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithGPSFixStoreContentsReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackedRaceWithSensorFixStoreContentsReplicationTest.java @@ -37,7 +37,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.persistence.MongoWindStoreFactory; import com.sap.sailing.domain.persistence.PersistenceFactory; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.test.PositionAssert; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; @@ -58,13 +58,13 @@ import com.sap.sse.replication.ReplicationMasterDescriptor; import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp.ReplicationServiceTestImpl; /** - * Runs the same tests as {@link TrackedRaceContentsReplicationTest}, but with a non-empty {@link GPSFixStore} that + * Runs the same tests as {@link TrackedRaceContentsReplicationTest}, but with a non-empty {@link SensorFixStore} that * has special serialization requirements. * * @author Axel Uhl (d043530) * */ -public class TrackedRaceWithGPSFixStoreContentsReplicationTest extends AbstractServerReplicationTest { +public class TrackedRaceWithSensorFixStoreContentsReplicationTest extends AbstractServerReplicationTest { private Competitor competitor; private DynamicTrackedRace trackedRace; private RegattaNameAndRaceName raceIdentifier; diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index eb84a8de7ab..1c79016d831 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -411,7 +411,7 @@ public class MasterDataImportTest { raceColumnOnTarget.setTrackedRace(fleet1OnTarget, trackedRaceForTarget); - Assert.assertEquals(factor, raceColumnOnTarget.getFactor()); + Assert.assertEquals(factor, leaderboard.getScoringScheme().getScoreFactor(raceColumnOnTarget)); Iterable competitorsOnTarget = leaderboardOnTarget.getAllCompetitors(); Iterator competitorIterator = competitorsOnTarget.iterator(); @@ -423,10 +423,8 @@ public class MasterDataImportTest { scoreCorrection, leaderboardOnTarget.getScoreCorrection().getExplicitScoreCorrection(competitorOnTarget, raceColumnOnTarget)); - Assert.assertEquals( - maxPointsReason, - leaderboardOnTarget.getScoreCorrection().getMaxPointsReason(competitorOnTarget, raceColumnOnTarget, - MillisecondsTimePoint.now())); + Assert.assertEquals(maxPointsReason, leaderboardOnTarget.getScoreCorrection() + .getMaxPointsReason(competitorOnTarget, raceColumnOnTarget, MillisecondsTimePoint.now())); // Check for carried points Assert.assertEquals(carriedPoints, leaderboardOnTarget.getCarriedPoints(competitorOnTarget)); @@ -2093,7 +2091,7 @@ public class MasterDataImportTest { RaceColumn metaColumn = metaColumns.iterator().next(); Assert.assertNotNull(metaColumn); - Assert.assertEquals(factor, metaColumn.getFactor()); + Assert.assertEquals(factor, overallLeaderboard.getScoringScheme().getScoreFactor(metaColumn)); // Verify that overall leaderboard data has been persisted RacingEventService persistenceVerifier = new RacingEventServiceImplMock(){}; @@ -2115,7 +2113,7 @@ public class MasterDataImportTest { metaColumn = metaColumns.iterator().next(); Assert.assertNotNull(metaColumn); - Assert.assertEquals(factor, metaColumn.getFactor()); + Assert.assertEquals(factor, overallLeaderboard.getScoringScheme().getScoreFactor(metaColumn)); } @@ -2289,17 +2287,15 @@ public class MasterDataImportTest { raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, trackedRaceForTarget); - Assert.assertEquals(factor, raceColumnOnTarget.getFactor()); + Assert.assertEquals(factor, leaderboardOnTarget.getScoringScheme().getScoreFactor(raceColumnOnTarget)); // Check for score corrections Assert.assertEquals( scoreCorrection, leaderboardOnTarget.getScoreCorrection().getExplicitScoreCorrection(competitorOnTarget, raceColumnOnTarget)); - Assert.assertEquals( - maxPointsReason, - leaderboardOnTarget.getScoreCorrection().getMaxPointsReason(competitorOnTarget, raceColumnOnTarget, - MillisecondsTimePoint.now())); + Assert.assertEquals(maxPointsReason, leaderboardOnTarget.getScoreCorrection() + .getMaxPointsReason(competitorOnTarget, raceColumnOnTarget, MillisecondsTimePoint.now())); // Check for carried points Assert.assertEquals(carriedPoints, leaderboardOnTarget.getCarriedPoints(competitorOnTarget)); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/util/ShardingLeaderBoardEncodingTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/util/ShardingLeaderBoardEncodingTest.java new file mode 100644 index 00000000000..60432760c09 --- /dev/null +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/util/ShardingLeaderBoardEncodingTest.java @@ -0,0 +1,19 @@ +package com.sap.sailing.server.util; + +import org.junit.Test; + +import com.sap.sailing.domain.common.sharding.ShardingType; + +import junit.framework.Assert; + +public class ShardingLeaderBoardEncodingTest { + @Test + public void testEncoding() { + Assert.assertEquals("/leaderboard/pureascistring", ShardingType.LEADERBOARDNAME.encodeIfNeeded("pureascistring")); + Assert.assertEquals("/leaderboard/unpure_asci_string", ShardingType.LEADERBOARDNAME.encodeIfNeeded("unpure asci string")); + Assert.assertEquals("/leaderboard/c_dille", ShardingType.LEADERBOARDNAME.encodeIfNeeded("cédille")); + Assert.assertEquals("/leaderboard/Hello_World", ShardingType.LEADERBOARDNAME.encodeIfNeeded("Hello+World")); + Assert.assertEquals("/leaderboard/Hello_World_", ShardingType.LEADERBOARDNAME.encodeIfNeeded("Hello(World)")); + + } +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java index 0c501a4abcd..a8c51048d72 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RegattaByKeywordSearchService.java @@ -126,12 +126,12 @@ public class RegattaByKeywordSearchService { * * @param leaderboard the leaderboard to get the matching events for * @param leaderboardGroup the LeaderboardGroup hosting the leaderboard - * @param events all events hosting the LeaderboardGroup + * @param events all events hosting the LeaderboardGroup; may be {@code null}; if {@code null}, result will be {@code null} * @return the best matching events for the given Leaderboard/LeaderboardGroup */ private Set filterEventsForLeaderboard(Leaderboard leaderboard, LeaderboardGroup leaderboardGroup, Set events) { final Set result; - if (leaderboardGroup.hasOverallLeaderboard()) { + if (events != null && leaderboardGroup.hasOverallLeaderboard()) { CourseArea defaultCourseArea = leaderboard.getDefaultCourseArea(); Set preResult = null; if (defaultCourseArea != null) { diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index b2efaf15c4b..23ab8d659ab 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -22,13 +22,37 @@

      Release Notes - Administration Console

      +

      July 2018

      +
        +
      • When adding a YouTube video for tracked races ("Manage Media" in RaceBoard.html or "Tracked Races > Audio & Video" in AdminConsole.html) the respective video metadata is now read using YouTube API v3. + This functionality used to work some years ago using API v2 but was broken since this API version was discontinued some time ago. + Due to limitations of the new API we can't read the start timepoint of videos by now. You still need to provide this value manually.
      • +
      • In the "Tracked races" tab, within the eponymous section of the AdminConsole, the "Set start + time received" dialog can be used to remove the currently set start time received, by simply + leaving the value empty and confirming the dialog.
      • +
      • Up to 15 discards can now be configured for series and leaderboards. This enables, e.g., "Wednesday Night" scenarios + where over the season, say, 20 races are run but due to changing participation only, say, the five best ones shall be scored.
      • +
      +

      June 2018

        +
      • Added the available WindFinder Spot Collections. The desired ones can be selected from the WindFinder Tab in the Event Dialog.
      • Added the ability to change the url of multiple Mediatracks with a common prefix at once. This is useful e.g. for video migration scenarios when movin videaos from one server to another. This can be done from the adminconsole->tracked races->audio & videos tab
      • In raceboard when you edit a video you do not need to explicitly press "Preview" before saving anymore. Saving now implicitly applies the changes.
      • +
      • The URLs of our GWT services are extended to include a leaderboard name if a call is context sensitive to a specific leaderboard. + This does not affect the functionality of any service but allows us to setup load balancing with leaderboard specific routing. + The leaderboard names put into the URL are cleaned: Any character that isn't a latin upper/lowercase letter or digit is replaced with an underscore character. + Currently, the following leaderboard specific URLs are used: +
          +
        • /gwt/service/sailing/leaderboard/[CLEANED-LEADERBOARD-NAME]
        • +
        • /gwt/service/dispatch/leaderboard/[CLEANED-LEADERBOARD-NAME]
        • +
        + Be aware that request without leaderboard suffix may be sent from pages that do not show leaderboard specific contents. + Due to this it is required to define a fallback rule for any unqualified request. +

      May 2018

      diff --git a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java index b5c5076cd14..d813d9dd68d 100644 --- a/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java +++ b/java/com.sap.sailing.xmlexport/src/com/sap/sailing/xmlexport/LeaderboardData.java @@ -796,8 +796,8 @@ public class LeaderboardData extends ExportAction { addNamedElementWithValue(maneuverInformation, "type", man.getType().toString()); addNamedElementWithValue(maneuverInformation, "position", maneuverCounter); addNamedElementWithValue(maneuverInformation, "direction_change_in_degrees", man.getDirectionChangeInDegrees()); - Distance maneuverLoss = man.getManeuverLoss(); - addNamedElementWithValue(maneuverInformation, "loss_in_meters", maneuverLoss != null ? maneuverLoss.getMeters() : 0.0); + addNamedElementWithValue(maneuverInformation, "loss_in_meters", + man.getManeuverLoss() != null ? man.getManeuverLoss().getProjectedDistanceLost().getMeters() : 0.0); addNamedElementWithValue(maneuverInformation, "speed_before_in_knots", man.getSpeedWithBearingBefore().getKnots()); addNamedElementWithValue(maneuverInformation, "speed_after_in_knots", man.getSpeedWithBearingAfter().getKnots()); maneuversElement.addContent(maneuverInformation); diff --git a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/GroupKey.java b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/GroupKey.java index 586c575ed46..599eadeb22e 100644 --- a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/GroupKey.java +++ b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/GroupKey.java @@ -9,7 +9,7 @@ public interface GroupKey extends Serializable, Comparable { /** * @return a string representation of this group key. */ - public String asString(); + String asString(); /** * Checks if this group key has at least one sub key. For example if the data was grouped @@ -17,7 +17,7 @@ public interface GroupKey extends Serializable, Comparable { * This means, that {@link #getKeys()} returns a {@link List} that contains at least two elements. * @return true, if this group key has at least one sub key */ - public boolean hasSubKeys(); + boolean hasSubKeys(); /** * Returns the number of group keys represented by this key: @@ -29,7 +29,7 @@ public interface GroupKey extends Serializable, Comparable { * * @return the number of group keys represented by this key. */ - public int size(); + int size(); /** * Returns a non-empty {@link List} of group keys that are represented by this key: @@ -44,7 +44,7 @@ public interface GroupKey extends Serializable, Comparable { * * @return the group keys that are represented by this key. */ - public List getKeys(); + List getKeys(); //Enforce hash code and equals in all subclasses @Override diff --git a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO.java b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO.java index 4f999a94c9b..36b73dea513 100755 --- a/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO.java +++ b/java/com.sap.sse.datamining.shared/src/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO.java @@ -8,20 +8,25 @@ import java.util.Map; public class ReducedDimensionsDTO implements Serializable { private static final long serialVersionUID = 6874154208776613306L; + final HashMap> reducedDimensions; final HashMap fromOriginalDimensionToReducedDimension; + public ReducedDimensionsDTO(HashMap> reducedDimensions, HashMap fromOriginalDimensionToReducedDimension) { super(); this.reducedDimensions = reducedDimensions; this.fromOriginalDimensionToReducedDimension = fromOriginalDimensionToReducedDimension; } + public HashMap> getReducedDimensions() { return reducedDimensions; } + public FunctionDTO getReducedDimension(FunctionDTO originalDimension) { return fromOriginalDimensionToReducedDimension.get(originalDimension); } + public Map getFromOriginalToReducedDimension() { return Collections.unmodifiableMap(fromOriginalDimensionToReducedDimension); } diff --git a/java/com.sap.sse.datamining.ui.test/.classpath b/java/com.sap.sse.datamining.ui.test/.classpath new file mode 100644 index 00000000000..341ee4d6e0b --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/java/com.sap.sse.datamining.ui.test/.project b/java/com.sap.sse.datamining.ui.test/.project new file mode 100644 index 00000000000..ad59090e252 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.project @@ -0,0 +1,39 @@ + + + com.sap.sse.datamining.ui.test + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + com.gwtplugins.gdt.eclipse.core.webAppProjectValidator + + + + + com.gwtplugins.gwt.eclipse.core.gwtProjectValidator + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + com.gwtplugins.gwt.eclipse.core.gwtNature + + diff --git a/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gdt.eclipse.core.prefs b/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gdt.eclipse.core.prefs new file mode 100644 index 00000000000..8f0b83a4967 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gdt.eclipse.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +jarsExcludedFromWebInfLib= +warSrcDir= +warSrcDirIsOutput=true diff --git a/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gwt.eclipse.core.prefs b/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gwt.eclipse.core.prefs new file mode 100644 index 00000000000..f23a7529805 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.settings/com.gwtplugins.gwt.eclipse.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +entryPointModules= diff --git a/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sse.datamining.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sse.datamining.ui.test/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..fa71dc3af04 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sse.datamining.ui.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Automatic-Module-Name: com.sap.sse.datamining.ui.test +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.junit, + com.google.gwt.user;bundle-version="2.8.2", + com.sap.sse.datamining.ui, + com.sap.sse.gwt;bundle-version="1.0.0", + slf4j.api;bundle-version="1.6.4", + com.sap.sse.common, + com.sap.sse.datamining.shared diff --git a/java/com.sap.sse.datamining.ui.test/build.properties b/java/com.sap.sse.datamining.ui.test/build.properties new file mode 100644 index 00000000000..237d94f640a --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/,\ + test/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/TestSSEDataMiningUi.gwt.xml b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/TestSSEDataMiningUi.gwt.xml new file mode 100644 index 00000000000..50bbcf4d464 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/TestSSEDataMiningUi.gwt.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyQueryResultDTO.java b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyQueryResultDTO.java new file mode 100644 index 00000000000..1cb0945ee63 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyQueryResultDTO.java @@ -0,0 +1,16 @@ +package com.sap.sse.datamining.ui.test.client.presentation; + +import java.io.Serializable; +import java.util.HashMap; + +import com.sap.sse.datamining.shared.data.QueryResultState; +import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; + +public class DummyQueryResultDTO extends QueryResultDTO { + + private static final long serialVersionUID = 4163808455941696613L; + + public DummyQueryResultDTO(Class resultType) { + super(QueryResultState.NORMAL, resultType, new HashMap<>()); + } +} diff --git a/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyResultsPresenter.java b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyResultsPresenter.java new file mode 100644 index 00000000000..eeb53fde4c6 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/src/com/sap/sse/datamining/ui/test/client/presentation/DummyResultsPresenter.java @@ -0,0 +1,85 @@ +package com.sap.sse.datamining.ui.test.client.presentation; + +import java.util.List; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.common.settings.Settings; +import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; +import com.sap.sse.datamining.ui.client.presentation.AbstractResultsPresenter; +import com.sap.sse.datamining.ui.client.presentation.TabbedResultsPresenter; +import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; + +/** + * Dummy class for testing the + * {@link TabbedResultsPresenter#registerResultsPresenter(Class, com.sap.sse.datamining.ui.client.ResultsPresenter)} + * method. Therefore its {@link #showResult(QueryResultDTO)} method is overridden and simply stores a reference of this + * presenter instance. + * + * @author D064866 + * + */ +public class DummyResultsPresenter extends AbstractResultsPresenter { + + private final DockLayoutPanel layoutPanel; + private final List executedResultsPresenter; + + public DummyResultsPresenter(List executeResultsPresenter) { + super(null, null); + this.executedResultsPresenter = executeResultsPresenter; + layoutPanel = new DockLayoutPanel(Unit.PCT); + } + + /** + * Stores this ResultsPresenter for test purposes. + */ + @Override + public void showResult(QueryResultDTO result) { + executedResultsPresenter.add(this); + } + + @Override + public String getId() { + return null; + } + + @Override + public String getLocalizedShortName() { + return null; + } + + @Override + public boolean hasSettings() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent(Settings useTheseSettings) { + return null; + } + + @Override + public Settings getSettings() { + return null; + } + + @Override + public void updateSettings(Settings newSettings) { + } + + @Override + public String getDependentCssClassName() { + return null; + } + + @Override + protected Widget getPresentationWidget() { + return layoutPanel; + } + + @Override + protected void internalShowResults(QueryResultDTO result) { + } + +} diff --git a/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiJUnit.gwt.xml b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiJUnit.gwt.xml new file mode 100644 index 00000000000..6667613bc7b --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiJUnit.gwt.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiSuite.java b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiSuite.java new file mode 100644 index 00000000000..7fdf2040b1e --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/TestSSEDataMiningUiSuite.java @@ -0,0 +1,19 @@ +package com.sap.sse.datamining.ui.test; + +import com.sap.sse.datamining.ui.test.client.TestSSEDataMiningUi; +import com.google.gwt.junit.tools.GWTTestSuite; +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * + * @author D064866 + * + */ +public class TestSSEDataMiningUiSuite extends GWTTestSuite { + public static Test suite() { + TestSuite suite = new TestSuite("Tests for TestSSEDataMiningUi"); + suite.addTestSuite(TestSSEDataMiningUi.class); + return suite; + } +} diff --git a/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/client/TestSSEDataMiningUi.java b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/client/TestSSEDataMiningUi.java new file mode 100644 index 00000000000..59e4dc4ef87 --- /dev/null +++ b/java/com.sap.sse.datamining.ui.test/test/com/sap/sse/datamining/ui/test/client/TestSSEDataMiningUi.java @@ -0,0 +1,70 @@ +package com.sap.sse.datamining.ui.test.client; + +import java.util.ArrayList; +import java.util.List; + +import com.google.gwt.junit.client.GWTTestCase; +import com.sap.sse.datamining.ui.client.presentation.MultiResultsPresenter; +import com.sap.sse.datamining.ui.client.presentation.PlainResultsPresenter; +import com.sap.sse.datamining.ui.client.presentation.ResultsChart; +import com.sap.sse.datamining.ui.client.presentation.TabbedResultsPresenter; +import com.sap.sse.datamining.ui.test.client.presentation.DummyQueryResultDTO; +import com.sap.sse.datamining.ui.test.client.presentation.DummyResultsPresenter; + +/** + * GWT JUnit tests must extend GWTTestCase. + * + * @author D064866 + */ +public class TestSSEDataMiningUi extends GWTTestCase { + + /** + * Must refer to a valid module that sources this class. + */ + public String getModuleName() { + return "com.sap.sse.datamining.ui.test.TestSSEDataMiningUiJUnit"; + } + + /** + * Tests the mechanism {@link TabbedResultsPresenter#registerResultsPresenter(Class, ResultsPresenter)}. + */ + public void testAvoidDuplicateResultTypeRegistration() { + TabbedResultsPresenter presenter = new TabbedResultsPresenter(null, null, null); + + presenter.registerResultsPresenter(String.class, new PlainResultsPresenter(null, null)); + presenter.registerResultsPresenter(Double.class, new MultiResultsPresenter(null, null, null)); + try { + presenter.registerResultsPresenter(String.class, new ResultsChart(null, null, true, null)); + fail(); + } catch (IllegalStateException e) { + assertTrue(true); + } + } + + /** + * Tests the mechanism {@link TabbedResultsPresenter#showResult(QueryResultDTO)}. + */ + public void testShowResultWithCorrectResultsPresenter() { + TabbedResultsPresenter presenter = new TabbedResultsPresenter(null, null, null); + List executedPresenter = new ArrayList<>(); + + DummyResultsPresenter stringResultsPresenter = new DummyResultsPresenter(executedPresenter); + DummyResultsPresenter doubleResultsPresenter = new DummyResultsPresenter(executedPresenter); + + presenter.registerResultsPresenter(String.class, stringResultsPresenter); + presenter.registerResultsPresenter(Double.class, doubleResultsPresenter); + + presenter.showResult(new DummyQueryResultDTO<>(String.class)); + assertTrue(executedPresenter.size() == 1); + assertEquals(executedPresenter.get(0), stringResultsPresenter); + + presenter.showResult(new DummyQueryResultDTO<>(Double.class)); + assertTrue(executedPresenter.size() == 2); + assertEquals(executedPresenter.get(1), doubleResultsPresenter); + + presenter.showResult(new DummyQueryResultDTO<>(Number.class)); + assertTrue(executedPresenter.size() == 2); + assertEquals(executedPresenter.get(0), stringResultsPresenter); + assertEquals(executedPresenter.get(1), doubleResultsPresenter); + } +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/ChartToCsvExporter.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/ChartToCsvExporter.java new file mode 100644 index 00000000000..012e8f0d12f --- /dev/null +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/ChartToCsvExporter.java @@ -0,0 +1,65 @@ +package com.sap.sse.datamining.ui.client; + +import org.moxieapps.gwt.highcharts.client.Chart; +import org.moxieapps.gwt.highcharts.client.Point; +import org.moxieapps.gwt.highcharts.client.Series; + +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.user.client.Window; +import com.sap.sse.gwt.shared.TextExporter; + +/** + * Provides a service for export of chart data into CSV format. + * + * @author Vladislav Chumak (D069712) + * + */ +public class ChartToCsvExporter { + + private final String exportedMessage; + + public ChartToCsvExporter(String exportedMessage) { + this.exportedMessage = exportedMessage; + } + + /** + * Converts the data of the chart into CSV format and stores it in clipboard. Finally, window alert gets shown + * to the user with information that there is CSV content in his clipboard. + * + * @param chartToExport + * Chart with data to export + */ + public void exportChartAsCsvToClipboard(Chart chartToExport) { + String csvContentExport = createCsvExportContentForStatisticsCurve(chartToExport); + TextExporter.exportToClipboard(csvContentExport); + Window.alert(exportedMessage); + } + + private static String createCsvExportContentForStatisticsCurve(Chart chartToExport) { + if (chartToExport != null && chartToExport.getSeries().length > 0) { + StringBuilder csvStr = new StringBuilder("Series name"); + for (Point point : chartToExport.getSeries()[0].getPoints()) { + String name = point.getName(); + csvStr.append(';'); + if (name != null && !name.isEmpty()) { + csvStr.append(name); + } else { + csvStr.append(point.getX()); + } + } + csvStr.append("\r\n"); + for (Series series : chartToExport.getSeries()) { + csvStr.append(series.getName()); + for (Point point : series.getPoints()) { + csvStr.append(';'); + csvStr.append(NumberFormat.getDecimalFormat().format(point.getY())); + } + csvStr.append("\r\n"); + } + String csvContentExport = csvStr.toString(); + return csvContentExport; + } + return "Statistics are empty"; + } + +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningComponentProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningComponentProvider.java index 78c755f272d..2a97d6dd7d8 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningComponentProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningComponentProvider.java @@ -15,7 +15,7 @@ public interface DataMiningComponentProvider exte * @return true, if the provider is reloading its components or is waiting for the * {@link #reloadComponents()} call. */ - public boolean isAwatingReload(); + public boolean isAwaitingReload(); public void reloadComponents(); diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.java index d12e5cbb33b..9a5d2bb0314 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.java @@ -58,4 +58,12 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages { String tabbedResultsPresenter(); String useStringLiterals(); String numberPairResultsPresenter(); + String copiedToClipboard(); + String csvExport(); + String search(); + String csvCopiedToClipboard(); + String filter(); + String filterShownDimensions(); + String selectDimensionsToFilterBy(); + } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.properties b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.properties index 8c44d48ebd2..522283d4f2b 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.properties +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages.properties @@ -42,3 +42,10 @@ dataMiningRetrieval=Data Retrieval tabbedResultsPresenter=Tabbed Results Presenter useStringLiterals=Use string literals for type names numberPairResultsPresenter=Scatterplot +copiedToClipboard=The text has been copied to clipboard. +csvExport=Export as CSV +search=Search +csvCopiedToClipboard=CSV content has been copied to clipboard. +filter=Filter +filterShownDimensions=Filter shown dimensions +selectDimensionsToFilterBy=Select dimensions to filter by \ No newline at end of file diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages_de.properties b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages_de.properties index e63565b6173..7fd0a304851 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages_de.properties +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/StringMessages_de.properties @@ -42,3 +42,10 @@ dataMiningRetrieval=Datenbeschaffung tabbedResultsPresenter=Tab Ergebnis-Darsteller useStringLiterals=Verwende String-Literale für Typnamen numberPairResultsPresenter=Scatterplot +copiedToClipboard=Der Text wurde in die Zwischenablage kopiert. +csvExport=Export als CSV +search=Suche +csvCopiedToClipboard=Der Inhalt wurde als CSV in die Zwischenablage kopiert. +filter=Filter +filterShownDimensions=Angezeigte Dimensionen filtern +selectDimensionsToFilterBy=Dimensionen der Filterung auswählen \ No newline at end of file diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/PredefinedQueryRunner.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/PredefinedQueryRunner.java index a35bfa98938..9c3cb8247a0 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/PredefinedQueryRunner.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/PredefinedQueryRunner.java @@ -150,7 +150,7 @@ public class PredefinedQueryRunner extends ComponentWithoutSettings { } @Override - public void onSuccess(QueryResultDTO result) { + public void onSuccess(QueryResultDTO result) { resultsPresenter.showResult(result); } }); diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionParser.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionParser.java index f7652eee787..078a23db696 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionParser.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionParser.java @@ -14,6 +14,7 @@ import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.sap.sse.common.settings.SerializableSettings; import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; import com.sap.sse.datamining.shared.impl.dto.AggregationProcessorDefinitionDTO; +import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; @@ -256,6 +257,15 @@ public class QueryDefinitionParser { if (value instanceof Enum) { return value.getClass().getName()+".valueOf(\""+((Enum) value).name()+"\")"; } + if (value instanceof ClusterDTO) { + ClusterDTO cluster = (ClusterDTO) value; + StringBuilder builder = new StringBuilder(); + builder.append("new ").append(ClusterDTO.class.getSimpleName()).append("(") + .append("\"").append(cluster.getSignifier()).append("\"").append(", ") + .append("\"").append(cluster.getLocalizedName()).append("\"") + .append(")"); + return builder.toString(); + } throw new IllegalArgumentException("Can't create literal for values of type " + value.getClass().getName()); } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java index c2553161ae3..ef961bb9412 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/developer/QueryDefinitionViewer.java @@ -5,6 +5,7 @@ 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.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.HTML; @@ -20,6 +21,7 @@ import com.sap.sse.datamining.ui.client.developer.QueryDefinitionParser.TypeToCo import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.ComponentWithoutSettings; import com.sap.sse.gwt.client.shared.settings.ComponentContext; +import com.sap.sse.gwt.shared.TextExporter; public class QueryDefinitionViewer extends ComponentWithoutSettings implements QueryDefinitionChangedListener { @@ -36,6 +38,7 @@ public class QueryDefinitionViewer extends ComponentWithoutSettings implements Q private final RadioButton useStringLiteralsRadioButton; private StatisticQueryDefinitionDTO currentDefinition; + private boolean active; public QueryDefinitionViewer(Component parent, ComponentContext context, StringMessages stringMessages) { super(parent, context); @@ -75,10 +78,12 @@ public class QueryDefinitionViewer extends ComponentWithoutSettings implements Q public void onClick(ClickEvent event) { switch (contentPanel.getSelectedIndex()) { case 0: - copyToClipboard(queryDefinitionParser.parseToDetailsAsText(currentDefinition)); + TextExporter.exportToClipboard(queryDefinitionParser.parseToDetailsAsText(currentDefinition)); + Window.alert(stringMessages.copiedToClipboard()); break; case 1: - copyToClipboard(queryDefinitionParser.parseToCodeAsText(currentDefinition, getTypeStrategy())); + TextExporter.exportToClipboard(queryDefinitionParser.parseToCodeAsText(currentDefinition, getTypeStrategy())); + Window.alert(stringMessages.copiedToClipboard()); break; } } @@ -99,15 +104,14 @@ public class QueryDefinitionViewer extends ComponentWithoutSettings implements Q dockPanel.add(contentPanel); } - public static native void copyToClipboard(String text) /*-{ - window.prompt("Copy to clipboard: Ctrl+C, Enter", text); - }-*/; @Override public void queryDefinitionChanged(StatisticQueryDefinitionDTO newQueryDefinition) { currentDefinition = newQueryDefinition; - updateDetails(); - updateCode(); + if (active) { + updateDetails(); + updateCode(); + } } private void updateDetails() { @@ -128,6 +132,18 @@ public class QueryDefinitionViewer extends ComponentWithoutSettings implements Q return TypeToCodeStrategy.CLASS_GET_NAME; } + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + if (active && this.active != active) { + updateDetails(); + updateCode(); + } + this.active = active; + } + @Override public String getLocalizedShortName() { return stringMessages.queryDefinitionViewer(); diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/execution/SimpleQueryRunner.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/execution/SimpleQueryRunner.java index 52fb1c77137..a2c4f64a0e0 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/execution/SimpleQueryRunner.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/execution/SimpleQueryRunner.java @@ -94,8 +94,7 @@ public class SimpleQueryRunner extends AbstractDataMiningComponent(counter) { + dataMiningService.runQuery(session, queryDefinition, new ManagedDataMiningQueryCallback(counter) { @Override protected void handleFailure(Throwable caught) { errorReporter.reportError("Error running the query: " + caught.getMessage()); diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/PlainFilterSelectionPresenter.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/PlainFilterSelectionPresenter.java index 8a178ce9a4f..f0e5ff2a043 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/PlainFilterSelectionPresenter.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/PlainFilterSelectionPresenter.java @@ -46,6 +46,8 @@ public class PlainFilterSelectionPresenter extends AbstractComponent(); results = new HashMap<>(); - // appendResultCheckBox = new CheckBox(stringMessages.appendResult()); - // addControl(appendResultCheckBox); - resultsLabel = new HTML(); scrollPanel = new ScrollPanel(resultsLabel); } @@ -49,11 +44,6 @@ public class PlainResultsPresenter extends AbstractNumericResultsPresenter resultValues, Map> errorMargins) { - // boolean appendResult = appendResultCheckBox.getValue(); - // if (!appendResult) { - // signifiers.clear(); - // results.clear(); - // } signifiers.clear(); results.clear(); diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/ResultsChart.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/ResultsChart.java index bf0331b7245..88d13fc2d8a 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/ResultsChart.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/ResultsChart.java @@ -1,5 +1,6 @@ package com.sap.sse.datamining.ui.client.presentation; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -25,12 +26,15 @@ import org.moxieapps.gwt.highcharts.client.events.SeriesClickEvent; import org.moxieapps.gwt.highcharts.client.events.SeriesClickEventHandler; import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsData; import org.moxieapps.gwt.highcharts.client.labels.AxisLabelsFormatter; +import org.moxieapps.gwt.highcharts.client.labels.DataLabels; +import org.moxieapps.gwt.highcharts.client.labels.DataLabelsData; +import org.moxieapps.gwt.highcharts.client.labels.DataLabelsFormatter; import org.moxieapps.gwt.highcharts.client.labels.YAxisLabels; import org.moxieapps.gwt.highcharts.client.plotOptions.SeriesPlotOptions; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.text.shared.AbstractRenderer; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimpleLayoutPanel; @@ -41,6 +45,8 @@ import com.sap.sse.common.settings.Settings; import com.sap.sse.datamining.shared.GroupKey; import com.sap.sse.datamining.shared.impl.CompoundGroupKey; import com.sap.sse.datamining.shared.impl.GenericGroupKey; +import com.sap.sse.datamining.ui.client.ChartToCsvExporter; +import com.sap.sse.datamining.ui.client.StringMessages; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.controls.SimpleObjectRenderer; @@ -135,6 +141,7 @@ public class ResultsChart extends AbstractNumericResultsPresenter { private final HorizontalPanel sortByPanel; private final ValueListBox> keyComparatorListBox; private final ValueListBox decimalsListBox; + private final CheckBox showDataLabelsCheckBox; private final SimpleLayoutPanel chartPanel; private final Chart chart; @@ -165,6 +172,17 @@ public class ResultsChart extends AbstractNumericResultsPresenter { super(parent, context); this.showErrorBars = showErrorBars; this.drillDownCallback = drillDownCallback; + + chartPanel = new SimpleLayoutPanel() { + @Override + public void onResize() { + chart.setSizeToMatchContainer(); + chart.redraw(); + } + }; + chart = createChart(); + chartPanel.setWidget(chart); + sortByPanel = new HorizontalPanel(); sortByPanel.setSpacing(5); sortByPanel.add(new Label(getDataMiningStringMessages().sortBy() + ":")); @@ -174,12 +192,9 @@ public class ResultsChart extends AbstractNumericResultsPresenter { return object.toString(); } }); - keyComparatorListBox.addValueChangeHandler(new ValueChangeHandler>() { - @Override - public void onValueChange(ValueChangeEvent> event) { - resetChartSeries(); - showResultData(); - } + keyComparatorListBox.addValueChangeHandler(e -> { + resetChartSeries(); + showResultData(); }); sortByPanel.add(keyComparatorListBox); sortByPanel.setVisible(false); @@ -192,25 +207,31 @@ public class ResultsChart extends AbstractNumericResultsPresenter { decimalsPanel.add(decimalsListBox); decimalsListBox.setValue(0); decimalsListBox.setAcceptableValues(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); - decimalsListBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - updateChartLabels(); - resetChartSeries(); - showResultData(); - } + decimalsListBox.addValueChangeHandler(e -> { + updateChartLabels(); + resetChartSeries(); + showResultData(); }); addControl(decimalsPanel); + + HorizontalPanel showDataLabelsPanel = new HorizontalPanel(); + showDataLabelsPanel.setSpacing(5); + showDataLabelsPanel.add(new Label(getDataMiningStringMessages().showDataLabels() + ":")); + showDataLabelsCheckBox = new CheckBox(); + showDataLabelsPanel.add(showDataLabelsCheckBox); + showDataLabelsCheckBox.setValue(true); + showDataLabelsCheckBox.addValueChangeHandler(e -> { + resetChartSeries(); + showResultData(); + }); + addControl(showDataLabelsPanel); + - chartPanel = new SimpleLayoutPanel() { - @Override - public void onResize() { - chart.setSizeToMatchContainer(); - chart.redraw(); - } - }; - chart = createChart(); - chartPanel.setWidget(chart); + StringMessages stringMessages = getDataMiningStringMessages(); + ChartToCsvExporter csvExporter = new ChartToCsvExporter(stringMessages.csvCopiedToClipboard()); + Button exportButton = new Button(stringMessages.csvExport()); + exportButton.addClickHandler(e -> csvExporter.exportChartAsCsvToClipboard(chart)); + addControl(exportButton); seriesMappedByGroupKey = new HashMap<>(); errorSeriesMappedByGroupKey = new HashMap<>(); @@ -428,7 +449,15 @@ public class ResultsChart extends AbstractNumericResultsPresenter { } } })); - chart.setSeriesPlotOptions(new SeriesPlotOptions().setSeriesClickEventHandler(new SeriesClickHandler())); + chart.setSeriesPlotOptions(new SeriesPlotOptions() + .setDataLabels(new DataLabels().setEnabled(true).setFormatter(new DataLabelsFormatter() { + @Override + public String format(DataLabelsData dataLabelsData) { + String dataLabel = String.valueOf(BigDecimal.valueOf(dataLabelsData.getYAsDouble()) + .setScale(decimalsListBox.getValue(), BigDecimal.ROUND_HALF_UP).doubleValue()); + return showDataLabelsCheckBox.getValue() ? dataLabel : null; + } + })).setSeriesClickEventHandler(new SeriesClickHandler())); return chart; } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/AbstractTabbedResultsPresenter.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/TabbedResultsPresenter.java old mode 100644 new mode 100755 similarity index 90% rename from java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/AbstractTabbedResultsPresenter.java rename to java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/TabbedResultsPresenter.java index 18ae8a52c58..bc6bbc13d2c --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/AbstractTabbedResultsPresenter.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/presentation/TabbedResultsPresenter.java @@ -9,23 +9,22 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.BeforeSelectionEvent; import com.google.gwt.event.logical.shared.BeforeSelectionHandler; -import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; import com.sap.sse.common.settings.Settings; import com.sap.sse.datamining.shared.impl.dto.QueryResultDTO; import com.sap.sse.datamining.ui.client.AbstractDataMiningComponent; -import com.sap.sse.datamining.ui.client.DataMiningResources; import com.sap.sse.datamining.ui.client.ResultsPresenter; import com.sap.sse.datamining.ui.client.presentation.ResultsChart.DrillDownCallback; +import com.sap.sse.datamining.ui.client.resources.DataMiningResources; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.controls.ScrolledTabLayoutPanel; import com.sap.sse.gwt.client.shared.settings.ComponentContext; -public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningComponent +public class TabbedResultsPresenter extends AbstractDataMiningComponent implements ResultsPresenter { protected static final DataMiningResources resources = GWT.create(DataMiningResources.class); @@ -34,13 +33,12 @@ public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningC protected final DrillDownCallback drillDownCallback; protected final Map> registeredResultPresenterMap; - public AbstractTabbedResultsPresenter(Component parent, ComponentContext context, + public TabbedResultsPresenter(Component parent, ComponentContext context, DrillDownCallback drillDownCallback) { super(parent, context); this.drillDownCallback = drillDownCallback; tabPanel = new ScrolledTabLayoutPanel(30, Unit.PX, resources.arrowLeftIcon(), resources.arrowRightIcon()); tabPanel.setAnimationDuration(0); - tabPanel.getElement().getStyle().setMarginTop(10, Unit.PX); presentersMappedByHeader = new HashMap<>(); registeredResultPresenterMap = new HashMap<>(); @@ -50,7 +48,9 @@ public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningC private void addNewTabTab() { Label widget = new Label("This should never be shown"); - Image header = new Image(resources.plusIcon()); + FlowPanel header = new FlowPanel(); + header.addStyleName("resultsPresenterTabHeader"); + header.add(new Image(resources.plusIcon())); tabPanel.add(widget, header); // This is necessary to stop the selection of this pseudo tab tabPanel.addBeforeSelectionHandler(new BeforeSelectionHandler() { @@ -58,7 +58,7 @@ public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningC public void onBeforeSelection(BeforeSelectionEvent event) { if (event.getItem() == tabPanel.getWidgetCount() - 1) { event.cancel(); - addTabAndFocus(new MultiResultsPresenter(AbstractTabbedResultsPresenter.this, getComponentContext(), + addTabAndFocus(new MultiResultsPresenter(TabbedResultsPresenter.this, getComponentContext(), drillDownCallback)); } } @@ -156,13 +156,14 @@ public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningC return "tabbedResultsPresenters"; } - public class CloseableTabHeader extends HorizontalPanel { + public class CloseableTabHeader extends FlowPanel { - private final HTML label; + private final Label label; public CloseableTabHeader() { - label = new HTML(getDataMiningStringMessages().empty()); - label.getElement().getStyle().setMarginRight(5, Unit.PX); + this.addStyleName("resultsPresenterTabHeader"); + + label = new Label(getDataMiningStringMessages().empty()); this.add(label); Image closeImage = new Image(resources.closeIcon()); closeImage.addClickHandler(new ClickHandler() { @@ -202,7 +203,7 @@ public abstract class AbstractTabbedResultsPresenter extends AbstractDataMiningC * @throws IllegalStateException * if the {@link resultType} is already registered. */ - protected void registerResultPresenter(Class resultType, ResultsPresenter resultPresenter) + public void registerResultsPresenter(Class resultType, ResultsPresenter resultPresenter) throws IllegalStateException { String className = resultType.getName(); if (!registeredResultPresenterMap.containsKey(className)) { diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterTable.css b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningCellTable.css similarity index 100% rename from java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterTable.css rename to java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningCellTable.css diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningCellTableResources.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningCellTableResources.java new file mode 100644 index 00000000000..df45cdbcca6 --- /dev/null +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningCellTableResources.java @@ -0,0 +1,12 @@ +package com.sap.sse.datamining.ui.client.resources; + +import com.google.gwt.user.cellview.client.CellTable; + +public interface DataMiningCellTableResources extends CellTable.Resources { + + @Source({ CellTable.Style.DEFAULT_CSS, "DataMiningCellTable.css" }) + DataMiningCellTableStyle cellTableStyle(); + + interface DataMiningCellTableStyle extends CellTable.Style { + } +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGrid.css b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGrid.css new file mode 100644 index 00000000000..9f3ff36a969 --- /dev/null +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGrid.css @@ -0,0 +1,31 @@ +.dataGridCell { + border: none; +} + +.dataGridFirstColumn { + width: 20px; + padding: 2px 0 2px 3px; +} + +.dataGridHoveredRow { + cursor: default; +} + +.dataGridSubHeader { + text-align: center; + margin-top: 5px; + background-color: #eee; +} + +.dataGridSubHeaderLabel { + position: relative; + left: -20px; +} + +.dataGridSpacedSubHeader { + padding-top: 1em; +} + +.dataGridClearFocus *:focus { + outline: none; +} \ No newline at end of file diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGridResources.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGridResources.java new file mode 100644 index 00000000000..ed435942b91 --- /dev/null +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningDataGridResources.java @@ -0,0 +1,21 @@ +package com.sap.sse.datamining.ui.client.resources; + +import com.google.gwt.user.cellview.client.DataGrid; + +public interface DataMiningDataGridResources extends DataGrid.Resources { + + @Source({ DataGrid.Style.DEFAULT_CSS, "DataMiningDataGrid.css" }) + DataMiningDataGridStyle dataGridStyle(); + + interface DataMiningDataGridStyle extends DataGrid.Style { + + String dataGridSubHeader(); + + String dataGridSpacedSubHeader(); + + String dataGridSubHeaderLabel(); + + String dataGridClearFocus(); + + } +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningResources.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java similarity index 92% rename from java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningResources.java rename to java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java index 9a4b5bcde4e..81122a16629 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/DataMiningResources.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/resources/DataMiningResources.java @@ -1,4 +1,4 @@ -package com.sap.sse.datamining.ui.client; +package com.sap.sse.datamining.ui.client.resources; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ImageResource; diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/ExtractionFunctionAndAggregatorDefinitionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/ExtractionFunctionAndAggregatorDefinitionProvider.java deleted file mode 100644 index 32350029644..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/ExtractionFunctionAndAggregatorDefinitionProvider.java +++ /dev/null @@ -1,289 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; - -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.event.logical.shared.ValueChangeHandler; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ValueListBox; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sse.common.Util; -import com.sap.sse.common.settings.SerializableSettings; -import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.AggregationProcessorDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; -import com.sap.sse.datamining.ui.client.AbstractDataMiningComponent; -import com.sap.sse.datamining.ui.client.AggregatorDefinitionChangedListener; -import com.sap.sse.datamining.ui.client.AggregatorDefinitionProvider; -import com.sap.sse.datamining.ui.client.DataMiningServiceAsync; -import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionChangedListener; -import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionProvider; -import com.sap.sse.datamining.ui.client.ExtractionFunctionChangedListener; -import com.sap.sse.datamining.ui.client.ExtractionFunctionProvider; -import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.shared.components.Component; -import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; -import com.sap.sse.gwt.client.shared.controls.AbstractObjectRenderer; -import com.sap.sse.gwt.client.shared.settings.ComponentContext; - -public class ExtractionFunctionAndAggregatorDefinitionProvider extends AbstractDataMiningComponent - implements ExtractionFunctionProvider, AggregatorDefinitionProvider, - DataRetrieverChainDefinitionChangedListener { - - private static final String STATISTIC_PROVIDER_ELEMENT_STYLE = "statisticProviderElement"; - - private final DataMiningServiceAsync dataMiningService; - private final ErrorReporter errorReporter; - private final Collection extractionFunctionListeners; - private final Collection aggregatorDefinitionListeners; - - private boolean isAwaitingReload; - private DataRetrieverChainDefinitionDTO currentRetrieverChainDefinition; - - private final FlowPanel mainPanel; - private final ValueListBox extractionFunctionListBox; - private final ValueListBox aggregatorDefinitionListBox; - - public ExtractionFunctionAndAggregatorDefinitionProvider(Component parent, ComponentContext context, - DataMiningServiceAsync dataMiningService, ErrorReporter errorReporter, - DataRetrieverChainDefinitionProvider retrieverChainProvider) { - super(parent, context); - this.dataMiningService = dataMiningService; - this.errorReporter = errorReporter; - extractionFunctionListeners = new HashSet<>(); - aggregatorDefinitionListeners = new HashSet<>(); - - isAwaitingReload = false; - currentRetrieverChainDefinition = null; - - HorizontalPanel selectionPanel = new HorizontalPanel(); - extractionFunctionListBox = createExtractionFunctionListBox(); - extractionFunctionListBox.addStyleName(STATISTIC_PROVIDER_ELEMENT_STYLE); - selectionPanel.add(extractionFunctionListBox); - aggregatorDefinitionListBox = createAggregatorListBox(); - aggregatorDefinitionListBox.addStyleName(STATISTIC_PROVIDER_ELEMENT_STYLE); - selectionPanel.add(aggregatorDefinitionListBox); - - mainPanel = new FlowPanel(); - Label label = new Label(this.getDataMiningStringMessages().calculateThe()); - label.addStyleName(STATISTIC_PROVIDER_ELEMENT_STYLE); - mainPanel.add(label); - mainPanel.add(selectionPanel); - - retrieverChainProvider.addDataRetrieverChainDefinitionChangedListener(this); - } - - private ValueListBox createExtractionFunctionListBox() { - ValueListBox extractionFunctionListBox = new ValueListBox<>( - new AbstractObjectRenderer() { - @Override - protected String convertObjectToString(FunctionDTO function) { - return function.getDisplayName(); - } - }); - extractionFunctionListBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - notifyExtractionFunctionListeners(); - updateAggregatorDefinitions(); - } - }); - return extractionFunctionListBox; - } - - private ValueListBox createAggregatorListBox() { - ValueListBox aggregatorListBox = new ValueListBox( - new AbstractObjectRenderer() { - @Override - protected String convertObjectToString(AggregationProcessorDefinitionDTO nonNullObject) { - return nonNullObject.getDisplayName(); - } - }); - aggregatorListBox.addValueChangeHandler(new ValueChangeHandler() { - @Override - public void onValueChange(ValueChangeEvent event) { - notifyAggregatorDefinitionListeners(); - } - }); - return aggregatorListBox; - } - - @Override - public void awaitReloadComponents() { - isAwaitingReload = true; - } - - @Override - public boolean isAwatingReload() { - return isAwaitingReload; - } - - @Override - public void reloadComponents() { - isAwaitingReload = false; - updateExtractionFunctions(); - } - - @Override - public void dataRetrieverChainDefinitionChanged(DataRetrieverChainDefinitionDTO newRetrieverChainDefinition) { - if (!Objects.equals(currentRetrieverChainDefinition, newRetrieverChainDefinition)) { - currentRetrieverChainDefinition = newRetrieverChainDefinition; - if (!isAwaitingReload) { - updateExtractionFunctions(); - } - } - } - - private void updateExtractionFunctions() { - if (currentRetrieverChainDefinition == null) { - updateListBox(extractionFunctionListBox, Collections.emptyList()); - } else { - dataMiningService.getStatisticsFor(currentRetrieverChainDefinition, - LocaleInfo.getCurrentLocale().getLocaleName(), new AsyncCallback>() { - @Override - public void onSuccess(HashSet functions) { - List sortedFunctions = new ArrayList<>(functions); - Collections.sort(sortedFunctions); - updateListBox(extractionFunctionListBox, sortedFunctions); - } - - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError( - "Error fetching the available statistics from the server: " + caught.getMessage()); - } - }); - } - } - - private void updateAggregatorDefinitions() { - if (getExtractionFunction() == null) { - updateListBox(aggregatorDefinitionListBox, Collections.emptyList()); - } else { - dataMiningService.getAggregatorDefinitionsFor(getExtractionFunction(), - LocaleInfo.getCurrentLocale().getLocaleName(), - new AsyncCallback>() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError( - "Error fetching the available aggregators from the server: " + caught.getMessage()); - } - - @Override - public void onSuccess(HashSet definitions) { - List sortedDefinitions = new ArrayList<>(definitions); - Collections.sort(sortedDefinitions); - updateListBox(aggregatorDefinitionListBox, sortedDefinitions); - } - }); - } - } - - private void updateListBox(ValueListBox listBox, Collection acceptableValues) { - T currentValue = listBox.getValue(); - T valueToBeSelected = acceptableValues.contains(currentValue) ? currentValue : Util.first(acceptableValues); - - listBox.setValue(valueToBeSelected, true); - listBox.setAcceptableValues(acceptableValues); - } - - @Override - public void applyQueryDefinition(StatisticQueryDefinitionDTO queryDefinition) { - extractionFunctionListBox.setValue(queryDefinition.getStatisticToCalculate()); - aggregatorDefinitionListBox.setValue(queryDefinition.getAggregatorDefinition(), false); - } - - private void notifyExtractionFunctionListeners() { - FunctionDTO extractionFunction = getExtractionFunction(); - for (ExtractionFunctionChangedListener listener : extractionFunctionListeners) { - listener.extractionFunctionChanged(extractionFunction); - } - } - - private void notifyAggregatorDefinitionListeners() { - AggregationProcessorDefinitionDTO aggregatorDefinition = getAggregatorDefinition(); - for (AggregatorDefinitionChangedListener listener : aggregatorDefinitionListeners) { - listener.aggregatorDefinitionChanged(aggregatorDefinition); - } - } - - @Override - public void addExtractionFunctionChangedListener(ExtractionFunctionChangedListener listener) { - extractionFunctionListeners.add(listener); - } - - @Override - public void addAggregatorDefinitionChangedListener(AggregatorDefinitionChangedListener listener) { - aggregatorDefinitionListeners.add(listener); - } - - @Override - public FunctionDTO getExtractionFunction() { - return extractionFunctionListBox.getValue(); - } - - @Override - public AggregationProcessorDefinitionDTO getAggregatorDefinition() { - return aggregatorDefinitionListBox.getValue(); - } - - @Override - public String getLocalizedShortName() { - return getDataMiningStringMessages().statisticProvider(); - } - - @Override - public Widget getEntryWidget() { - return mainPanel; - } - - @Override - public boolean isVisible() { - return mainPanel.isVisible(); - } - - @Override - public void setVisible(boolean visibility) { - mainPanel.setVisible(visibility); - } - - @Override - public boolean hasSettings() { - return false; - } - - @Override - public SettingsDialogComponent getSettingsDialogComponent(SerializableSettings settings) { - return null; - } - - @Override - public void updateSettings(SerializableSettings newSettings) { - // no-op - } - - @Override - public String getDependentCssClassName() { - return "simpleStatisticsProvider"; - } - - @Override - public SerializableSettings getSettings() { - return null; - } - - @Override - public String getId() { - return "SimpleStatisticProvider"; - } -} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/MultiDimensionalGroupingProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/MultiDimensionalGroupingProvider.java index 768d5cedcc7..68bb72469a8 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/MultiDimensionalGroupingProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/MultiDimensionalGroupingProvider.java @@ -3,6 +3,7 @@ package com.sap.sse.datamining.ui.client.selection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -19,6 +20,7 @@ import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sse.common.Util; import com.sap.sse.common.settings.SerializableSettings; +import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; @@ -37,6 +39,16 @@ public class MultiDimensionalGroupingProvider extends AbstractDataMiningComponen implements GroupingProvider { private static final String GROUPING_PROVIDER_ELEMENT_STYLE = "groupingProviderElement"; + private static final NaturalComparator NaturalComparator = new NaturalComparator(); + private static final Comparator DimensionComparator = (d1, d2) -> { + // Null values (to deselect the dimension) on top + if (d1 == null || d2 == null) { + if (d1 == d2) return 0; + if (d1 == null) return -1; + if (d2 == null) return 1; + } + return NaturalComparator.compare(d1.getDisplayName(), d2.getDisplayName()); + }; private final DataMiningServiceAsync dataMiningService; private final ErrorReporter errorReporter; @@ -62,8 +74,11 @@ public class MultiDimensionalGroupingProvider extends AbstractDataMiningComponen dimensionToGroupByBoxes = new ArrayList>(); mainPanel = new FlowPanel(); + mainPanel.addStyleName("groupingProvider"); Label groupByLabel = new Label(this.getDataMiningStringMessages().groupBy()); groupByLabel.addStyleName(GROUPING_PROVIDER_ELEMENT_STYLE); + groupByLabel.addStyleName("queryProviderElementLabel"); + groupByLabel.addStyleName("emphasizedLabel"); mainPanel.add(groupByLabel); ValueListBox firstDimensionToGroupByBox = createDimensionToGroupByBox(); @@ -77,7 +92,7 @@ public class MultiDimensionalGroupingProvider extends AbstractDataMiningComponen } @Override - public boolean isAwatingReload() { + public boolean isAwaitingReload() { return isAwaitingReload; } @@ -110,7 +125,7 @@ public class MultiDimensionalGroupingProvider extends AbstractDataMiningComponen ValueListBox firstDimensionToGroupByBox = createDimensionToGroupByBox(); addDimensionToGroupByBoxAndUpdateAcceptableValues(firstDimensionToGroupByBox); if (!availableDimensions.isEmpty()) { - Collections.sort(availableDimensions); + Collections.sort(availableDimensions, DimensionComparator); firstDimensionToGroupByBox.setValue(availableDimensions.iterator().next(), true); } else { notifyListeners(); @@ -202,8 +217,8 @@ public class MultiDimensionalGroupingProvider extends AbstractDataMiningComponen if (dimensionToGroupByBox.getValue() != null) { acceptableValues.add(dimensionToGroupByBox.getValue()); } - Collections.sort(acceptableValues); acceptableValues.add(null); + Collections.sort(acceptableValues, DimensionComparator); dimensionToGroupByBox.setAcceptableValues(acceptableValues); } } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java index a27572ba74d..57e88513dc1 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/QueryDefinitionProviderWithControls.java @@ -29,25 +29,21 @@ import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; import com.sap.sse.datamining.shared.impl.dto.ModifiableStatisticQueryDefinitionDTO; -import com.sap.sse.datamining.ui.client.AggregatorDefinitionChangedListener; -import com.sap.sse.datamining.ui.client.AggregatorDefinitionProvider; import com.sap.sse.datamining.ui.client.DataMiningComponentProvider; import com.sap.sse.datamining.ui.client.DataMiningServiceAsync; import com.sap.sse.datamining.ui.client.DataMiningSettingsControl; import com.sap.sse.datamining.ui.client.DataMiningSettingsInfoManager; -import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionChangedListener; -import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionProvider; -import com.sap.sse.datamining.ui.client.ExtractionFunctionChangedListener; -import com.sap.sse.datamining.ui.client.ExtractionFunctionProvider; import com.sap.sse.datamining.ui.client.FilterSelectionChangedListener; import com.sap.sse.datamining.ui.client.FilterSelectionProvider; import com.sap.sse.datamining.ui.client.GroupingChangedListener; import com.sap.sse.datamining.ui.client.GroupingProvider; import com.sap.sse.datamining.ui.client.ResultsPresenter; +import com.sap.sse.datamining.ui.client.StatisticChangedListener; +import com.sap.sse.datamining.ui.client.StatisticProvider; import com.sap.sse.datamining.ui.client.WithControls; import com.sap.sse.datamining.ui.client.developer.PredefinedQueryRunner; import com.sap.sse.datamining.ui.client.developer.QueryDefinitionViewer; -import com.sap.sse.datamining.ui.client.selection.filter.ListRetrieverChainFilterSelectionProvider; +import com.sap.sse.datamining.ui.client.selection.filter.HierarchicalDimensionListFilterSelectionProvider; import com.sap.sse.datamining.ui.client.settings.AdvancedDataMiningSettings; import com.sap.sse.datamining.ui.client.settings.AdvancedDataMiningSettingsDialogComponent; import com.sap.sse.gwt.client.ErrorReporter; @@ -60,8 +56,9 @@ import com.sap.sse.gwt.client.shared.settings.ComponentContext; public class QueryDefinitionProviderWithControls extends AbstractQueryDefinitionProvider implements WithControls { - private static final double headerPanelHeight = 45; - private static final double footerPanelHeight = 50; + private static final double HeaderPanelHeight = 45; + private static final double FooterPanelHeight = 50; + private static final int SplitterSize = 10; private final DockLayoutPanel mainPanel; private final FlowPanel controlsPanel; @@ -74,9 +71,7 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition private final ProviderListener providerListener; private final Collection> providers; - private final DataRetrieverChainDefinitionProvider retrieverChainProvider; - private final ExtractionFunctionProvider extractionFunctionProvider; - private final AggregatorDefinitionProvider aggregationDefinitionProvider; + private final StatisticProvider statisticProvider; private final GroupingProvider groupingProvider; private final SplitLayoutPanel filterSplitPanel; private final FilterSelectionProvider filterSelectionProvider; @@ -100,14 +95,17 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition new ClickHandler() { @Override public void onClick(ClickEvent event) { - filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), - !queryDefinitionViewerToggleButton.getValue()); + boolean active = queryDefinitionViewerToggleButton.isDown(); + filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), !active); + queryDefinitionViewer.setActive(active); } }); queryDefinitionViewer = new QueryDefinitionViewer(parent, context, getDataMiningStringMessages()); + queryDefinitionViewer.getEntryWidget().addStyleName("dataMiningMarginRight"); + queryDefinitionViewer.setActive(false); addQueryDefinitionChangedListener(queryDefinitionViewer); predefinedQueryRunner = new PredefinedQueryRunner(parent, context, session, getDataMiningStringMessages(), - dataMiningService, errorReporter, resultsPresenter); + dataMiningService, errorReporter, resultsPresenter); Button clearSelectionButton = new Button(getDataMiningStringMessages().clearSelection()); clearSelectionButton.addClickHandler(new ClickHandler() { @@ -132,48 +130,42 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition addControl(predefinedQueryRunner.getEntryWidget()); } - SuggestBoxStatisticProvider statisticProvider = new SuggestBoxStatisticProvider(parent, context, - getDataMiningService(), getErrorReporter(), settingsControl, settingsManager); - - retrieverChainProvider = statisticProvider; - retrieverChainProvider.addDataRetrieverChainDefinitionChangedListener(providerListener); - - SplitLayoutPanel headerPanel = new SplitLayoutPanel(15); - headerPanel.addWest(statisticProvider.getEntryWidget(), 800); - headerPanel.add(controlsPanel); - - extractionFunctionProvider = statisticProvider; - extractionFunctionProvider.addExtractionFunctionChangedListener(providerListener); - aggregationDefinitionProvider = statisticProvider; - aggregationDefinitionProvider.addAggregatorDefinitionChangedListener(providerListener); - - groupingProvider = new MultiDimensionalGroupingProvider(parent, context, getDataMiningService(), - getErrorReporter(), retrieverChainProvider); + statisticProvider = new SuggestBoxStatisticProvider(parent, context, dataMiningService, + errorReporter, settingsControl, settingsManager); + statisticProvider.getEntryWidget().addStyleName("statisticProvider"); + statisticProvider.addStatisticChangedListener(providerListener); + + groupingProvider = new MultiDimensionalGroupingProvider(parent, context, dataMiningService, + errorReporter, statisticProvider); groupingProvider.addGroupingChangedListener(providerListener); + groupingProvider.getEntryWidget().addStyleName("dataMiningMarginBase"); - filterSplitPanel = new SplitLayoutPanel(15); - filterSplitPanel.addSouth(groupingProvider.getEntryWidget(), footerPanelHeight); + filterSelectionProvider = new HierarchicalDimensionListFilterSelectionProvider( + parent, context, session, getDataMiningStringMessages(), dataMiningService, errorReporter, statisticProvider); + filterSelectionProvider.addSelectionChangedListener(providerListener); + filterSelectionProvider.getEntryWidget().addStyleName("dataMiningBorderTop"); + + filterSplitPanel = new SplitLayoutPanel(SplitterSize); + filterSplitPanel.addSouth(groupingProvider.getEntryWidget(), FooterPanelHeight); filterSplitPanel.addEast(queryDefinitionViewer.getEntryWidget(), 600); filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), true); - filterSelectionProvider = new ListRetrieverChainFilterSelectionProvider(parent, context, session, - dataMiningService, errorReporter, retrieverChainProvider); - filterSelectionProvider.addSelectionChangedListener(providerListener); filterSplitPanel.add(filterSelectionProvider.getEntryWidget()); + SplitLayoutPanel headerPanel = new SplitLayoutPanel(SplitterSize); + headerPanel.addStyleName("dataMiningMarginBase"); + headerPanel.addWest(statisticProvider.getEntryWidget(), 800); + headerPanel.add(controlsPanel); + mainPanel = new DockLayoutPanel(Unit.PX); - mainPanel.addNorth(headerPanel, headerPanelHeight); + mainPanel.addNorth(headerPanel, HeaderPanelHeight); mainPanel.add(filterSplitPanel); // Storing the different component providers in a list providers = new ArrayList<>(); - providers.add(retrieverChainProvider); - providers.add(extractionFunctionProvider); - providers.add(aggregationDefinitionProvider); + providers.add(statisticProvider); providers.add(groupingProvider); providers.add(filterSelectionProvider); - - // Set await reload flag to initialize the components, when the retriever chains have been loaded - providers.forEach(provider -> provider.awaitReloadComponents()); + reloadComponents(); } /** @@ -197,11 +189,15 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition final Collection dimensionsToGroupBy = groupingProvider.getDimensionsToGroupBy(); if (!dimensionsToGroupBy.isEmpty()) { final FunctionDTO firstDimension = dimensionsToGroupBy.iterator().next(); - filterSelectionProvider.setHighestRetrieverLevelWithFilterDimension(firstDimension, - (Serializable) groupKeyForSingleDimension.getValue()); if (dimensionsToGroupBy.size() == 1) { - letUserSelectADifferentFirstDimension(onSuccessCallback); + letUserSelectADifferentFirstDimension(() -> { + filterSelectionProvider.setHighestRetrieverLevelWithFilterDimension(firstDimension, + (Serializable) groupKeyForSingleDimension.getValue()); + onSuccessCallback.run(); + }); } else { + filterSelectionProvider.setHighestRetrieverLevelWithFilterDimension(firstDimension, + (Serializable) groupKeyForSingleDimension.getValue()); groupingProvider.removeDimensionToGroupBy(firstDimension); onSuccessCallback.run(); } @@ -265,9 +261,9 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition } @Override - public boolean isAwatingReload() { + public boolean isAwaitingReload() { for (DataMiningComponentProvider provider : providers) { - if (provider.isAwatingReload()) { + if (provider.isAwaitingReload()) { return true; } } @@ -276,27 +272,20 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition @Override public void reloadComponents() { - retrieverChainProvider.awaitReloadComponents(); - extractionFunctionProvider.awaitReloadComponents(); - aggregationDefinitionProvider.awaitReloadComponents(); - groupingProvider.awaitReloadComponents(); - filterSelectionProvider.awaitReloadComponents(); - retrieverChainProvider.reloadComponents(); + providers.forEach(provider -> provider.awaitReloadComponents()); + statisticProvider.reloadComponents(); } @Override public StatisticQueryDefinitionDTO getQueryDefinition() { ModifiableStatisticQueryDefinitionDTO queryDTO = new ModifiableStatisticQueryDefinitionDTO( - LocaleInfo.getCurrentLocale().getLocaleName(), extractionFunctionProvider.getExtractionFunction(), - aggregationDefinitionProvider.getAggregatorDefinition(), - retrieverChainProvider.getDataRetrieverChainDefinition()); - + LocaleInfo.getCurrentLocale().getLocaleName(), statisticProvider.getExtractionFunction(), + statisticProvider.getAggregatorDefinition(), statisticProvider.getDataRetrieverChainDefinition()); for (FunctionDTO dimension : groupingProvider.getDimensionsToGroupBy()) { queryDTO.appendDimensionToGroupBy(dimension); } - for (Entry retrieverSettingsEntry : retrieverChainProvider - .getRetrieverSettings().entrySet()) { + for (Entry retrieverSettingsEntry : statisticProvider.getRetrieverSettings().entrySet()) { queryDTO.setRetrieverSettings(retrieverSettingsEntry.getKey(), retrieverSettingsEntry.getValue()); } @@ -311,9 +300,7 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition @Override public void applyQueryDefinition(StatisticQueryDefinitionDTO queryDefinition) { setBlockChangeNotification(true); - retrieverChainProvider.applyQueryDefinition(queryDefinition); - extractionFunctionProvider.applyQueryDefinition(queryDefinition); - aggregationDefinitionProvider.applyQueryDefinition(queryDefinition); + statisticProvider.applyQueryDefinition(queryDefinition); groupingProvider.applyQueryDefinition(queryDefinition); filterSelectionProvider.applyQueryDefinition(queryDefinition); setBlockChangeNotification(false); @@ -373,12 +360,14 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition settings.setDeveloperOptions(newSettings.isDeveloperOptions()); if (settings.isDeveloperOptions()) { addControl(queryDefinitionViewerToggleButton); - filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), - !queryDefinitionViewerToggleButton.getValue()); + boolean queryDefinitionViewerActive = queryDefinitionViewerToggleButton.isDown(); + filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), !queryDefinitionViewerActive); + queryDefinitionViewer.setActive(queryDefinitionViewerActive); addControl(predefinedQueryRunner.getEntryWidget()); } else { removeControl(queryDefinitionViewerToggleButton); filterSplitPanel.setWidgetHidden(queryDefinitionViewer.getEntryWidget(), true); + queryDefinitionViewer.setActive(false); removeControl(predefinedQueryRunner.getEntryWidget()); } } @@ -394,14 +383,11 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition return "QueryDefinitionProviderWithControls"; } - private class ProviderListener - implements DataRetrieverChainDefinitionChangedListener, ExtractionFunctionChangedListener, - AggregatorDefinitionChangedListener, GroupingChangedListener, FilterSelectionChangedListener { + private class ProviderListener implements StatisticChangedListener, FilterSelectionChangedListener, GroupingChangedListener { @Override - public void dataRetrieverChainDefinitionChanged( - DataRetrieverChainDefinitionDTO newDataRetrieverChainDefinition) { - if (isAwatingReload()) { + public void dataRetrieverChainDefinitionChanged(DataRetrieverChainDefinitionDTO newDataRetrieverChainDefinition) { + if (isAwaitingReload()) { groupingProvider.dataRetrieverChainDefinitionChanged(newDataRetrieverChainDefinition); groupingProvider.reloadComponents(); @@ -414,28 +400,28 @@ public class QueryDefinitionProviderWithControls extends AbstractQueryDefinition @Override public void aggregatorDefinitionChanged(AggregationProcessorDefinitionDTO newAggregatorDefinition) { - if (!isAwatingReload()) { + if (!isAwaitingReload()) { notifyQueryDefinitionChanged(); } } @Override public void extractionFunctionChanged(FunctionDTO extractionFunction) { - if (!isAwatingReload()) { + if (!isAwaitingReload()) { notifyQueryDefinitionChanged(); } } @Override public void groupingChanged() { - if (!isAwatingReload()) { + if (!isAwaitingReload()) { notifyQueryDefinitionChanged(); } } @Override public void selectionChanged() { - if (!isAwatingReload()) { + if (!isAwaitingReload()) { notifyQueryDefinitionChanged(); } } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SimpleDataRetrieverChainDefinitionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SimpleDataRetrieverChainDefinitionProvider.java index f4ca492b8ae..2e426dac9b3 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SimpleDataRetrieverChainDefinitionProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SimpleDataRetrieverChainDefinitionProvider.java @@ -102,7 +102,7 @@ public class SimpleDataRetrieverChainDefinitionProvider extends AbstractDataMini } @Override - public boolean isAwatingReload() { + public boolean isAwaitingReload() { return isAwaitingReload; } diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java index c16abbae685..c06d793afd0 100644 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/SuggestBoxStatisticProvider.java @@ -11,6 +11,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Set; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.i18n.client.LocaleInfo; @@ -99,6 +100,8 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent(); @@ -114,16 +117,21 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent { + int keyCode = e.getNativeEvent().getKeyCode(); + if (keyCode == KeyCodes.KEY_ESCAPE) { + extractionFunctionSuggestBox.hideSuggestionList(); + extractionFunctionSuggestBox.setFocus(false); + } + }); extractionFunctionSuggestBox.setLimit(Integer.MAX_VALUE); extractionFunctionSuggestBox.addStyleName(STATISTIC_PROVIDER_ELEMENT_STYLE); - extractionFunctionSuggestBox.setWidth("70%"); + extractionFunctionSuggestBox.setWidth("540px"); mainPanel.add(extractionFunctionSuggestBox); aggregatorListBox = createAggregatorListBox(); aggregatorListBox.addStyleName(STATISTIC_PROVIDER_ELEMENT_STYLE); mainPanel.add(aggregatorListBox); - - updateContent(); } private ValueListBox createAggregatorListBox() { @@ -149,7 +157,7 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent { private final AbstractListSuggestOracle suggestOracle; + private final ScrollableSuggestionDisplay display; + private ExtractionFunctionWithContext extractionFunction; @SuppressWarnings("unchecked") public ExtractionFunctionSuggestBox() { super(new AbstractListSuggestOracle() { + @Override + protected Iterable getKeywordStrings(Iterable queryTokens) { + String filterText = Util.first(queryTokens); + if (filterText == null) { + return queryTokens; + } + return Util.splitAlongWhitespaceRespectingDoubleQuotedPhrases(filterText); + } @Override protected Iterable getMatchingStrings(ExtractionFunctionWithContext value) { @@ -587,8 +606,14 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent) getSuggestOracle(); + display = (ScrollableSuggestionDisplay) getSuggestionDisplay(); addSuggestionSelectionHandler(this::setExtractionFunction); } + + @Override + public void hideSuggestionList() { + display.hideSuggestions(); + } public void setSelectableValues(Collection selectableValues) { suggestOracle.setSelectableValues(selectableValues); @@ -597,6 +622,7 @@ public class SuggestBoxStatisticProvider extends AbstractDataMiningComponent { private static final DataMiningResources resources = GWT.create(DataMiningResources.class); - + private static final NaturalComparator NaturalComparator = new NaturalComparator(); + private final DataMiningServiceAsync dataMiningService; private final ErrorReporter errorReporter; private final DataMiningSession session; - private final RetrieverLevelFilterSelectionProvider retrieverLevelSelectionProvider; private final ManagedDataMiningQueriesCounter counter; + private final Set listeners; - private final FlowPanel mainPanel; - - private final HorizontalPanel controlsPanel; - private final ValueListBox dimensionListBox; - private final DimensionChangedHandler dimensionChangedHandler; - private final ToggleButton toggleFilterButton; - + private final DataRetrieverChainDefinitionProvider retrieverChainProvider; + private final FilterSelectionProvider filterSelectionProvider; + + private final DataRetrieverLevelDTO retrieverLevel; + private final FunctionDTO dimension; + private final Set selectionToBeApplied; + + private final DockLayoutPanel mainPanel; + private final AbstractFilterablePanel filterPanel; + private final LayoutPanel contentContainer; private final SimpleBusyIndicator busyIndicator; - private final FilterableSelectionTable selectionTable; - private Collection selectionToBeApplied; - public DimensionFilterSelectionProvider(DataMiningServiceAsync dataMiningService, ErrorReporter errorReporter, - DataMiningSession session, RetrieverLevelFilterSelectionProvider retrieverLevelSelectionProvider) { + private final ListDataProvider filteredData; + private final MultiSelectionModel selectionModel; + private final DataGrid dataGrid; + private final Column checkboxColumn; + + public DimensionFilterSelectionProvider(Component parent, ComponentContext componentContext, DataMiningServiceAsync dataMiningService, + ErrorReporter errorReporter, DataMiningSession session, DataRetrieverChainDefinitionProvider retrieverChainProvider, + FilterSelectionProvider filterSelectionProvider, DataRetrieverLevelDTO retrieverLevel, FunctionDTO dimension) { + super(parent, componentContext); this.dataMiningService = dataMiningService; this.errorReporter = errorReporter; this.session = session; - this.retrieverLevelSelectionProvider = retrieverLevelSelectionProvider; + this.retrieverChainProvider = retrieverChainProvider; + this.filterSelectionProvider = filterSelectionProvider; + this.retrieverLevel = retrieverLevel; + this.dimension = dimension; + counter = new SimpleManagedDataMiningQueriesCounter(); + listeners = new HashSet<>(); + selectionToBeApplied = new HashSet<>(); - mainPanel = new FlowPanel(); - - controlsPanel = new HorizontalPanel(); - controlsPanel.setSpacing(2); - mainPanel.add(controlsPanel); - - dimensionListBox = new ValueListBox(new AbstractObjectRenderer() { + DataMiningDataGridResources dataGridResources = GWT.create(DataMiningDataGridResources.class); + dataGrid = new DataGrid<>(Integer.MAX_VALUE, dataGridResources); + dataGrid.setAutoHeaderRefreshDisabled(true); + dataGrid.setAutoFooterRefreshDisabled(true); + dataGrid.addStyleName("dataMiningBorderTop"); + + filteredData = new ListDataProvider(this::elementAsString); + filterPanel = new AbstractFilterablePanel(null, dataGrid, filteredData) { @Override - protected String convertObjectToString(FunctionDTO function) { - return function.getDisplayName(); + public Iterable getSearchableStrings(Serializable element) { + return Collections.singleton(elementAsString(element)); } - }); - dimensionListBox.setWidth("100%"); - dimensionChangedHandler = new DimensionChangedHandler(); - dimensionListBox.addValueChangeHandler(dimensionChangedHandler); - controlsPanel.add(dimensionListBox); - - toggleFilterButton = new ToggleButton(new Image(resources.searchIcon())); - toggleFilterButton.addClickHandler(new ClickHandler() { + }; + filterPanel.setWidth("100%"); + filterPanel.setSpacing(1); + filterPanel.getTextBox().setWidth("100%"); + filterPanel.getAllListDataProvider().addDataDisplay(dataGrid); + + selectionModel = new MultiSelectionModel<>(this::elementAsString); + selectionModel.addSelectionChangeHandler(this::selectionChanged); + dataGrid.setSelectionModel(selectionModel, DefaultSelectionEventManager.createCustomManager(new CustomCheckboxEventTranslator())); + + checkboxColumn = new Column(new CheckboxCell(true, false)) { @Override - public void onClick(ClickEvent event) { - selectionTable.setFilteringEnabled(toggleFilterButton.isDown()); + public Boolean getValue(Serializable object) { + return selectionModel.isSelected(object); } - }); - toggleFilterButton.setVisible(false); - controlsPanel.add(toggleFilterButton); - - busyIndicator = new SimpleBusyIndicator(true, 0.7f); - busyIndicator.setVisible(false); - mainPanel.add(busyIndicator); - - selectionTable = new FilterableSelectionTable<>(); - selectionTable.addSelectionChangeHandler(new FilterSelectionChangedListener() { + }; + dataGrid.addColumn(checkboxColumn); + TextColumn contentColumn = new TextColumn() { @Override - public void selectionChanged() { - DimensionFilterSelectionProvider.this.retrieverLevelSelectionProvider - .dimensionFilterSelectionChanged(DimensionFilterSelectionProvider.this); + public String getValue(Serializable element) { + return elementAsString(element); } - }); - selectionTable.setVisible(false); - mainPanel.add(selectionTable.getWidget()); + }; + contentColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); + dataGrid.addColumn(contentColumn); + + busyIndicator = new SimpleBusyIndicator(false, 0.85f); + busyIndicator.getElement().getStyle().setTextAlign(TextAlign.CENTER); + + contentContainer = new LayoutPanel(); + contentContainer.add(busyIndicator); + contentContainer.setWidgetTopBottom(busyIndicator, 10, Unit.PX, 10, Unit.PX); + contentContainer.setWidgetLeftRight(busyIndicator, 10, Unit.PX, 10, Unit.PX); + + mainPanel = new DockLayoutPanel(Unit.PX); + mainPanel.addNorth(createHeaderPanel(), 40); + mainPanel.addNorth(filterPanel, 35); + mainPanel.setWidgetHidden(filterPanel, true); + mainPanel.add(contentContainer); + + updateContent(null); } - private class DimensionChangedHandler implements ValueChangeHandler { - boolean firstChange = true; + private Widget createHeaderPanel() { + HorizontalPanel headerPanel = new HorizontalPanel(); + headerPanel.setSpacing(2); + headerPanel.setWidth("100%"); + headerPanel.setHeight("100%"); + headerPanel.addStyleName("dimensionFilterSelectionHeader"); + headerPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); + + Label headerLabel = new Label(dimension.getDisplayName()); + headerPanel.add(headerLabel); + headerPanel.setCellWidth(headerLabel, "100%"); + headerPanel.setCellHorizontalAlignment(headerLabel, HasHorizontalAlignment.ALIGN_CENTER); - @Override - public void onValueChange(ValueChangeEvent event) { - final FunctionDTO dimension = event.getValue(); - if (dimension != null) { - fetchAndSetAvailableData(); - if (firstChange && retrieverLevelSelectionProvider.canAddDimensionSelectionProvider()) { - retrieverLevelSelectionProvider.createAndAddDimensionSelectionProvider(); - } - firstChange = false; - } else if (retrieverLevelSelectionProvider.shouldRemoveDimensionSelectionProvider()) { - retrieverLevelSelectionProvider.removeDimensionFilter(DimensionFilterSelectionProvider.this); - firstChange = false; - } else { - // only one dimension remaining for selection; there is no additional dimension selection provider - // that would remain if we remove the current one; therefore, clear the selection on the current one - // and see what the user does with it... - selectionTable.clearSelection(); - selectionTable.setContent(new ArrayList<>(), true); - selectionTable.setVisible(false); - toggleFilterButton.setVisible(false); - firstChange = true; + ToggleButton toggleFilterButton = new ToggleButton(new Image(resources.searchIcon())); + toggleFilterButton.addClickHandler(e -> { + boolean enabled = toggleFilterButton.isDown(); + mainPanel.setWidgetHidden(filterPanel, !enabled); + if (enabled) { + Scheduler.get().scheduleDeferred(() -> { + filterPanel.getTextBox().setFocus(true); + filterPanel.getTextBox().selectAll(); + }); } - retrieverLevelSelectionProvider.updateAvailableDimensions(); + + ListDataProvider oldProvider = enabled ? filterPanel.getAllListDataProvider(): filteredData; + ListDataProvider newProvider = enabled ? filteredData : filterPanel.getAllListDataProvider(); + oldProvider.removeDataDisplay(dataGrid); + newProvider.addDataDisplay(dataGrid); + }); + headerPanel.add(toggleFilterButton); + headerPanel.setCellHorizontalAlignment(toggleFilterButton, HasHorizontalAlignment.ALIGN_RIGHT); + + return headerPanel; + } + + public void updateContent(Runnable callback) { + HashMap retrieverSettings = retrieverChainProvider.getRetrieverSettings(); + HashMap>> filterSelection = filterSelectionProvider.getSelection(); + if (filterSelection.containsKey(retrieverLevel)) { + filterSelection.get(retrieverLevel).remove(dimension); } - } - - private void fetchAndSetAvailableData() { - fetchAndDisplayAvailableData(false, true, null); - } - - void fetchAndUpdateAvailableData( - final Iterator retrieverLevelSelectionProviderIterator) { - fetchAndDisplayAvailableData(true, false, retrieverLevelSelectionProviderIterator); - } - - /** - * Fetches the dimension values from the server and displays it. The data is filtered by the current filter - * selection (of all retriever levels). - * - * @param isUpdate - * true, if the call should update the data and preserve the selection and - * false, if it should override the data and clear the selection. - * @param selectionChangedCallback - * An {@link AsyncCallback} to react, if the selection has been changed or not. - */ - private void fetchAndDisplayAvailableData(final boolean isUpdate, final boolean notifyListenersWhenSelectionChanged, - final Iterator retrieverLevelSelectionProviderIterator) { - final FunctionDTO dimension = getSelectedDimension(); - HashSet dimensionDTOs = new HashSet<>(); - dimensionDTOs.add(dimension); - HashMap retrieverSettingsDTO = retrieverLevelSelectionProvider - .getRetrieverSettings(); - HashMap>> filterSelectionDTO = retrieverLevelSelectionProvider - .getCompleteFilterSelection(); - DataRetrieverLevelDTO retrieverLevel = retrieverLevelSelectionProvider.getRetrieverLevel(); - if (filterSelectionDTO.containsKey(retrieverLevel)) { - filterSelectionDTO.get(retrieverLevel).remove(dimension); - } - busyIndicator.setVisible(true); + HashSet dimensions = new HashSet<>(); + dimensions.add(dimension); + counter.increase(); - dataMiningService.getDimensionValuesFor(session, retrieverLevelSelectionProvider.getDataRetrieverChain(), - retrieverLevel, dimensionDTOs, retrieverSettingsDTO, filterSelectionDTO, - LocaleInfo.getCurrentLocale().getLocaleName(), - new ManagedDataMiningQueryCallback>(counter) { + contentContainer.remove(dataGrid); + busyIndicator.setBusy(true); + dataMiningService.getDimensionValuesFor(session, retrieverChainProvider.getDataRetrieverChainDefinition(), retrieverLevel, dimensions, + retrieverSettings, filterSelection, LocaleInfo.getCurrentLocale().getLocaleName(), new ManagedDataMiningQueryCallback>(counter) { + @SuppressWarnings("unchecked") @Override protected void handleSuccess(QueryResultDTO> result) { Map> results = result.getResults(); - List content = new ArrayList(); - + List content = new ArrayList<>(); + if (!results.isEmpty()) { GroupKey contentKey = new GenericGroupKey(dimension); - content.addAll(results.get(contentKey)); - Collections.sort(content, new Comparator() { - @Override - public int compare(Object o1, Object o2) { - Comparator naturalComparator = new NaturalComparator(); - return naturalComparator.compare(o1.toString(), o2.toString()); - } - }); + content.addAll((Collection) results.get(contentKey)); + content.sort((o1, o2) -> NaturalComparator.compare(o1.toString(), o2.toString())); } - - boolean selectionChanged; - if (!isUpdate || content.isEmpty()) { - selectionChanged = selectionTable.setContent(content, notifyListenersWhenSelectionChanged); - } else { - selectionChanged = selectionTable.updateContent(content, - notifyListenersWhenSelectionChanged); - } - updateSelectionTable(notifyListenersWhenSelectionChanged); - busyIndicator.setVisible(false); - selectionTable.setVisible(true); - toggleFilterButton.setVisible(true); - - if (retrieverLevelSelectionProviderIterator != null) { - if (selectionChanged) { - // Update the complete retriever level, because the selection changed - retrieverLevelSelectionProvider.updateAvailableData(getSelectedDimension()); - } else { - // Continue with the update of the retriever level selection provider - retrieverLevelSelectionProvider.updateAvailableData(getSelectedDimension(), - retrieverLevelSelectionProviderIterator); - } + + busyIndicator.setBusy(false); + filterPanel.updateAll(content); + contentContainer.add(dataGrid); + + internalSetSelection(!selectionToBeApplied.isEmpty() ? selectionToBeApplied : selectionModel.getSelectedSet()); + selectionToBeApplied.clear(); + + if (callback != null) { + callback.run(); } } - @Override protected void handleFailure(Throwable caught) { - errorReporter.reportError( - "Error fetching the dimension values of " + dimension + ": " + caught.getMessage()); + errorReporter.reportError("Error fetching the dimension values of " + dimension + ": " + caught.getMessage()); } }); } - - /** - * If {@code #selectionToBeApplied} is not {@code null}, the table displaying the selection will have its selection - * model updated based on the contents of {@link #selectionToBeApplied}, and {@link #selectionToBeApplied} will be - * set to {@code null} afterwards. - * - * @param notifyListenersWhenSelectionChanged - * if {@code true}, selection listeners will be notified about any change that is caused by invoking this - * method - */ - private void updateSelectionTable(final boolean notifyListenersWhenSelectionChanged) { - if (selectionToBeApplied != null) { - selectionTable.setSelection(selectionToBeApplied, notifyListenersWhenSelectionChanged); - selectionToBeApplied = null; - } + + private String elementAsString(Object element) { + return element.toString(); } - - void setAvailableDimensions(Collection availableDimensions) { - dimensionListBox.setAcceptableValues(availableDimensions); - } - - public FunctionDTO getSelectedDimension() { - return dimensionListBox.getValue(); + + private void selectionChanged(SelectionChangeEvent event) { + notifyListeners(); } public HashSet getSelection() { - return selectionTable.getSelection(); + return new HashSet<>(selectionModel.getSelectedSet()); + } + + public void setSelection(Iterable items) { + selectionToBeApplied.clear(); + for (Serializable item : items) { + selectionToBeApplied.add(item); + } + + if (!busyIndicator.isBusy()) { + internalSetSelection(selectionToBeApplied); + selectionToBeApplied.clear(); + } } - void clearSelection() { - selectionTable.clearSelection(); + private void internalSetSelection(Set selection) { + clearSelection(); + if (!selection.isEmpty()) { + for (Serializable item : filterPanel.getAll()) { + if (selection.contains(item)) { + selectionModel.setSelected(item, true); + } + } + } } - void setSelectedDimensionAndValues(FunctionDTO functionDTO, Collection selection) { - dimensionChangedHandler.firstChange = false; - dimensionListBox.setValue(functionDTO, true); - selectionToBeApplied = selection; + public void clearSelection() { + selectionModel.clear(); } + public void addListener(FilterSelectionChangedListener listener) { + listeners.add(listener); + } + + private void notifyListeners() { + for (FilterSelectionChangedListener listener : listeners) { + listener.selectionChanged(); + } + } + + @Override public Widget getEntryWidget() { return mainPanel; } -} \ No newline at end of file + @Override + public String getId() { + return "DimensionFilterSelectionProvider"; + } + + @Override + public String getLocalizedShortName() { + return getClass().getSimpleName(); + } + + @Override + public boolean isVisible() { + return getEntryWidget().isVisible(); + } + + @Override + public void setVisible(boolean visibility) { + getEntryWidget().setVisible(visibility); + } + + @Override + public boolean hasSettings() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent(SerializableSettings settings) { + return null; + } + + @Override + public SerializableSettings getSettings() { + return null; + } + + @Override + public void updateSettings(SerializableSettings newSettings) { + // no-op + } + + @Override + public String getDependentCssClassName() { + return "dimensionFilterSelectionProvider"; + } + + private class CustomCheckboxEventTranslator implements EventTranslator { + + @Override + public boolean clearCurrentSelection(CellPreviewEvent event) { + return !isCheckboxColumn(event.getColumn()); + } + + @Override + public SelectAction translateSelectionEvent(CellPreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (BrowserEvents.CLICK.equals(nativeEvent.getType())) { + if (nativeEvent.getCtrlKey()) { + Serializable value = event.getValue(); + selectionModel.setSelected(value, !selectionModel.isSelected(value)); + return SelectAction.IGNORE; + } + if (!selectionModel.getSelectedSet().isEmpty() && !isCheckboxColumn(event.getColumn())) { + return SelectAction.DEFAULT; + } + } + return SelectAction.TOGGLE; + } + + private boolean isCheckboxColumn(int columnIndex) { + return columnIndex == dataGrid.getColumnIndex(checkboxColumn); + } + + } + +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterTableResources.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterTableResources.java deleted file mode 100644 index e3c23ced6a4..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterTableResources.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection.filter; - -import com.google.gwt.user.cellview.client.CellTable; - -public interface FilterTableResources extends CellTable.Resources { - - @Source({ CellTable.Style.DEFAULT_CSS, "FilterTable.css" }) - FilterTableStyle cellTableStyle(); - - interface FilterTableStyle extends CellTable.Style { - } -} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterableSelectionTable.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterableSelectionTable.java deleted file mode 100644 index cd0332ae432..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/FilterableSelectionTable.java +++ /dev/null @@ -1,274 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection.filter; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import com.google.gwt.cell.client.CheckboxCell; -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.BrowserEvents; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.cellview.client.CellTable; -import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.TextColumn; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.HasHorizontalAlignment; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.CellPreviewEvent; -import com.google.gwt.view.client.DefaultSelectionEventManager; -import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction; -import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.ProvidesKey; -import com.google.gwt.view.client.SelectionChangeEvent; -import com.sap.sse.datamining.ui.client.FilterSelectionChangedListener; -import com.sap.sse.gwt.client.celltable.BaseCelltable; -import com.sap.sse.gwt.client.panels.AbstractFilterablePanel; - -public class FilterableSelectionTable { - - private final Set listeners; - - private final Collection allData; - - private final FlowPanel mainPanel; - private final AbstractFilterablePanel filterPanel; - private final CellTable table; - private final ControllableMultiSelectionModel selectionModel; - private final ListDataProvider dataProvider; - - public FilterableSelectionTable() { - listeners = new HashSet<>(); - - allData = new ArrayList(); - - FilterTableResources tableRes = GWT.create(FilterTableResources.class); - table = new BaseCelltable<>(Integer.MAX_VALUE, tableRes); - table.setWidth("100%"); - table.setAutoHeaderRefreshDisabled(true); - table.setAutoFooterRefreshDisabled(true); - - TextColumn contentColumn = new TextColumn() { - @Override - public String getValue(ContentType content) { - return FilterableSelectionTable.this.getElementAsString(content); - } - }; - contentColumn.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER); - - selectionModel = new ControllableMultiSelectionModel<>(); - final Column checkColumn = new Column( - new CheckboxCell(true, false)) { - @Override - public Boolean getValue(ContentType object) { - return selectionModel.isSelected(object); - } - }; - - dataProvider = new ListDataProvider(new ProvidesKey() { - @Override - public Object getKey(ContentType item) { - return getElementAsString(item); - } - }); - dataProvider.addDataDisplay(table); - - table.addColumn(checkColumn); - table.addColumn(contentColumn); - - selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - notifyListeners(); - } - }); - table.setSelectionModel(selectionModel, DefaultSelectionEventManager. createCustomManager( - new DefaultSelectionEventManager.CheckboxEventTranslator() { - @Override - public boolean clearCurrentSelection(CellPreviewEvent event) { - return !isCheckboxColumn(event.getColumn()); - } - - @Override - public SelectAction translateSelectionEvent(CellPreviewEvent event) { - NativeEvent nativeEvent = event.getNativeEvent(); - if (BrowserEvents.CLICK.equals(nativeEvent.getType())) { - if (nativeEvent.getCtrlKey()) { - ContentType value = event.getValue(); - selectionModel.setSelected(value, !selectionModel.isSelected(value)); - return SelectAction.IGNORE; - } - if (!selectionModel.getSelectedSet().isEmpty() && !isCheckboxColumn(event.getColumn())) { - return SelectAction.DEFAULT; - } - } - return SelectAction.TOGGLE; - } - - private boolean isCheckboxColumn(int columnIndex) { - return columnIndex == table.getColumnIndex(checkColumn); - } - })); - - filterPanel = new AbstractFilterablePanel(allData, table, dataProvider) { - @Override - public Iterable getSearchableStrings(ContentType content) { - Collection searchableStrings = new ArrayList(); - searchableStrings.add(getElementAsString(content)); - return searchableStrings; - } - }; - filterPanel.setSpacing(2); - filterPanel.setWidth("95%"); - filterPanel.getTextBox().setWidth("95%"); - filterPanel.setVisible(false); - - mainPanel = new FlowPanel(); - mainPanel.add(filterPanel); - mainPanel.add(table); - } - - private String getElementAsString(ContentType element) { - return element.toString(); - } - - /** - * Replaces the current content of the table with the new one and clears the current selection.
      - * Returns true and notifies the listeners, if the selection changed. - * - * @param newContent - */ - @SuppressWarnings("unchecked") // You can't use instanceof for generic type parameters - public boolean setContent(Collection newContent, boolean notifyListenersWhenSelectionChanged) { - Collection specificNewContent = null; - try { - specificNewContent = (Collection) newContent; - } catch (ClassCastException e) { - return false; - } - internalSetContent(specificNewContent); - Set previousSelection = selectionModel.getSelectedSet(); - if (notifyListenersWhenSelectionChanged) { - clearSelection(); - } else { - selectionModel.setBlockNotifications(true); - clearSelection(); - selectionModel.setBlockNotifications(false); - } - return !previousSelection.isEmpty(); - } - - /** - * Replaces the current content of the table with the new one, while preserving the current selection.
      - * Returns true and notifies the listeners, if previously selected elements have been removed. - * - * @param newContent - * @return true, if previously selected elements have been removed. - */ - @SuppressWarnings("unchecked") // You can't use instanceof for generic type parameters - public boolean updateContent(Collection newContent, boolean notifyListenersWhenSelectionChanged) { - Collection specificNewContent = null; - try { - specificNewContent = (Collection) newContent; - } catch (ClassCastException e) { - return false; - } - internalSetContent(specificNewContent); - return cleanSelection(notifyListenersWhenSelectionChanged); - } - - private void internalSetContent(Collection specificNewContent) { - allData.clear(); - allData.addAll(specificNewContent); - dataProvider.getList().clear(); - dataProvider.getList().addAll(allData); - filterPanel.updateAll(allData); - } - - /** - * Unselects the selected elements, that aren't in the data anymore. - * - * @return true, if elements have been unselected; - */ - private boolean cleanSelection(boolean notifyListenersWhenSelectionChanged) { - boolean selectionChanged = false; - selectionModel.setBlockNotifications(true); - - for (ContentType selectedElement : getSelection()) { - Object selectedElementKey = dataProvider.getKey(selectedElement); - boolean contentContainsSelectedElement = false; - for (ContentType contentElement : allData) { - if (selectedElementKey.equals(dataProvider.getKey(contentElement))) { - contentContainsSelectedElement = true; - break; - } - } - if (!contentContainsSelectedElement) { - selectionModel.setSelected(selectedElement, false); - selectionChanged = true; - } - } - - selectionModel.setBlockNotifications(false); - if (selectionChanged && notifyListenersWhenSelectionChanged) { - notifyListeners(); - } - return selectionChanged; - } - - private void notifyListeners() { - for (FilterSelectionChangedListener listener : listeners) { - listener.selectionChanged(); - } - } - - public HashSet getSelection() { - return new HashSet<>(selectionModel.getSelectedSet()); - } - - public void setSelection(Iterable elements, boolean notifyListenersWhenSelectionChanged) { - try { - @SuppressWarnings("unchecked") // You can't use instanceof for generic type parameters - Iterable specificContent = (Iterable) elements; - selectionModel.setBlockNotifications(!notifyListenersWhenSelectionChanged); - clearSelection(); - for (ContentType element : specificContent) { - selectionModel.setSelected(element, true); - } - if (!notifyListenersWhenSelectionChanged) { - selectionModel.setBlockNotifications(false); - } - } catch (ClassCastException e) { - /* Ignore the elements, because they don't match the ContentType */} - } - - public void clearSelection() { - selectionModel.clear(); - } - - public void addSelectionChangeHandler(FilterSelectionChangedListener handler) { - listeners.add(handler); - } - - public boolean isFilteringEnabled() { - return filterPanel.isVisible(); - } - - public void setFilteringEnabled(boolean visible) { - filterPanel.setVisible(visible); - if (visible) { - filterPanel.getTextBox().setFocus(true); - filterPanel.getTextBox().selectAll(); - } - } - - public void setVisible(boolean visible) { - mainPanel.setVisible(visible); - } - - public Widget getWidget() { - return mainPanel; - } - -} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java new file mode 100644 index 00000000000..0581878a35f --- /dev/null +++ b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/HierarchicalDimensionListFilterSelectionProvider.java @@ -0,0 +1,618 @@ +package com.sap.sse.datamining.ui.client.selection.filter; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.gwt.cell.client.CheckboxCell; +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.builder.shared.TableCellBuilder; +import com.google.gwt.dom.builder.shared.TableRowBuilder; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.NodeList; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.TableCellElement; +import com.google.gwt.dom.client.TableRowElement; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.user.cellview.client.AbstractCellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.cellview.client.DataGrid; +import com.google.gwt.user.cellview.client.RowHoverEvent; +import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ScrollPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.CellPreviewEvent; +import com.google.gwt.view.client.DefaultSelectionEventManager; +import com.google.gwt.view.client.DefaultSelectionEventManager.EventTranslator; +import com.google.gwt.view.client.DefaultSelectionEventManager.SelectAction; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.MultiSelectionModel; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.sap.sse.common.settings.SerializableSettings; +import com.sap.sse.datamining.shared.DataMiningSession; +import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; +import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; +import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; +import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; +import com.sap.sse.datamining.shared.impl.dto.ReducedDimensionsDTO; +import com.sap.sse.datamining.ui.client.DataMiningServiceAsync; +import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionProvider; +import com.sap.sse.datamining.ui.client.FilterSelectionChangedListener; +import com.sap.sse.datamining.ui.client.FilterSelectionPresenter; +import com.sap.sse.datamining.ui.client.FilterSelectionProvider; +import com.sap.sse.datamining.ui.client.StringMessages; +import com.sap.sse.datamining.ui.client.presentation.PlainFilterSelectionPresenter; +import com.sap.sse.datamining.ui.client.resources.DataMiningDataGridResources; +import com.sap.sse.datamining.ui.client.resources.DataMiningDataGridResources.DataMiningDataGridStyle; +import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.celltable.BaseCellTableBuilder; +import com.sap.sse.gwt.client.panels.AbstractFilterablePanel; +import com.sap.sse.gwt.client.shared.components.AbstractComponent; +import com.sap.sse.gwt.client.shared.components.Component; +import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; +import com.sap.sse.gwt.client.shared.settings.ComponentContext; + +public class HierarchicalDimensionListFilterSelectionProvider extends AbstractComponent implements FilterSelectionProvider { + + private final static String DimensionListSubheaderAttribute = "subheader"; + + private static final Unit LayoutUnit = Unit.PX; + private static final double SelectionPresenterHeight = 100; + private static final double DimensionSelectionWidth = 300; + private static final double DimensionSelectionHeaderHeight = 40; + private static final double FilterSelectionTableWidth = 250; + + private final DataMiningSession session; + private final DataMiningServiceAsync dataMiningService; + private final ErrorReporter errorReporter; + private final DataRetrieverChainDefinitionProvider retrieverChainProvider; + private final Set listeners; + + private boolean isAwaitingReload; + private DataRetrieverChainDefinitionDTO retrieverChain; + private ReducedDimensionsDTO reducedDimensions; + private final List allFilterDimensions; + private final ListDataProvider filteredFilterDimensions; + + private final DockLayoutPanel mainPanel; + + private final AbstractFilterablePanel filterFilterDimensionsPanel; + private final MultiSelectionModel filterDimensionSelectionModel; + private final DataGrid filterDimensionsList; + private final Column checkboxColumn; + + private final DockLayoutPanel dimensionFilterSelectionProvidersPanel; + private final Map dimensionFilterSelectionProviders; + private boolean updateInProgress; + + private final FilterSelectionPresenter filterSelectionPresenter; + private final ScrollPanel filterSelectionPresenterContainer; + + public HierarchicalDimensionListFilterSelectionProvider(Component parent, ComponentContext context, + DataMiningSession session, StringMessages stringMessages, + DataMiningServiceAsync dataMiningService, ErrorReporter errorReporter, + DataRetrieverChainDefinitionProvider retrieverChainProvider) { + super(parent, context); + this.session = session; + this.dataMiningService = dataMiningService; + this.errorReporter = errorReporter; + this.retrieverChainProvider = retrieverChainProvider; + retrieverChainProvider.addDataRetrieverChainDefinitionChangedListener(this); + + listeners = new HashSet<>(); + isAwaitingReload = false; + retrieverChain = null; + allFilterDimensions = new ArrayList<>(); + filteredFilterDimensions = new ListDataProvider<>(); + + Label filterDimensionsSelectionTitleLabel = new Label(stringMessages.selectDimensionsToFilterBy()); + filterDimensionsSelectionTitleLabel.addStyleName("emphasizedLabel"); + filterDimensionsSelectionTitleLabel.addStyleName("dataMiningMarginLeft"); + filterDimensionsSelectionTitleLabel.addStyleName("filterDimensionsTitleLabel"); + + DataMiningDataGridResources resources = GWT.create(DataMiningDataGridResources.class); + filterDimensionsList = new DataGrid<>(Integer.MAX_VALUE, resources); + filterDimensionsList.setAutoHeaderRefreshDisabled(true); + filterDimensionsList.setAutoFooterRefreshDisabled(true); + filterDimensionsList.setTableBuilder(new FilterDimensionsListBuilder(filterDimensionsList, resources.dataGridStyle())); + filteredFilterDimensions.addDataDisplay(filterDimensionsList); + + filterFilterDimensionsPanel = new AbstractFilterablePanel( + null, filterDimensionsList, filteredFilterDimensions) + { + @Override + public Iterable getSearchableStrings(DimensionWithContext dimension) { + return dimension.getMatchingStrings(); + } + }; + filterFilterDimensionsPanel.addStyleName("filterFilterDimensionsPanel"); + filterFilterDimensionsPanel.setSpacing(2); + filterFilterDimensionsPanel.setWidth("100%"); + filterFilterDimensionsPanel.setHeight("100%"); + filterFilterDimensionsPanel.getTextBox().setWidth("100%"); + filterFilterDimensionsPanel.getTextBox().getElement().setPropertyString("placeholder", stringMessages.filterShownDimensions()); + + filterDimensionSelectionModel = new MultiSelectionModel<>(); + filterDimensionSelectionModel.addSelectionChangeHandler(this::selectedFilterDimensionsChanged); + filterDimensionsList.setSelectionModel(filterDimensionSelectionModel, DefaultSelectionEventManager.createCustomManager(new CustomCheckboxEventTranslator())); + + checkboxColumn = new Column(new CheckboxCell(true, false)) { + @Override + public Boolean getValue(DimensionWithContext object) { + return filterDimensionSelectionModel.isSelected(object); + } + }; + filterDimensionsList.addColumn(checkboxColumn); + TextColumn dimensionColumn = new TextColumn() { + @Override + public String getValue(DimensionWithContext object) { + return object.getDimension().getDisplayName(); + } + }; + filterDimensionsList.addColumn(dimensionColumn); + + DockLayoutPanel filterDimensionsSelectionPanel = new DockLayoutPanel(LayoutUnit); + filterDimensionsSelectionPanel.addNorth(filterDimensionsSelectionTitleLabel, 20); + filterDimensionsSelectionPanel.addNorth(filterFilterDimensionsPanel, DimensionSelectionHeaderHeight); + filterDimensionsSelectionPanel.add(filterDimensionsList); + + dimensionFilterSelectionProviders = new HashMap<>(); + dimensionFilterSelectionProvidersPanel = new DockLayoutPanel(LayoutUnit); + dimensionFilterSelectionProvidersPanel.addStyleName("dimensionFilterSelectionTablesContainer"); + dimensionFilterSelectionProvidersPanel.addStyleName("dataMiningBorderLeft"); + + filterSelectionPresenter = new PlainFilterSelectionPresenter(this, context, stringMessages, retrieverChainProvider, this); + filterSelectionPresenter.getEntryWidget().addStyleName("dataMiningMarginLeft"); + filterSelectionPresenterContainer = new ScrollPanel(filterSelectionPresenter.getEntryWidget()); + filterSelectionPresenterContainer.addStyleName("dataMiningBorderTop"); + + mainPanel = new DockLayoutPanel(LayoutUnit); + mainPanel.addSouth(filterSelectionPresenterContainer, SelectionPresenterHeight); + mainPanel.setWidgetHidden(filterSelectionPresenterContainer, true); + mainPanel.addWest(filterDimensionsSelectionPanel, DimensionSelectionWidth); + mainPanel.add(dimensionFilterSelectionProvidersPanel); + } + + @Override + public void awaitReloadComponents() { + isAwaitingReload = true; + } + + @Override + public boolean isAwaitingReload() { + return isAwaitingReload; + } + + @Override + public void reloadComponents() { + updateFilterDimensions(); + isAwaitingReload = false; + notifyListeners(); + } + + @Override + public void dataRetrieverChainDefinitionChanged(DataRetrieverChainDefinitionDTO newDataRetrieverChainDefinition) { + if (!Objects.equals(retrieverChain, newDataRetrieverChainDefinition)) { + retrieverChain = newDataRetrieverChainDefinition; + if (!isAwaitingReload && retrieverChain != null) { + updateFilterDimensions(); + } else if (!isAwaitingReload) { + clearContent(); + } + } + } + + private void updateFilterDimensions() { + clearContent(); + dataMiningService.getReducedDimensionsMappedByLevelFor(retrieverChain, LocaleInfo.getCurrentLocale().getLocaleName(), new AsyncCallback() { + @Override + public void onSuccess(ReducedDimensionsDTO result) { + reducedDimensions = result; + for (Entry> entry : reducedDimensions.getReducedDimensions().entrySet()) { + DataRetrieverLevelDTO retrieverlevel = entry.getKey(); + for (FunctionDTO dimension : entry.getValue()) { + allFilterDimensions.add(new DimensionWithContext(dimension, retrieverlevel)); + } + } + allFilterDimensions.sort(null); + filterFilterDimensionsPanel.updateAll(allFilterDimensions); + } + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error fetching the dimensions of the retriever chain from the server: " + caught.getMessage()); + } + }); + } + + private void clearContent() { + reducedDimensions = null; + allFilterDimensions.clear(); + filterDimensionSelectionModel.clear(); + filterFilterDimensionsPanel.removeAll(); + } + + @Override + public void applyQueryDefinition(StatisticQueryDefinitionDTO queryDefinition) { + HashMap>> filterSelection = queryDefinition.getFilterSelection(); + for (DimensionWithContext dimensionWithContext : allFilterDimensions) { + HashMap> retrieverLevelSelection = filterSelection.get(dimensionWithContext.getRetrieverLevel()); + if (retrieverLevelSelection != null) { + HashSet items = retrieverLevelSelection.get(dimensionWithContext.getDimension()); + if (items != null) { + setSelection(dimensionWithContext, items); + } + } + } + } + + @Override + public void setHighestRetrieverLevelWithFilterDimension(FunctionDTO dimension, Serializable groupKey) { + FunctionDTO reducedDimension = reducedDimensions == null ? dimension : reducedDimensions.getReducedDimension(dimension); + Collection items = Collections.singleton(groupKey); + for (DimensionWithContext dimensionWithContext : allFilterDimensions) { + if (dimensionWithContext.getDimension().equals(reducedDimension)) { + setSelection(dimensionWithContext, items); + break; + } + } + } + + private void setSelection(DimensionWithContext dimension, Collection items) { + DimensionFilterSelectionProvider selectionProvider; + if (filterDimensionSelectionModel.isSelected(dimension)) { + selectionProvider = dimensionFilterSelectionProviders.get(dimension); + } else { + selectionProvider = addDimensionFilterSelectionProvider(dimension); + filterDimensionSelectionModel.setSelected(dimension, true); + } + selectionProvider.setSelection(items); + } + + private void selectedFilterDimensionsChanged(SelectionChangeEvent event) { + Iterable displayedDimensions = new HashSet<>(dimensionFilterSelectionProviders.keySet()); + for (DimensionWithContext displayedDimension : displayedDimensions) { + if (!filterDimensionSelectionModel.isSelected(displayedDimension)) { + removeDimensionFilterSelectionProvider(displayedDimension); + } + } + + for (DimensionWithContext selectedDimension : filterDimensionSelectionModel.getSelectedSet()) { + if (!dimensionFilterSelectionProviders.containsKey(selectedDimension)) { + addDimensionFilterSelectionProvider(selectedDimension); + } + } + } + + private DimensionFilterSelectionProvider addDimensionFilterSelectionProvider(DimensionWithContext dimension) { + DimensionFilterSelectionProvider selectionProvider = new DimensionFilterSelectionProvider(this, getComponentContext(), + dataMiningService, errorReporter, session, retrieverChainProvider, this, dimension.getRetrieverLevel(), dimension.getDimension()); + selectionProvider.addListener(() -> filterSelectionChanged(dimension)); + dimensionFilterSelectionProviders.put(dimension, selectionProvider); + + DimensionWithContext nextDimension = null; + for (DimensionWithContext displayedDimension : dimensionFilterSelectionProviders.keySet()) { + if (displayedDimension.compareTo(dimension) > 0 && + (nextDimension == null || displayedDimension.compareTo(nextDimension) < 0)) { + nextDimension = displayedDimension; + } + } + Widget beforeWidget = nextDimension != null ? dimensionFilterSelectionProviders.get(nextDimension).getEntryWidget() : null; + dimensionFilterSelectionProvidersPanel.insertWest(selectionProvider.getEntryWidget(), FilterSelectionTableWidth, beforeWidget); + + return selectionProvider; + } + + private void removeDimensionFilterSelectionProvider(DimensionWithContext dimension) { + DimensionFilterSelectionProvider selectionProvider = dimensionFilterSelectionProviders.get(dimension); + if (selectionProvider != null) { + selectionProvider.clearSelection(); + dimensionFilterSelectionProviders.remove(dimension); + dimensionFilterSelectionProvidersPanel.remove(selectionProvider.getEntryWidget()); + dimensionFilterSelectionProvidersPanel.animate(0); // Schedule Layout + } + } + + private void filterSelectionChanged(DimensionWithContext changedDimension) { + if (!updateInProgress) { + updateInProgress = true; + int levelToStartWith = changedDimension.getRetrieverLevel().getLevel(); + List dimensionsToUpdate = dimensionFilterSelectionProviders.keySet() + .stream().filter(d -> d.getRetrieverLevel().getLevel() >= levelToStartWith) + .collect(Collectors.toList()); + updateDimensionFilterSelectionProviders(dimensionsToUpdate.iterator(), changedDimension); + } + } + + private void updateDimensionFilterSelectionProviders(Iterator dimensionIterator, DimensionWithContext exceptDimension) { + if (dimensionIterator.hasNext()) { + DimensionWithContext dimension = dimensionIterator.next(); + if (dimension.equals(exceptDimension)) { + updateDimensionFilterSelectionProviders(dimensionIterator, exceptDimension); + } else { + DimensionFilterSelectionProvider selectionProvider = dimensionFilterSelectionProviders.get(dimension); + HashSet selectionBefore = selectionProvider.getSelection(); + selectionProvider.updateContent(() -> { + boolean selectionChanged = !selectionBefore.equals(selectionProvider.getSelection()); + if (selectionChanged) { + updateInProgress = false; + filterSelectionChanged(dimension); + } else { + updateDimensionFilterSelectionProviders(dimensionIterator, exceptDimension); + } + }); + } + } else { + updateInProgress = false; + notifyListeners(); + } + } + + @Override + public HashMap>> getSelection() { + HashMap>> filterSelection = new HashMap<>(); + for (DimensionWithContext dimensionWithContext : allFilterDimensions) { + DataRetrieverLevelDTO retrieverLevel = dimensionWithContext.getRetrieverLevel(); + FunctionDTO dimension = dimensionWithContext.getDimension(); + DimensionFilterSelectionProvider selectionProvider = dimensionFilterSelectionProviders.get(dimensionWithContext); + if (selectionProvider != null) { + HashSet dimensionFilterSelection = selectionProvider.getSelection(); + if (!dimensionFilterSelection.isEmpty()) { + HashMap> retrieverFilterSelection = filterSelection.get(retrieverLevel); + if (retrieverFilterSelection == null) { + retrieverFilterSelection = new HashMap<>(); + filterSelection.put(retrieverLevel, retrieverFilterSelection); + } + retrieverFilterSelection.put(dimension, dimensionFilterSelection); + } + } + } + return filterSelection; + } + + @Override + public void clearSelection() { + for (DimensionFilterSelectionProvider selectionProvider : dimensionFilterSelectionProviders.values()) { + selectionProvider.clearSelection(); + } + } + + @Override + public void addSelectionChangedListener(FilterSelectionChangedListener listener) { + listeners.add(listener); + } + + private void notifyListeners() { + mainPanel.setWidgetHidden(filterSelectionPresenterContainer, getSelection().isEmpty()); + for (FilterSelectionChangedListener listener : listeners) { + listener.selectionChanged(); + } + } + + @Override + public String getLocalizedShortName() { + return getClass().getSimpleName(); + } + + @Override + public Widget getEntryWidget() { + return mainPanel; + } + + @Override + public boolean isVisible() { + return mainPanel.isVisible(); + } + + @Override + public void setVisible(boolean visibility) { + mainPanel.setVisible(visibility); + } + + @Override + public String getDependentCssClassName() { + return "hierarchicalDimensionListFilterSelectionProvider"; + } + + @Override + public boolean hasSettings() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent(SerializableSettings settings) { + return null; + } + + @Override + public void updateSettings(SerializableSettings newSettings) { + // no-op + } + + @Override + public SerializableSettings getSettings() { + return null; + } + + @Override + public String getId() { + return "HierarchicalDimensionListFilterSelectionProvider"; + } + + private static class DimensionWithContext implements Comparable { + + private final FunctionDTO dimension; + private final DataRetrieverLevelDTO retrieverLevel; + private Collection matchingStrings; + + public DimensionWithContext(FunctionDTO dimension, DataRetrieverLevelDTO retrieverLevel) { + this.dimension = dimension; + this.retrieverLevel = retrieverLevel; + } + + public FunctionDTO getDimension() { + return dimension; + } + + public DataRetrieverLevelDTO getRetrieverLevel() { + return retrieverLevel; + } + + public Collection getMatchingStrings() { + if (matchingStrings == null) { + matchingStrings = new ArrayList<>(2); + matchingStrings.add(dimension.getDisplayName()); + matchingStrings.add(retrieverLevel.getRetrievedDataType().getDisplayName()); + } + return matchingStrings; + } + + @Override + public int compareTo(DimensionWithContext o) { + int retrieverLevelComparison = retrieverLevel.compareTo(o.retrieverLevel); + if (retrieverLevelComparison != 0) return retrieverLevelComparison; + + return dimension.compareTo(o.dimension); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((dimension == null) ? 0 : dimension.hashCode()); + result = prime * result + ((retrieverLevel == null) ? 0 : retrieverLevel.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DimensionWithContext other = (DimensionWithContext) obj; + if (dimension == null) { + if (other.dimension != null) + return false; + } else if (!dimension.equals(other.dimension)) + return false; + if (retrieverLevel == null) { + if (other.retrieverLevel != null) + return false; + } else if (!retrieverLevel.equals(other.retrieverLevel)) + return false; + return true; + } + + } + + private class FilterDimensionsListBuilder extends BaseCellTableBuilder { + + private final String headerStyle; + private final String subHeaderStyle; + private final String spacedSubHeaderStyle; + private final String subHeaderLabelStyle; + private final String firstColumnStyle; + private final String hoveredRowStyle; + private final String hoveredRowCellStyle; + private final String clearFocus; + + public FilterDimensionsListBuilder(AbstractCellTable cellTable, DataMiningDataGridStyle style) { + super(cellTable); + headerStyle = style.dataGridHeader(); + subHeaderStyle = style.dataGridSubHeader(); + firstColumnStyle = style.dataGridFirstColumn(); + spacedSubHeaderStyle = style.dataGridSpacedSubHeader(); + hoveredRowStyle = style.dataGridHoveredRow(); + hoveredRowCellStyle = style.dataGridHoveredRowCell(); + subHeaderLabelStyle = style.dataGridSubHeaderLabel(); + clearFocus = style.dataGridClearFocus(); + + cellTable.addRowHoverHandler(new RowHoverEvent.Handler() { + @Override + public void onRowHover(RowHoverEvent event) { + TableRowElement tr = event.getHoveringRow(); + if (tr.hasAttribute(DimensionListSubheaderAttribute)) { + tr.removeClassName(hoveredRowStyle); + NodeList cells = tr.getCells(); + for (int i = 0; i < cells.getLength(); i++) { + cells.getItem(i).removeClassName(hoveredRowCellStyle); + } + } + } + }); + } + + @Override + public void buildRowImpl(DimensionWithContext rowValue, int absRowIndex) { + DataRetrieverLevelDTO valueLevel = rowValue.getRetrieverLevel(); + DataRetrieverLevelDTO previousLevel = null; + if (absRowIndex > 0) { + previousLevel = filteredFilterDimensions.getList().get(absRowIndex - 1).getRetrieverLevel(); + } + + if (!Objects.equals(previousLevel, valueLevel)) { + StringBuilder styleBuilder = new StringBuilder(); + styleBuilder.append(subHeaderStyle).append(" ").append(headerStyle) + .append(" ").append(clearFocus); + if (absRowIndex != 0) { + styleBuilder.append(" ").append(spacedSubHeaderStyle); + } + String style = styleBuilder.toString(); + + String subHeaderText = valueLevel.getRetrievedDataType().getDisplayName(); + TableRowBuilder subHeaderBuilder = startRow().className(clearFocus).attribute(DimensionListSubheaderAttribute, subHeaderText); + // Additional cell to fix the checkbox column size + subHeaderBuilder.startTD().className(style + " " + firstColumnStyle).endTD(); + // Actual header cell + TableCellBuilder headerCellBuilder = subHeaderBuilder.startTD(); + headerCellBuilder.colSpan(this.cellTable.getColumnCount() - 1).className(style); + headerCellBuilder.startDiv().className(subHeaderLabelStyle).text(subHeaderText).endDiv(); + headerCellBuilder.endTD(); + subHeaderBuilder.endTR(); + } + + super.buildRowImpl(rowValue, absRowIndex); + } + } + + private class CustomCheckboxEventTranslator implements EventTranslator { + + @Override + public boolean clearCurrentSelection(CellPreviewEvent event) { + return false; + } + + @Override + public SelectAction translateSelectionEvent(CellPreviewEvent event) { + SelectAction action = SelectAction.TOGGLE; + + NativeEvent nativeEvent = event.getNativeEvent(); + Element targetRow = Element.as(nativeEvent.getEventTarget()); + while (!TableRowElement.is(targetRow) && targetRow != null) { + targetRow = targetRow.getParentElement(); + } + if (targetRow != null && targetRow.hasAttribute(DimensionListSubheaderAttribute)) { + action = SelectAction.IGNORE; + } + return action; + } + + } + +} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ListRetrieverChainFilterSelectionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ListRetrieverChainFilterSelectionProvider.java deleted file mode 100644 index 61047d5210f..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/ListRetrieverChainFilterSelectionProvider.java +++ /dev/null @@ -1,383 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection.filter; - -import java.io.Serializable; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; - -import com.google.gwt.cell.client.AbstractCell; -import com.google.gwt.cell.client.Cell; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.safehtml.shared.SafeHtmlBuilder; -import com.google.gwt.user.cellview.client.CellList; -import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.DockLayoutPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.ScrollPanel; -import com.google.gwt.user.client.ui.Widget; -import com.google.gwt.view.client.ListDataProvider; -import com.google.gwt.view.client.SelectionChangeEvent; -import com.google.gwt.view.client.SingleSelectionModel; -import com.sap.sse.common.settings.SerializableSettings; -import com.sap.sse.datamining.shared.DataMiningSession; -import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; -import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; -import com.sap.sse.datamining.shared.impl.dto.ReducedDimensionsDTO; -import com.sap.sse.datamining.ui.client.AbstractDataMiningComponent; -import com.sap.sse.datamining.ui.client.DataMiningServiceAsync; -import com.sap.sse.datamining.ui.client.DataRetrieverChainDefinitionProvider; -import com.sap.sse.datamining.ui.client.FilterSelectionChangedListener; -import com.sap.sse.datamining.ui.client.FilterSelectionPresenter; -import com.sap.sse.datamining.ui.client.FilterSelectionProvider; -import com.sap.sse.datamining.ui.client.presentation.PlainFilterSelectionPresenter; -import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.shared.components.Component; -import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; -import com.sap.sse.gwt.client.shared.settings.ComponentContext; - -public class ListRetrieverChainFilterSelectionProvider extends AbstractDataMiningComponent - implements FilterSelectionProvider { - - private final DataMiningSession session; - private final DataMiningServiceAsync dataMiningService; - private final ErrorReporter errorReporter; - private final DataRetrieverChainDefinitionProvider retrieverChainProvider; - private final Set listeners; - - private boolean isAwaitingReload; - private boolean blockDataUpdates; - private DataRetrieverChainDefinitionDTO retrieverChain; - private final Map selectionProvidersMappedByRetrievedDataType; - - private final DockLayoutPanel mainPanel; - private final CellList retrieverLevelList; - private final SingleSelectionModel retrieverLevelSelectionModel; - private final ListDataProvider retrieverLevelDataProvider; - private final ScrollPanel selectionPanel; - private final FilterSelectionPresenter selectionPresenter; - private final ScrollPanel selectionPresenterScrollPanel; - - /** - * When the {@link #retrieverLevelList retriever levels} have been received, this field is initialized with the - * mapping of dimension functions to the reduced set shown in the per-level filters. This way, an original dimension - * can be {@link ReducedDimensionsDTO#getReducedDimension(FunctionDTO) mapped} to its corresponding dimension from - * the reduced set of dimensions that are shown in the per-level filters. - */ - private ReducedDimensionsDTO reducedDimensions; - - public ListRetrieverChainFilterSelectionProvider(Component parent, ComponentContext context, - DataMiningSession session, DataMiningServiceAsync dataMiningService, ErrorReporter errorReporter, - DataRetrieverChainDefinitionProvider retrieverChainProvider) { - super(parent, context); - this.session = session; - this.dataMiningService = dataMiningService; - this.errorReporter = errorReporter; - this.retrieverChainProvider = retrieverChainProvider; - listeners = new HashSet<>(); - retrieverChainProvider.addDataRetrieverChainDefinitionChangedListener(this); - - isAwaitingReload = false; - blockDataUpdates = false; - retrieverChain = null; - selectionProvidersMappedByRetrievedDataType = new HashMap<>(); - - retrieverLevelList = new CellList<>(new RetrieverLevelCell()); - retrieverLevelSelectionModel = new RetrieverLevelSelectionModel(); - retrieverLevelList.setSelectionModel(retrieverLevelSelectionModel); - retrieverLevelDataProvider = new ListDataProvider<>(); - retrieverLevelDataProvider.addDataDisplay(retrieverLevelList); - ScrollPanel retrieverLevelListScrollPanel = new ScrollPanel(retrieverLevelList); - - DockLayoutPanel selectionDockLayoutPanel = new DockLayoutPanel(Unit.PX); - selectionDockLayoutPanel.addNorth(new Label(getDataMiningStringMessages().filterBy()), 18); - selectionPanel = new ScrollPanel(); - selectionDockLayoutPanel.add(selectionPanel); - - selectionPresenter = new PlainFilterSelectionPresenter(this, context, getDataMiningStringMessages(), retrieverChainProvider, - this); - selectionPresenterScrollPanel = new ScrollPanel(selectionPresenter.getEntryWidget()); - - mainPanel = new DockLayoutPanel(Unit.PX); - mainPanel.addSouth(selectionPresenterScrollPanel, 100); - mainPanel.addWest(retrieverLevelListScrollPanel, 300); - mainPanel.add(selectionDockLayoutPanel); - mainPanel.setWidgetHidden(selectionPresenterScrollPanel, true); - } - - @Override - public void setHighestRetrieverLevelWithFilterDimension(FunctionDTO dimension, Serializable groupKey) { - FunctionDTO dimensionMappedToReducedDimensions = reducedDimensions == null ? dimension - : reducedDimensions.getReducedDimension(dimension); - for (final DataRetrieverLevelDTO retrieverLevel : retrieverChain.getRetrieverLevels()) { - final RetrieverLevelFilterSelectionProvider selectionProvider = selectionProvidersMappedByRetrievedDataType - .get(retrieverLevel); - if (selectionProvider.hasDimension(dimensionMappedToReducedDimensions)) { - selectionProvider.addFilter(dimensionMappedToReducedDimensions, Collections.singleton(groupKey)); - break; - } - } - } - - @Override - public void awaitReloadComponents() { - isAwaitingReload = true; - } - - @Override - public boolean isAwatingReload() { - return isAwaitingReload; - } - - @Override - public void reloadComponents() { - updateRetrievalLevels(); - isAwaitingReload = false; - notifyListeners(); - } - - @Override - public void dataRetrieverChainDefinitionChanged(DataRetrieverChainDefinitionDTO newDataRetrieverChainDefinition) { - if (!Objects.equals(retrieverChain, newDataRetrieverChainDefinition)) { - retrieverChain = newDataRetrieverChainDefinition; - if (!isAwaitingReload && retrieverChain != null) { - updateRetrievalLevels(); - } else if (!isAwaitingReload) { - clearContent(); - } - } - } - - private void updateRetrievalLevels() { - clearContent(); - retrieverLevelDataProvider.getList().addAll(retrieverChain.getRetrieverLevels()); - retrieverLevelList.setPageSize(retrieverLevelDataProvider.getList().size()); - dataMiningService.getReducedDimensionsMappedByLevelFor(retrieverChain, - LocaleInfo.getCurrentLocale().getLocaleName(), new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter - .reportError("Error fetching the dimensions of the retrieval chain from the server: " - + caught.getMessage()); - } - - @Override - public void onSuccess(ReducedDimensionsDTO dimensionsMappedByLevel) { - ListRetrieverChainFilterSelectionProvider.this.reducedDimensions = dimensionsMappedByLevel; - int firstFilterableRetrieverLevel = Integer.MAX_VALUE; - for (Entry> dimensionsEntry : dimensionsMappedByLevel - .getReducedDimensions().entrySet()) { - if (!dimensionsEntry.getValue().isEmpty()) { - DataRetrieverLevelDTO retrieverLevel = dimensionsEntry.getKey(); - RetrieverLevelFilterSelectionProvider selectionProvider = new RetrieverLevelFilterSelectionProvider( - ListRetrieverChainFilterSelectionProvider.this, getComponentContext(), session, - dataMiningService, errorReporter, - ListRetrieverChainFilterSelectionProvider.this, retrieverChain, retrieverLevel); - selectionProvider.setAvailableDimensions(dimensionsEntry.getValue()); - selectionProvidersMappedByRetrievedDataType.put(retrieverLevel, selectionProvider); - firstFilterableRetrieverLevel = retrieverLevel - .getLevel() < firstFilterableRetrieverLevel ? retrieverLevel.getLevel() - : firstFilterableRetrieverLevel; - } - } - retrieverLevelSelectionModel - .setSelected(retrieverChain.getRetrieverLevel(firstFilterableRetrieverLevel), true); - } - }); - } - - private void clearContent() { - blockDataUpdates = true; - retrieverLevelDataProvider.getList().clear(); - retrieverLevelSelectionModel.clear(); - selectionPanel.clear(); - clearSelection(); - selectionProvidersMappedByRetrievedDataType.clear(); - blockDataUpdates = false; - forwardSelectionChanged(); - } - - HashMap getRetrieverSettings() { - return retrieverChainProvider.getRetrieverSettings(); - } - - void retrieverLevelFilterSelectionChanged( - RetrieverLevelFilterSelectionProvider retrieverLevelFilterSelectionProvider, - DimensionFilterSelectionProvider dimensionFilterSelectionProvider) { - if (!blockDataUpdates) { - updateFilterSelectionProviders(retrieverLevelFilterSelectionProvider.getRetrieverLevel(), - dimensionFilterSelectionProvider.getSelectedDimension()); - } - } - - void updateFilterSelectionProviders(DataRetrieverLevelDTO beginningWithLevel, - final FunctionDTO exceptForDimension) { - DataRetrieverLevelDTO currentLevel = beginningWithLevel; - DataRetrieverLevelDTO retrieverLevelToUpdate = null; - while (currentLevel != null) { - if (selectionProvidersMappedByRetrievedDataType.containsKey(currentLevel)) { - retrieverLevelToUpdate = currentLevel; - break; - } - currentLevel = retrieverChain.getNextRetrieverLevel(currentLevel); - } - - if (retrieverLevelToUpdate != null) { - selectionProvidersMappedByRetrievedDataType.get(retrieverLevelToUpdate) - .updateAvailableData(exceptForDimension); - } else { - // The update of the whole retriever chain is completed. Notify the listeners. - forwardSelectionChanged(); - } - } - - private void forwardSelectionChanged() { - mainPanel.setWidgetHidden(selectionPresenterScrollPanel, getSelection().isEmpty()); - notifyListeners(); - } - - @Override - public HashMap>> getSelection() { - HashMap>> filterSelection = new HashMap<>(); - for (RetrieverLevelFilterSelectionProvider selectionProvider : selectionProvidersMappedByRetrievedDataType - .values()) { - Map> levelFilterSelection = selectionProvider - .getFilterSelection(); - if (!levelFilterSelection.isEmpty()) { - filterSelection.put(selectionProvider.getRetrieverLevel(), new HashMap<>(levelFilterSelection)); - } - } - return filterSelection; - } - - @Override - public void applyQueryDefinition(StatisticQueryDefinitionDTO queryDefinition) { - for (RetrieverLevelFilterSelectionProvider selectionProvider : selectionProvidersMappedByRetrievedDataType - .values()) { - Map>> filterSelection = queryDefinition - .getFilterSelection(); - DataRetrieverLevelDTO retrieverLevel = selectionProvider.getRetrieverLevel(); - if (filterSelection.containsKey(retrieverLevel)) { - selectionProvider.applySelection(filterSelection.get(retrieverLevel)); - } - } - } - - @Override - public void clearSelection() { - for (RetrieverLevelFilterSelectionProvider selectionProvider : selectionProvidersMappedByRetrievedDataType - .values()) { - selectionProvider.clearSelection(); - } - } - - @Override - public void addSelectionChangedListener(FilterSelectionChangedListener listener) { - listeners.add(listener); - } - - private void notifyListeners() { - for (FilterSelectionChangedListener listener : listeners) { - listener.selectionChanged(); - } - } - - private class RetrieverLevelCell extends AbstractCell { - @Override - public void render(Cell.Context context, DataRetrieverLevelDTO value, SafeHtmlBuilder sb) { - if (value == null) { - return; - } - - sb.appendHtmlConstant(""); - sb.appendHtmlConstant(""); - - List dataList = retrieverLevelDataProvider.getList(); - if (!dataList.get(dataList.size() - 1).equals(value)) { - sb.appendHtmlConstant(""); - } - - sb.appendHtmlConstant("
      " + value.getRetrievedDataType().getDisplayName() + "
      |
      "); - } - } - - private class RetrieverLevelSelectionModel extends SingleSelectionModel { - public RetrieverLevelSelectionModel() { - super(); - addSelectionChangeHandler(new SelectionChangeEvent.Handler() { - @Override - public void onSelectionChange(SelectionChangeEvent event) { - selectionPanel.setWidget(selectionProvidersMappedByRetrievedDataType - .get(retrieverLevelSelectionModel.getSelectedObject()).getEntryWidget()); - } - }); - } - - @Override - public void setSelected(DataRetrieverLevelDTO item, boolean selected) { - if (!selected || !selectionProvidersMappedByRetrievedDataType.containsKey(item)) { - // Prevents the selection of retriever levels, that don't have any dimensions and can't filter - return; - } - - super.setSelected(item, selected); - } - } - - @Override - public String getLocalizedShortName() { - return getClass().getSimpleName(); - } - - @Override - public Widget getEntryWidget() { - return mainPanel; - } - - @Override - public boolean isVisible() { - return mainPanel.isVisible(); - } - - @Override - public void setVisible(boolean visibility) { - mainPanel.setVisible(visibility); - } - - @Override - public String getDependentCssClassName() { - return "listRetrieverChainSelectionProvider"; - } - - @Override - public boolean hasSettings() { - return false; - } - - @Override - public SettingsDialogComponent getSettingsDialogComponent(SerializableSettings settings) { - return null; - } - - @Override - public void updateSettings(SerializableSettings newSettings) { - // no-op - } - - @Override - public SerializableSettings getSettings() { - return null; - } - - @Override - public String getId() { - return "ListRetrieverChainFilterSelectionProvider"; - } -} diff --git a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/RetrieverLevelFilterSelectionProvider.java b/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/RetrieverLevelFilterSelectionProvider.java deleted file mode 100644 index c348b9bcc57..00000000000 --- a/java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client/selection/filter/RetrieverLevelFilterSelectionProvider.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.sap.sse.datamining.ui.client.selection.filter; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -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 com.google.gwt.dom.client.Style.Display; -import com.google.gwt.user.client.ui.HorizontalPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sse.common.Util; -import com.sap.sse.common.settings.AbstractSettings; -import com.sap.sse.common.settings.SerializableSettings; -import com.sap.sse.datamining.shared.DataMiningSession; -import com.sap.sse.datamining.shared.impl.dto.DataRetrieverChainDefinitionDTO; -import com.sap.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; -import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; -import com.sap.sse.datamining.ui.client.AbstractDataMiningComponent; -import com.sap.sse.datamining.ui.client.DataMiningServiceAsync; -import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.shared.components.Component; -import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; -import com.sap.sse.gwt.client.shared.settings.ComponentContext; - -public class RetrieverLevelFilterSelectionProvider extends AbstractDataMiningComponent { - - private final DataMiningServiceAsync dataMiningService; - private final ErrorReporter errorReporter; - - private final DataMiningSession session; - private final ListRetrieverChainFilterSelectionProvider retrieverChainSelectionProvider; - private final DataRetrieverChainDefinitionDTO retrieverChain; - private final DataRetrieverLevelDTO retrieverLevel; - private final Collection availableDimensions; - - private final HorizontalPanel mainPanel; - private final Collection dimensionSelectionProviders; - - public RetrieverLevelFilterSelectionProvider(Component parent, ComponentContext context, - DataMiningSession session, DataMiningServiceAsync dataMiningService, ErrorReporter errorReporter, - ListRetrieverChainFilterSelectionProvider retrieverChainSelectionProvider, - DataRetrieverChainDefinitionDTO retrieverChain, DataRetrieverLevelDTO retrieverLevel) { - super(parent, context); - this.dataMiningService = dataMiningService; - this.errorReporter = errorReporter; - - this.session = session; - this.retrieverChainSelectionProvider = retrieverChainSelectionProvider; - this.retrieverChain = retrieverChain; - this.retrieverLevel = retrieverLevel; - availableDimensions = new ArrayList<>(); - - mainPanel = new HorizontalPanel(); - dimensionSelectionProviders = new ArrayList<>(); - } - - public void setAvailableDimensions(Collection dimensions) { - availableDimensions.clear(); - availableDimensions.addAll(dimensions); - initializeDimensionSelectionProviders(); - } - - private void initializeDimensionSelectionProviders() { - DimensionFilterSelectionProvider dimensionFilter = createDimensionSelectionProvider(); - List availableDimensionsList = new ArrayList<>(availableDimensions); - Collections.sort(availableDimensionsList); - dimensionFilter.setAvailableDimensions(availableDimensionsList); - addDimensionSelectionProvider(dimensionFilter); - } - - boolean canAddDimensionSelectionProvider() { - return availableDimensions.size() != getSelectedDimensions().size(); - } - - void createAndAddDimensionSelectionProvider() { - addDimensionSelectionProvider(createDimensionSelectionProvider()); - } - - private DimensionFilterSelectionProvider createDimensionSelectionProvider() { - DimensionFilterSelectionProvider dimensionFilter = new DimensionFilterSelectionProvider(dataMiningService, - errorReporter, session, this); - return dimensionFilter; - } - - private void addDimensionSelectionProvider(DimensionFilterSelectionProvider dimensionFilter) { - dimensionSelectionProviders.add(dimensionFilter); - dimensionFilter.getEntryWidget().getElement().getStyle().setDisplay(Display.INLINE); - mainPanel.add(dimensionFilter.getEntryWidget()); - } - - boolean shouldRemoveDimensionSelectionProvider() { - return availableDimensions.size() - getSelectedDimensions().size() > 1; - } - - void removeDimensionFilter(DimensionFilterSelectionProvider dimensionFilter) { - dimensionFilter.clearSelection(); // Notifies the listeners, if values were selected - dimensionSelectionProviders.remove(dimensionFilter); - mainPanel.remove(dimensionFilter.getEntryWidget()); - } - - void updateAvailableDimensions() { - Collection remainingDimensionsBase = new ArrayList(availableDimensions); - remainingDimensionsBase.removeAll(getSelectedDimensions()); - for (DimensionFilterSelectionProvider dimensionFilter : dimensionSelectionProviders) { - List remainingDimensions = new ArrayList<>(remainingDimensionsBase); - FunctionDTO selectedDimension = dimensionFilter.getSelectedDimension(); - if (selectedDimension != null) { - remainingDimensions.add(selectedDimension); - } - Collections.sort(remainingDimensions); - remainingDimensions.add(null); - dimensionFilter.setAvailableDimensions(remainingDimensions); - } - } - - public boolean hasDimension(FunctionDTO dimension) { - return availableDimensions.contains(dimension); - } - - private Collection getSelectedDimensions() { - Collection selectedDimensions = new ArrayList<>(); - for (DimensionFilterSelectionProvider dimensionFilter : dimensionSelectionProviders) { - FunctionDTO selectedDimension = dimensionFilter.getSelectedDimension(); - if (selectedDimension != null) { - selectedDimensions.add(selectedDimension); - } - } - return selectedDimensions; - } - - /** - * Updates the dimension values except for the given dimension.
      - * Aborts the update, if it caused a selection change. - */ - void updateAvailableData(FunctionDTO exceptForDimension) { - updateAvailableData(exceptForDimension, dimensionSelectionProviders.iterator()); - } - - void updateAvailableData(final FunctionDTO exceptForDimension, - final Iterator selectionProviderIterator) { - if (selectionProviderIterator.hasNext()) { - DimensionFilterSelectionProvider selectionProvider = selectionProviderIterator.next(); - FunctionDTO selectedDimension = selectionProvider.getSelectedDimension(); - if (selectedDimension != null && !selectedDimension.equals(exceptForDimension)) { - selectionProvider.fetchAndUpdateAvailableData(selectionProviderIterator); - } else { - updateAvailableData(exceptForDimension, selectionProviderIterator); - } - } else { - // Update for this level is completed. Continue with the next level. - retrieverChainSelectionProvider.updateFilterSelectionProviders( - retrieverChain.getNextRetrieverLevel(getRetrieverLevel()), exceptForDimension); - } - } - - void dimensionFilterSelectionChanged(DimensionFilterSelectionProvider dimensionFilterSelectionProvider) { - retrieverChainSelectionProvider.retrieverLevelFilterSelectionChanged(this, dimensionFilterSelectionProvider); - } - - HashMap getRetrieverSettings() { - return retrieverChainSelectionProvider.getRetrieverSettings(); - } - - HashMap>> getCompleteFilterSelection() { - return retrieverChainSelectionProvider.getSelection(); - } - - public Map> getFilterSelection() { - HashMap> filterSelection = new HashMap<>(); - for (DimensionFilterSelectionProvider dimensionFilter : dimensionSelectionProviders) { - HashSet dimensionFilterSelection = dimensionFilter.getSelection(); - if (!dimensionFilterSelection.isEmpty()) { - filterSelection.put(dimensionFilter.getSelectedDimension(), dimensionFilterSelection); - } - } - return filterSelection; - } - - /** - * If there is already a filter set for {@code dimension}, replace its value selection by the {@code values} - * provided. Otherwise, set the last (expectedly non-selected) {@link DimensionFilterSelectionProvider} to - * {@code dimension} and set its filter values to {@code values}. - *

      - * - * Precondition: {@link #hasDimension(FunctionDTO) hasDimension(dimension)}{@code == true} - */ - public void addFilter(FunctionDTO dimension, Set values) { - DimensionFilterSelectionProvider dimensionFilter = null; - for (final DimensionFilterSelectionProvider dimensionSelectionProvider : dimensionSelectionProviders) { - dimensionFilter = dimensionSelectionProvider; - if (Util.equalsWithNull(dimensionSelectionProvider.getSelectedDimension(), dimension)) { - break; - } - } - // now dimensionFilter will either be the "perfect match" with the correct dimension, - // or it's the last DimensionFilterSelectionProvider found, expected to have an empty selection and - // will be used to set the dimension and filter; or it's null, meaning there was no dimension filter - // which is considered an error - if (dimensionFilter == null) { - throw new IllegalStateException( - "Internal error: must have at least one de-selected dimension filter per retriever level"); - } - if (dimensionFilter.getSelectedDimension() == null) { - // dimension not yet filtered for; use the last empty dimension filter box, set dimension and define filter - // values: - dimensionFilter.setSelectedDimensionAndValues(dimension, values); - final DimensionFilterSelectionProvider newDimensionFilter = createDimensionSelectionProvider(); - addDimensionSelectionProvider(newDimensionFilter); - updateAvailableDimensions(); - } else { - // was filtered for that dimension already; replace filter, restricting to values requested: - dimensionFilter.setSelectedDimensionAndValues(dimension, values); - } - } - - public void applySelection(HashMap> filterSelection) { - dimensionSelectionProviders.clear(); - mainPanel.clear(); - List sortedDimensions = new ArrayList<>(filterSelection.keySet()); - Collections.sort(sortedDimensions); - for (FunctionDTO functionDTO : sortedDimensions) { - DimensionFilterSelectionProvider dimensionFilter = createDimensionSelectionProvider(); - addDimensionSelectionProvider(dimensionFilter); - updateAvailableDimensions(); - dimensionFilter.setSelectedDimensionAndValues(functionDTO, filterSelection.get(functionDTO)); - } - } - - public void clearSelection() { - mainPanel.clear(); - for (DimensionFilterSelectionProvider dimensionFilter : dimensionSelectionProviders) { - dimensionFilter.clearSelection(); - } - dimensionSelectionProviders.clear(); - initializeDimensionSelectionProviders(); - } - - DataRetrieverChainDefinitionDTO getDataRetrieverChain() { - return retrieverChain; - } - - public DataRetrieverLevelDTO getRetrieverLevel() { - return retrieverLevel; - } - - @Override - public boolean hasSettings() { - return false; - } - - @Override - public SettingsDialogComponent getSettingsDialogComponent(AbstractSettings settings) { - return null; - } - - @Override - public void updateSettings(AbstractSettings newSettings) { - // no-op - } - - @Override - public String getLocalizedShortName() { - return getClass().getSimpleName(); - } - - @Override - public Widget getEntryWidget() { - return mainPanel; - } - - @Override - public boolean isVisible() { - return mainPanel.isVisible(); - } - - @Override - public void setVisible(boolean visibility) { - mainPanel.setVisible(visibility); - } - - @Override - public String getDependentCssClassName() { - return "singleRetrieverLevelSelectionPanel"; - } - - @Override - public AbstractSettings getSettings() { - return null; - } - - @Override - public String getId() { - return "RetrieverLevelFilterSelectionProvider"; - } -} diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java index 8a7aa40855f..7ec39013c85 100644 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedNumberPairCollectingProcessor.java @@ -54,14 +54,12 @@ public class ParallelGroupedNumberPairCollectingProcessor final Double doubleValueA = a == null ? null : a.doubleValue(); final Double doubleValueB = b == null ? null : b.doubleValue(); - if (doubleValueA != null && doubleValueB != null) { - if(aggregate == null) { + if (aggregate == null) { aggregate = new HashSet<>(); aggregate.add(new Pair<>(doubleValueA, doubleValueB)); individualPairs.put(element.getKey(), aggregate); - } - else { + } else { individualPairs.get(element.getKey()).add(new Pair<>(doubleValueA, doubleValueB)); } } diff --git a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java index 90547df3320..8b3a6155b0d 100755 --- a/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java +++ b/java/com.sap.sse.gwt.adminconsole/src/com/sap/sse/gwt/adminconsole/AdminConsolePanel.java @@ -227,7 +227,7 @@ public class AdminConsolePanel extends HeaderPanel implements HandleTabSelectabl final Anchor releaseNotesLink = new Anchor(new SafeHtmlBuilder().appendEscaped(releaseNotesAnchorLabel).toSafeHtml(), releaseNotesURL); sysinfoPanel.add(releaseNotesLink); informationPanel.add(sysinfoPanel, DockPanel.EAST); - informationPanel.setCellHorizontalAlignment(sysinfoPanel, HasHorizontalAlignment.ALIGN_RIGHT); + informationPanel.setCellHorizontalAlignment(sysinfoPanel, HasHorizontalAlignment.ALIGN_RIGHT); this.setFooterWidget(informationPanel); topLevelTabPanel.setSize("100%", "100%"); this.setContentWidget(topLevelTabPanel); diff --git a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF index 6e8d7576d06..38cf03ddfe8 100644 --- a/java/com.sap.sse.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.gwt/META-INF/MANIFEST.MF @@ -57,6 +57,7 @@ Export-Package: com.google.gwt.user.client.rpc.core.java.util, com.sap.sse.gwt.dispatch.client.system, com.sap.sse.gwt.dispatch.client.system.batching, com.sap.sse.gwt.dispatch.client.system.caching, + com.sap.sse.gwt.dispatch.client.system.routing, com.sap.sse.gwt.dispatch.client.transport.gwtrpc, com.sap.sse.gwt.dispatch.servlets, com.sap.sse.gwt.dispatch.shared.caching, diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DefaultErrorReporter.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DefaultErrorReporter.java index dab48bda285..fbc08f813ff 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DefaultErrorReporter.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/DefaultErrorReporter.java @@ -3,13 +3,13 @@ package com.sap.sse.gwt.client; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.safehtml.shared.SafeHtmlUtils; -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.HTML; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sse.gwt.client.Notification.NotificationType; public class DefaultErrorReporter implements ErrorReporter { private DialogBox errorDialogBox; @@ -49,7 +49,7 @@ public class DefaultErrorReporter implements ErrorRepo @Override public void reportError(String message, boolean silentMode) { if (silentMode) { - Window.setStatus(message); + Notification.notify(message, NotificationType.WARNING); } else { reportError(message); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java new file mode 100644 index 00000000000..ac73cd69d40 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/Notification.java @@ -0,0 +1,137 @@ +package com.sap.sse.gwt.client; + +import java.util.LinkedList; + +import com.google.gwt.animation.client.Animation; +import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.Style.Cursor; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.dom.client.Style.Visibility; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.RootPanel; +import com.sap.sse.common.Util.Pair; + +/** + * Utility class to show non obstructive warning / info messages using a small notification at the bottom of the page. Clicking the notification will hide it. It will autohide after some time. + */ +public class Notification { + private static final int NOTIFICATION_TIME = 20000; + private static final double FADE_OUT_PERCENT = 0.98; + private static final double FADE_IN_PERCENT = 0.005; + + interface NotificationResources extends ClientBundle { + @Source("notification.gss") + NotificationCSS css(); + } + + interface NotificationCSS extends CssResource { + String snackbar(); + } + + private final static FlowPanel snackBar = new FlowPanel(); + private final static NotificationResources ress = GWT.create(NotificationResources.class); + private static Animation notificationAnimation; + + static { + ress.css().ensureInjected(); + snackBar.addStyleName(ress.css().snackbar()); + snackBar.getElement().getStyle().setCursor(Cursor.POINTER); + snackBar.getElement().getStyle().setZIndex(99); + + notificationAnimation = new Animation() { + @Override + protected void onStart() { + super.onStart(); + snackBar.getElement().getStyle().setOpacity(0); + snackBar.getElement().getStyle().setVisibility(Visibility.VISIBLE); + } + + @Override + protected void onUpdate(double progress) { + if (progress < FADE_IN_PERCENT) { + double relPr = progress / FADE_IN_PERCENT; + double bottom = 60 * relPr - 30; + snackBar.getElement().getStyle().setOpacity(relPr); + snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); + } else if (progress > FADE_OUT_PERCENT) { + double relPr = (progress - FADE_OUT_PERCENT) / (1 - FADE_OUT_PERCENT); + snackBar.getElement().getStyle().setOpacity(1 - relPr); + double bottom = 60 * (1 - relPr) - 30; + snackBar.getElement().getStyle().setBottom(bottom, Unit.PX); + } else { + snackBar.getElement().getStyle().setOpacity(1); + snackBar.getElement().getStyle().setBottom(30, Unit.PX); + } + } + + @Override + protected void onComplete() { + snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); + checkQueue(true); + } + + @Override + protected void onCancel() { + snackBar.getElement().getStyle().setVisibility(Visibility.HIDDEN); + checkQueue(true); + } + }; + } + + private final static LinkedList> QUEUE = new LinkedList<>(); + + private Notification() { + } + + public enum NotificationType { + ERROR("\u2716", "RED", "WHITE"), + WARNING("\u26A0", "ORANGE", "WHITE"), + INFO("\u2139", "#008fcc", "WHITE"); + + private String decorator; + private String bgColor; + private String color; + + NotificationType(String decorator, String bgColor, String color) { + this.decorator = decorator; + this.bgColor = bgColor; + this.color = color; + } + } + + public static void notify(String message, NotificationType type) { + QUEUE.add(new Pair<>(message, type)); + checkQueue(false); + } + + /** + * Displays next message if non is displayed currently, else does nothing and will by called by currently displaying + * message TTL timer + */ + private static void checkQueue(boolean onAnimationFinish) { + if (!QUEUE.isEmpty()) { + if (!notificationAnimation.isRunning() || onAnimationFinish) { + RootPanel.get().add(snackBar); + Event.sinkEvents(snackBar.getElement(), Event.ONCLICK); + Event.setEventListener(snackBar.getElement(), new EventListener() { + @Override + public void onBrowserEvent(Event event) { + GWT.log("test"); + notificationAnimation.cancel(); + } + }); + + Pair notification = QUEUE.pop(); + snackBar.getElement().getStyle().setColor(notification.getB().color); + snackBar.getElement().getStyle().setBackgroundColor(notification.getB().bgColor); + snackBar.getElement().setInnerText(notification.getB().decorator + " " + notification.getA() + " " + + notification.getB().decorator); + notificationAnimation.run(NOTIFICATION_TIME); + } + } + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/ServiceRoutingProvider.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/ServiceRoutingProvider.java new file mode 100644 index 00000000000..7c23758b38b --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/ServiceRoutingProvider.java @@ -0,0 +1,12 @@ +package com.sap.sse.gwt.client; + +import com.sap.sse.gwt.dispatch.client.system.DispatchSystemDefaultImpl; + +/** + * Interface used to append a routing suffix to target url + * in GWT RPC service instances and in {@link DispatchSystemDefaultImpl} + */ +public interface ServiceRoutingProvider { + + String routingSuffixPath(); +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.java index cdfa9753003..af926f8145d 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.java @@ -77,6 +77,7 @@ public interface StringMessages extends Messages { String groupMedianDescending(); String choosePresentation(); String shownDecimals(); + String showDataLabels(); String elements(long count); String resultsChart(); String cantDisplayDataOfType(String resultType); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.properties b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.properties index 01326f6de65..04accf1bde3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.properties +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages.properties @@ -56,6 +56,7 @@ groupMedianAscending=Group Median (Ascending) groupMedianDescending=Group Median (Descending) choosePresentation=Choose Presentation shownDecimals=Shown Decimals +showDataLabels=Show labels elements={0} elements resultsChart=Results Chart cantDisplayDataOfType=Can''t display data of type {0} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages_de.properties b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages_de.properties index 4e63677bc34..5e9d8996ba8 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages_de.properties +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/StringMessages_de.properties @@ -55,6 +55,7 @@ groupMedianAscending=Gruppe Median (Aufsteigend) groupMedianDescending=Gruppe Median (Absteigend) choosePresentation=Wähle Präsentation shownDecimals=Angezeigte Nachkommastellen +showDataLabels=Zahlen anzeigen elements={0} Elemente resultsChart=Ergebnis-Diagramm cantDisplayDataOfType=Daten des Typs {0} können nicht dargestellt werden diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java index 8d1cd6e555f..889619b87c1 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java @@ -3,6 +3,7 @@ package com.sap.sse.gwt.client.controls.listedit; import java.util.ArrayList; 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.event.dom.client.KeyUpEvent; @@ -77,11 +78,9 @@ public abstract class GenericStringListEditorComposite extends ListEd } public static class ExpandedUi extends ExpandedListEditorUi { - protected final MultiWordSuggestOracle inputOracle; protected final String placeholderTextForAddTextbox; - - + protected final Integer inputBoxSize; public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable suggestValues) { this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null); } @@ -90,8 +89,17 @@ public abstract class GenericStringListEditorComposite extends ListEd * @param suggestValues must not be null but may be empty * @param placeholderTextForAddTextbox may be null */ - public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable suggestValues, String placeholderTextForAddTextbox) { - super(stringMessages, removeImage, /*canRemoveItems*/true); + public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable suggestValues, + String placeholderTextForAddTextbox) { + this(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox, /* inputBoxSize */ null); + + } + /** + * @param inputBoxSize The size of the input box in EM Unit. + */ + public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable suggestValues, + String placeholderTextForAddTextbox, Integer inputBoxSize) { + super(stringMessages, removeImage, /* canRemoveItems */true); this.placeholderTextForAddTextbox = placeholderTextForAddTextbox; this.inputOracle = new MultiWordSuggestOracle(); for (String suggestValue : suggestValues) { @@ -100,8 +108,9 @@ public abstract class GenericStringListEditorComposite extends ListEd List defaultSuggestions = new ArrayList<>(); Util.addAll(suggestValues, defaultSuggestions); this.inputOracle.setDefaultSuggestionsFromText(defaultSuggestions); + this.inputBoxSize = inputBoxSize; } - + protected GenericStringListEditorComposite getContext() { return (GenericStringListEditorComposite) context; } @@ -126,6 +135,9 @@ public abstract class GenericStringListEditorComposite extends ListEd protected Widget createAddWidget() { final SuggestBox inputBox = createSuggestBox(); inputBox.ensureDebugId("InputSuggestBox"); + if (inputBoxSize != null) { + inputBox.setWidth(Integer.toString(inputBoxSize) + Unit.EM); + } final Button addButton = new Button(getStringMessages().add()); addButton.ensureDebugId("AddButton"); addButton.setEnabled(false); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss new file mode 100644 index 00000000000..0e1f3b5a5e0 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/notification.gss @@ -0,0 +1,10 @@ +.snackbar { + visibility: hidden; + min-width: 250px; + margin-left: -125px; + text-align: center; + border-radius: 2px; + padding: 16px; + position: fixed; + left: 50%; +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/LabeledAbstractFilterablePanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/LabeledAbstractFilterablePanel.java index 8c3b42bb32b..b5d91608be7 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/LabeledAbstractFilterablePanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/LabeledAbstractFilterablePanel.java @@ -1,5 +1,6 @@ package com.sap.sse.gwt.client.panels; +import com.google.gwt.user.cellview.client.AbstractCellTable; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.client.ui.Label; import com.google.gwt.view.client.ListDataProvider; @@ -34,8 +35,7 @@ public abstract class LabeledAbstractFilterablePanel extends AbstractFilterab * {@link TableWrapper#registerSelectionModelOnNewDataProvider} method so that the selection is preserved * across setting and removing the filter */ - public LabeledAbstractFilterablePanel(Label label, Iterable all, CellTable display, - final ListDataProvider filtered) { + public LabeledAbstractFilterablePanel(Label label, Iterable all, AbstractCellTable display, final ListDataProvider filtered) { super(all, display, filtered); insert(label, 0); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java index 67aa7170cab..23864691929 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/suggestion/AbstractListSuggestOracle.java @@ -18,10 +18,14 @@ public abstract class AbstractListSuggestOracle extends AbstractSuggestOracle @Override protected final void getSuggestions(Request request, Callback callback, Iterable queryTokens) { - Iterable filteredList = suggestionMatchingFilter.applyFilter(queryTokens, suggestionObjectList); + Iterable filteredList = suggestionMatchingFilter.applyFilter(getKeywordStrings(queryTokens), suggestionObjectList); this.setSuggestions(request, callback, filteredList, queryTokens); } + protected Iterable getKeywordStrings(Iterable queryTokens) { + return queryTokens; + } + public final AbstractListFilter getSuggestionMatchingFilter() { return suggestionMatchingFilter; } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/client/system/DispatchSystemDefaultImpl.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/client/system/DispatchSystemDefaultImpl.java index 39cdc47e11b..c72ab6f1f1d 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/client/system/DispatchSystemDefaultImpl.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/dispatch/client/system/DispatchSystemDefaultImpl.java @@ -1,11 +1,14 @@ package com.sap.sse.gwt.dispatch.client.system; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.gwt.client.ServiceRoutingProvider; import com.sap.sse.gwt.dispatch.client.system.batching.AutomaticBatchingDispatch; import com.sap.sse.gwt.dispatch.client.system.caching.CachingDispatch; import com.sap.sse.gwt.dispatch.client.transport.DefaultTransport; @@ -27,8 +30,11 @@ public abstract class DispatchSystemDefaultImpl imp ProvidesServerTime { private final Logger LOG = Logger.getLogger(DispatchSystemDefaultImpl.class.getName()); private final DefaultTransport simpleDispatch; - private final DispatchSystemAsync dispatch; - + private final DispatchSystemAsync defaultDispatch; + private final Map> routingRegistry = new HashMap<>(); + private final boolean processResultsScheduled; + private final String dispatchRPCPath; + public DispatchSystemDefaultImpl(String dispatchRPCPath) { this(dispatchRPCPath, false); } @@ -43,10 +49,25 @@ public abstract class DispatchSystemDefaultImpl imp * use a {@link Scheduler} to process the results */ public DispatchSystemDefaultImpl(String dispatchRPCPath, boolean processResultsScheduled) { + this.dispatchRPCPath = dispatchRPCPath; + this.processResultsScheduled = processResultsScheduled; simpleDispatch = new DefaultTransport(dispatchRPCPath); - dispatch = new CachingDispatch(new AutomaticBatchingDispatch( - simpleDispatch, processResultsScheduled)); - LOG.finest("Started dispatch system for " + dispatchRPCPath); + defaultDispatch = new CachingDispatch(new AutomaticBatchingDispatch(simpleDispatch, processResultsScheduled)); + LOG.finest("Started default dispatch system for " + dispatchRPCPath); + } + + private DispatchSystemAsync createDispatchFor(String routing) { + final StringBuilder destinationUrlBuilder = new StringBuilder(dispatchRPCPath); + if (routing != null && !routing.isEmpty()) { + if (!dispatchRPCPath.endsWith("/")) { + destinationUrlBuilder.append("/"); + } + destinationUrlBuilder.append(routing.startsWith("/") ? routing.substring(1) : routing); + } + DefaultTransport transport = new DefaultTransport(destinationUrlBuilder.toString()); + DispatchSystemAsync dispatch = new CachingDispatch(new AutomaticBatchingDispatch( + transport, processResultsScheduled)); + return dispatch; } @Override @@ -66,7 +87,19 @@ public abstract class DispatchSystemDefaultImpl imp callback.onSuccess(result); } }; - dispatch.execute(action, wrappedCallback); + final DispatchSystemAsync dispatchToUse; + if (action instanceof ServiceRoutingProvider) { + ServiceRoutingProvider providesDispatchRoutingKey = (ServiceRoutingProvider) action; + String routingPath = providesDispatchRoutingKey.routingSuffixPath(); + + dispatchToUse = routingRegistry.computeIfAbsent(routingPath, this::createDispatchFor); + + LOG.fine("Using routed dispatch for path "+ routingPath); + } else { + LOG.fine("Using default dispatch for "+ action.getClass().getName()); + dispatchToUse = defaultDispatch; + } + dispatchToUse.execute(action, wrappedCallback); } @Override diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/TextExporter.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/TextExporter.java new file mode 100644 index 00000000000..97a86cce8f9 --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/shared/TextExporter.java @@ -0,0 +1,58 @@ +package com.sap.sse.gwt.shared; + +/** + * Provides a method to export a given text to the clients clipboard. + * + * @author Lennart Hensler + */ +public class TextExporter { + + /** + * Exports the given text to the clients clipboard using native javascript. + * @param text The text to export + */ + public static native void exportToClipboard(String text) /*-{ + var textArea = document.createElement("textarea"); + // + // *** This styling is an extra step which is likely not required. *** + // + // Why is it here? To ensure: + // 1. the element is able to have focus and selection. + // 2. if element was to flash render it has minimal visual impact. + // 3. less flakyness with selection and copying which **might** occur if + // the textarea element is not visible. + // + // The likelihood is the element won't even render, not even a flash, + // so some of these are just precautions. However in IE the element + // is visible whilst the popup box asking the user for permission for + // the web page to copy to the clipboard. + // + + // Place in top-left corner of screen regardless of scroll position. + textArea.style.position = 'fixed'; + textArea.style.top = 0; + textArea.style.left = 0; + // Ensure it has a small width and height. Setting to 1px / 1em + // doesn't work as this gives a negative w/h on some browsers. + textArea.style.width = '2em'; + textArea.style.height = '2em'; + // We don't need padding, reducing the size if it does flash render. + textArea.style.padding = 0; + // Clean up any borders. + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + // Avoid flash of white box if rendered for any reason. + textArea.style.background = 'transparent'; + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + try { + var successful = document.execCommand('copy'); + } catch (err) { + console.log('Unable to copy'); + } + document.body.removeChild(textArea); + }-*/; + +} diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index d868170ee30..b1359749d89 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.10.0 +Bundle-Version: 3.10.1 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar @@ -41,6 +41,7 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.dataflow.lib.prov.stored.tcp.put, com.tractrac.dataflow.lib.spi, com.tractrac.dataflow.lib.spi.authentication, + com.tractrac.datatypes.lib.api.sensor, com.tractrac.model.lib.api, com.tractrac.model.lib.api.attachment, com.tractrac.model.lib.api.data, @@ -59,10 +60,17 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.model.lib.impl.spatial, com.tractrac.model.server.lib.api, com.tractrac.model.server.lib.api.cache, + com.tractrac.model.server.lib.api.commands, com.tractrac.model.server.lib.api.controlpassing, + com.tractrac.model.server.lib.api.devices, + com.tractrac.model.server.lib.api.gms, + com.tractrac.model.server.lib.api.loype, + com.tractrac.model.server.lib.api.notifier, com.tractrac.model.server.lib.api.position, com.tractrac.model.server.lib.api.raceobject, com.tractrac.model.server.lib.api.route, + com.tractrac.model.server.lib.api.sensor, + com.tractrac.model.server.lib.api.view, com.tractrac.osgi, com.tractrac.subscription.app.tracapi, com.tractrac.subscription.lib.api, diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 6c8802c4d2d..90b283e5ebe 100755 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -15,6 +15,29 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file + +******************************************** +************* TracAPI 3.10.1 *************** +******************************************** + This is a final version.It fixes bugs in the implementation and it adds a some features. + It keeps the backward compatibility. + + Release date: 28/06/2018 + Build number: 35a26e503339fff26fe984617e5190a18438e10b + + 1) Features + + - Adding a method to retrieve positions by class id (Requested by Chris Terkelsen, 07/05/2018) + + 2) Bugs + + - When the client is offline, if it tries to load a race it gets a RaceLoadingException. But then, + when the client is online again, it can't reuse the same race because it is in an internal wrong + status (Reported by Thomas Scott, 19/06/2018) + - The live delay is not propagated (sometimes). This feature has been reimplemented and now the + value is attached and send with the race object using the same approach used to transmit the race + start time or the tracking start time (Reported by Axel Uhl, 19/06/2018) + ******************************************** ************* TracAPI 3.10.0 *************** ******************************************** diff --git a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar index 31f01cdeaf2..de4c29d27cd 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar and b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar differ diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 290f947d14e..3bc94a18dc1 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index 83574fa3088..e1d11c5efa5 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.10.0 + 3.10.1 eclipse-plugin diff --git a/java/target/env.sh b/java/target/env.sh index d3c61f6c659..5c56c44d898 100644 --- a/java/target/env.sh +++ b/java/target/env.sh @@ -88,7 +88,6 @@ ADDITIONAL_JAVA_ARGS="-Dpersistentcompetitors.clear=false -XX:ThreadPriorityPoli # Uncomment for use with SAP JVM only: #ADDITIONAL_JAVA_ARGS="$ADDITIONAL_JAVA_ARGS -XX:+GCHistory -XX:GCHistoryFilename=logs/sapjvm_gc@PID.prf" -JAVA_HOME=/opt/jdk1.8.0_20 if [[ ! -d $JAVA_HOME ]] && [[ -f "/usr/libexec/java_home" ]]; then JAVA_HOME=`/usr/libexec/java_home` fi diff --git a/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml index 8028c52fd27..6b5907753ff 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml @@ -29,7 +29,7 @@ Ungültiger QR-Code - Ihre Regattas: + Ihre Regatten: Aktualisieren diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java index 8fa4b0c4b1d..b32079cabbc 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java @@ -13,6 +13,7 @@ import com.sap.sailing.android.shared.R; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.services.sending.MessagePersistenceManager.MessageRestorer; import com.sap.sailing.android.shared.services.sending.MessageSenderTask.MessageSendingListener; +import com.sap.sailing.android.shared.util.NotificationHelper; import com.sap.sailing.android.shared.util.PrefUtils; import com.sap.sailing.domain.common.racelog.RaceLogServletConstants; @@ -227,9 +228,17 @@ public class MessageSendingService extends Service implements MessageSendingList if (persistenceManager.areIntentsDelayed()) { handleDelayedMessages(); } + startForeground(NotificationHelper.getNotificationId(), NotificationHelper.getNotification(this)); ExLog.i(this, TAG, "Sending Service on Create."); } + @Override + public void onDestroy() { + stopForeground(true); + ExLog.i(this, TAG, "Message Sending Service is being destroyed."); + super.onDestroy(); + } + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/ui/fragments/preference/BasePreferenceFragment.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/ui/fragments/preference/BasePreferenceFragment.java index 2204a0e282e..104b6bfc7a2 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/ui/fragments/preference/BasePreferenceFragment.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/ui/fragments/preference/BasePreferenceFragment.java @@ -6,16 +6,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import com.sap.sailing.android.shared.ui.utils.MultiplePreferenceChangeListener; +import com.sap.sailing.android.shared.ui.views.EditSetPreference; + import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.MultiSelectListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.PreferenceFragment; -import android.preference.PreferenceManager; - -import com.sap.sailing.android.shared.ui.utils.MultiplePreferenceChangeListener; -import com.sap.sailing.android.shared.ui.views.EditSetPreference; /** * Created by I074137 on 18.09.13. @@ -27,7 +26,7 @@ public class BasePreferenceFragment extends PreferenceFragment { return (T) findPreference(getString(resourceId)); } - protected static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { + protected Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object value) { String stringValue = value.toString(); @@ -53,34 +52,28 @@ public class BasePreferenceFragment extends PreferenceFragment { } }; - protected static void bindPreferenceSummaryToValue(Preference preference) { + protected void bindPreferenceSummaryToValue(Preference preference) { addOnPreferenceChangeListener(preference, sBindPreferenceSummaryToValueListener); sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, - PreferenceManager - .getDefaultSharedPreferences(preference.getContext()) - .getString(preference.getKey(), "")); + getPreferenceManager().getSharedPreferences().getString(preference.getKey(), "")); } - protected static void bindPreferenceSummaryToInteger(Preference preference) { + protected void bindPreferenceSummaryToInteger(Preference preference) { addOnPreferenceChangeListener(preference, sBindPreferenceSummaryToValueListener); sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, - PreferenceManager - .getDefaultSharedPreferences(preference.getContext()) - .getInt(preference.getKey(), 0)); + getPreferenceManager().getSharedPreferences().getInt(preference.getKey(), 0)); } - protected static void bindPreferenceSummaryToSet(Preference preference) { + protected void bindPreferenceSummaryToSet(Preference preference) { addOnPreferenceChangeListener(preference, sBindPreferenceSummaryToValueListener); sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, - PreferenceManager - .getDefaultSharedPreferences(preference.getContext()) - .getStringSet(preference.getKey(), new HashSet())); + getPreferenceManager().getSharedPreferences().getStringSet(preference.getKey(), new HashSet())); } - protected static void bindPreferenceToCheckbox(CheckBoxPreference checkboxPreference, final Preference target) { + protected void bindPreferenceToCheckbox(CheckBoxPreference checkboxPreference, final Preference target) { target.setEnabled(checkboxPreference.isChecked()); addOnPreferenceChangeListener(checkboxPreference, new Preference.OnPreferenceChangeListener() { @Override @@ -91,7 +84,7 @@ public class BasePreferenceFragment extends PreferenceFragment { }); } - protected static void bindPreferenceToListEntry(Preference preference, final String defaultValue) { + protected void bindPreferenceToListEntry(Preference preference, final String defaultValue) { final ListPreference listPreference = (ListPreference) preference; if (listPreference.getValue() == null) { listPreference.setValue(defaultValue); @@ -111,7 +104,7 @@ public class BasePreferenceFragment extends PreferenceFragment { }); } - protected static void addOnPreferenceChangeListener(Preference preference, OnPreferenceChangeListener newListener) { + protected void addOnPreferenceChangeListener(Preference preference, OnPreferenceChangeListener newListener) { OnPreferenceChangeListener oldListener = preference.getOnPreferenceChangeListener(); if (oldListener == null) { MultiplePreferenceChangeListener multiListener = new MultiplePreferenceChangeListener(); diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java index 2d982ac227c..bf50986df35 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/AppUtils.java @@ -1,9 +1,5 @@ package com.sap.sailing.android.shared.util; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; - import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; @@ -16,6 +12,10 @@ import android.util.Log; import com.sap.sailing.android.shared.R; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + public class AppUtils { private final static String TAG = AppUtils.class.getName(); @@ -37,9 +37,9 @@ public class AppUtils { */ public static void lockOrientation(Activity activity) { if (with(activity).isPhone()) { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); } else { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); } } diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/NotificationHelper.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/NotificationHelper.java new file mode 100644 index 00000000000..4bc5834d710 --- /dev/null +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/util/NotificationHelper.java @@ -0,0 +1,56 @@ +package com.sap.sailing.android.shared.util; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.graphics.Bitmap; +import android.support.v4.app.NotificationCompat; + +public class NotificationHelper { + + private static final int NOTIFICATION_ID = 13062018; + private static Bitmap largeIcon; + private static CharSequence title; + private static int smallIcon; + + public static void prepareNotificationWith(CharSequence appTitle, Bitmap appIcon, int notificationIcon) { + title = appTitle; + largeIcon = appIcon; + smallIcon = notificationIcon; + } + + public static Notification getNotification(Context context) { + return getNotification(context, title, ""); + } + + public static Notification getNotification(Context context, CharSequence customTitle, String content) { + return getNotification(context, customTitle, content, null); + } + + + public static Notification getNotification(Context context, CharSequence customTitle, String content, PendingIntent intent) { + return getNotification(context, customTitle, content, intent, Notification.COLOR_DEFAULT); + } + + public static Notification getNotification(Context context, CharSequence customTitle, String content, PendingIntent intent, int color) { + NotificationCompat.Builder builder = new NotificationCompat.Builder(context) + .setContentText(content) + .setContentTitle(customTitle) + .setLargeIcon(largeIcon) + .setSmallIcon(smallIcon) + .setOngoing(true) + .setColor(color) + .setPriority(Notification.PRIORITY_HIGH); + + if (intent != null) { + builder.setContentIntent(intent); + } + + return builder.build(); + } + + + public static int getNotificationId() { + return NOTIFICATION_ID; + } +} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java index 9b6745e6c50..9228253f1e1 100755 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java @@ -15,12 +15,10 @@ import org.json.JSONException; import org.json.JSONObject; import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.graphics.BitmapFactory; import android.hardware.GeomagneticField; import android.location.Location; import android.location.LocationListener; @@ -33,12 +31,12 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.support.v4.app.NotificationCompat; import android.widget.Toast; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.services.sending.MessageSendingService; import com.sap.sailing.android.shared.ui.customviews.GPSQuality; +import com.sap.sailing.android.shared.util.NotificationHelper; import com.sap.sailing.android.tracking.app.BuildConfig; import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; @@ -64,16 +62,11 @@ public class TrackingService extends Service implements LocationListener { private static final int GREAT_DISTANCE = 10; private static final int NO_DISTANCE = 0; - private NotificationManager notificationManager; private AppPreferences prefs; private GPSQualityListener gpsQualityListener; private final IBinder trackingBinder = new TrackingBinder(); - // Unique Identification Number for the Notification. - // We use it on Notification start, and to cancel it. - private int NOTIFICATION_ID = R.string.tracker_started; - public final static int UPDATE_INTERVAL_IN_MILLIS_DEFAULT = 1000; public final static String GPS_DISABLED_MESSAGE = "gpsDisabled"; private float minLocationUpdateDistanceInMeters = 0f; @@ -127,7 +120,6 @@ public class TrackingService extends Service implements LocationListener { locationsQueuedBasedOnSendingInterval = new LinkedHashMap<>(); prefs = new AppPreferences(this); - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); } @@ -342,7 +334,7 @@ public class TrackingService extends Service implements LocationListener { @Override public void onDestroy() { stopTracking(); - notificationManager.cancel(NOTIFICATION_ID); + stopForeground(false); Toast.makeText(this, R.string.tracker_stopped, Toast.LENGTH_SHORT).show(); } @@ -387,16 +379,15 @@ public class TrackingService extends Service implements LocationListener { private void showNotification() { Intent intent = new Intent(this, TrackingActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); - Notification notification = new NotificationCompat.Builder(this) - .setContentTitle(getText(R.string.app_name)) - .setContentText(getString(R.string.tracking_notification_text, event.name)) - .setContentIntent(pi) - .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) - .setSmallIcon(R.drawable.ic_directions_boat) - .setOngoing(true) - .build(); - notificationManager.notify(NOTIFICATION_ID, notification); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); + + Notification notification = NotificationHelper.getNotification( + this, + getText(R.string.app_name), + getString(R.string.tracking_notification_text, event.name), + pendingIntent + ); + startForeground(NotificationHelper.getNotificationId(), notification); } public void registerGPSQualityListener(GPSQualityListener listener) { diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/StartActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/StartActivity.java index 750e8de0edf..df85fa03e80 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/StartActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/StartActivity.java @@ -6,6 +6,7 @@ import com.sap.sailing.android.shared.data.BaseCheckinData; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.ui.activities.AbstractStartActivity; import com.sap.sailing.android.shared.util.EulaHelper; +import com.sap.sailing.android.shared.util.NotificationHelper; import com.sap.sailing.android.tracking.app.R; import com.sap.sailing.android.tracking.app.ui.fragments.HomeFragment; import com.sap.sailing.android.tracking.app.utils.AboutHelper; @@ -19,6 +20,8 @@ import com.sap.sailing.android.tracking.app.valueobjects.MarkCheckinData; import com.sap.sailing.android.ui.fragments.AbstractHomeFragment; import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.Menu; @@ -49,6 +52,11 @@ public class StartActivity extends AbstractStartActivity { if (!EulaHelper.with(this).isEulaAccepted()) { EulaHelper.with(this).showEulaDialog(R.style.AppTheme_AlertDialog); } + + Bitmap largeIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); + int smallIcon = R.drawable.ic_directions_boat; + CharSequence title = getText(R.string.app_name); + NotificationHelper.prepareNotificationWith(title, largeIcon, smallIcon); } @Override diff --git a/mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml b/mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml index cce5b2f7dab..0ab09d05df2 100644 --- a/mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml +++ b/mobile/com.sap.sailing.racecommittee.app/AndroidManifest.xml @@ -44,7 +44,9 @@ android:name="com.google.android.maps.v2.API_KEY" android:value="AIzaSyDH-Dwy5yFlyBa2-VUgiZ3qGGUO8M_0MPY" /> - + @@ -69,10 +71,11 @@ + android:windowSoftInputMode="adjustPan" /> @@ -118,4 +121,4 @@ - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/compass_view.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/compass_view.xml index 69cfbe2b703..9e9417d3265 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/compass_view.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/compass_view.xml @@ -11,6 +11,7 @@ android:layout_centerInParent="true" android:layout_gravity="center" android:contentDescription="@string/compass_view_compass" + android:scaleType="centerInside" android:src="?attr/compass_back" /> - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/competitor_tools_layout.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/competitor_tools_layout.xml index 1baec17bd7a..cb776746a64 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/competitor_tools_layout.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/competitor_tools_layout.xml @@ -1,6 +1,5 @@ + + + + + + android:layout_alignParentRight="true" /> - - - - - - \ No newline at end of file + android:layout_toRightOf="@id/list_button_layout" /> + + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml index 9ff20880824..67bd13d5293 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml @@ -22,6 +22,7 @@ android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="@dimen/header_margin_top" + android:gravity="center_horizontal" android:textAppearance="?attr/textPanelHeader" android:visibility="visible" tools:text="@string/start_mode" /> @@ -33,6 +34,7 @@ android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:layout_marginBottom="@dimen/header_margin_top" + android:gravity="center_horizontal" android:textAppearance="?attr/textPanelCaption" android:visibility="gone" tools:text="@string/start_mode" /> diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_setup_ver.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_setup_ver.xml index 6054a98eb21..dd5aacf4650 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_setup_ver.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_panel_setup_ver.xml @@ -82,4 +82,4 @@ android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:background="?attr/black" /> - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_schedule_course_marks_xlarge.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_schedule_course_marks_xlarge.xml index a38d5b76f20..7195e381d3a 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_schedule_course_marks_xlarge.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_schedule_course_marks_xlarge.xml @@ -46,52 +46,54 @@ android:textStyle="bold" /> - - + android:text="@string/take_previous" /> -