From 149349aacf6fc3adcbeb3cd95fb4011e8c9ebbf6 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 16 May 2019 16:28:01 +0200 Subject: [PATCH 01/24] extrapolate to make sure we get a position for a Candidate, even if there are no real fixes; may help avoid a NullPointerException for calls on bounds Change-Id: Idfb92d61bba942860309ccb35c9fbac53163fd2f --- .../domain/markpassingcalculation/impl/StationarySequence.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java index cc1284d6283..4f8808edcbc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java @@ -322,7 +322,7 @@ public class StationarySequence { * If no estimated position can be obtained, {@code null} is returned. */ private Bounds createNewBounds(Candidate candidate) { - final Position estimatedPosition = track.getEstimatedPosition(candidate.getTimePoint(), /* extrapolate */ false); + final Position estimatedPosition = track.getEstimatedPosition(candidate.getTimePoint(), /* extrapolate */ true); return estimatedPosition == null ? null : new BoundsImpl(estimatedPosition); } From 8bb45a81511c22283e93d2f69669950293ec578f Mon Sep 17 00:00:00 2001 From: "service.tip.git" Date: Thu, 16 May 2019 16:04:28 +0000 Subject: [PATCH 02/24] [INTERNAL] Translation delivery: commit by SLS Change-Id: I75ab1dffb281e0fefb4860d4e87508ea9c90145e --- .../client/i18n/StringMessages_es.properties | 158 ++++++++--------- .../client/i18n/StringMessages_fr.properties | 158 ++++++++--------- .../client/i18n/StringMessages_ja.properties | 159 ++++++++--------- .../client/i18n/StringMessages_pt.properties | 160 ++++++++--------- .../client/i18n/StringMessages_ru.properties | 162 +++++++++--------- .../client/i18n/StringMessages_zh.properties | 161 ++++++++--------- 6 files changed, 480 insertions(+), 478 deletions(-) diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_es.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_es.properties index bf122e601bc..199ad3c6819 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_es.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_es.properties @@ -34,7 +34,7 @@ userDetails=Detalles de usuario deleteUser=Borrar usuario deleteUserQuestion=¿Desea borrar el usuario? doYouReallyWantToDeleteUser=¿Está seguro de que desea borrar el usuario {0}? -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers=¿Está seguro de que desea borrar los usuarios {0}? account={0}-Cuenta save=Grabar remove=Eliminar @@ -108,85 +108,85 @@ settingsRemovedError=No se han podido restablecer las parametrizaciones por defe showMoreInfoLogin=Regístrese para acceder a más funciones dismiss=Más tarde moreInfo=Más info -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... +editACL=Editar ACL +errorEditingACL=Error al editar la lista de control de accesos (ACL). +editAnACL=Editar una lista de control de accesos (ACL). +createUserGroup=Crear grupo de usuarios +removeUserGroup=Eliminar grupo de usuarios +youHaveToSelectAUserGroup=Debe seleccionar un grupo de usuarios. +doYouReallyWantToRemoveUserGroup=¿Está seguro de que desea eliminar el grupo de usuarios {0}? +couldNotDeleteUserGroup=No se ha podido borrar el grupo de usuarios. +filterUserGroups=Filtrar grupos de usuarios... id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles -actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes +displayName=Visualizar nombre +userGroups=Grupos de usuarios +errorTryingToLoadRoles=Error al intentar cargar roles: {0} +filterRoles=Filtrar roles +actions=Acciones +actionEdit=Editar +actionRemove=Eliminar +actionChangeOwnership=Modificar propiedad +errorUpdatingOwnership=Error al actualizar la propiedad del objeto {0} +doYouReallyWantToRemoveRole=¿Está seguro de que desea eliminar el rol o los roles "{0}"? +errorTryingToDeleteRole=Error al intentar eliminar el rol "{0}": {1} +pleaseEnterARoleName=Indique un nombre de rol +editRoles=Editar roles +otherRoleWithNameAlreadyExists=Ya existe otro rol con el nombre "{0}". +errorCreatingRole=Error al crear el rol "{0}": {1} +errorEditingRoles=Error al editar el rol "{0}": {1} +usersInUserGroup=Usuarios en el grupo de usuarios +allUsers=Todos los usuarios +couldNotAddUserToUserGroup=No se ha podido añadir el usuario {0} al grupo de usuarios {1}: {2} +couldNotRemoveUserFromUserGroup=No se ha podido eliminar el usuario {0} del grupo de usuarios {1}: {2} +enterUserGroupName=Indicar nombre de grupo de usuarios +defaultTenant=Grupo de usuarios que debe utilizarse para la creación de objetos +pagerStateInfo={0,number,#,###}-{1,number,#,###} de {2,number,#,###} +pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} de más de {2,number,#,###} +yes=Sí no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -errorDeletingUser=Se ha producido un error al borrar el usuario {0}: {1} -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +editUser=Editar usuario +validated=Validado +groups=Grupos +group=Grupo +errorDeletingUser=Error al borrar el usuario {0}: {1} +doYouReallyWantToRemoveUser=¿Está seguro de que desea eliminar el usuario {0}? +errorTryingToUpdateUser=Error al intentar actualizar el usuario {0}: {1} +ownership=Propiedad +editObjectOwnership=Editar propiedad de objeto +errorObtainingUser=Error al obtener el usuario: {0} +errorObtainingUserGroup=Error al obtener el grupo de usuarios: {0} +pleaseWaitUntilUsernameIsResolved=Espere hasta que el nombre del usuario se haya resuelto +pleaseWaitUntilUserGroupNameIsResolved=Espere hasta que el nombre del grupo de usuarios se haya resuelto +userNotFound=El usuario {0} no existe +usergroupNotFound=El grupo de usuarios {0} no existe +actionChangeACL=Modificar ACL +editACLForObject=Editar ACL para ''{0}'' acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=Error al actualizar ACL: {0}. +allowedActionName=Nombre de acción permitido +deniedActionName=Nombre de acción denegado +errorMessageUserGroupNameNotFound=No se ha encontrado el grupo de usuarios por nombre {0}. +successMessageAddedUserGroup=Grupo de usuarios añadido {0}. +successMessageRemovedUserGroup=Grupo de usuarios eliminado {0}. +allowedActions=Acciones permitidas +deniedActions=Acciones denegadas +migrateGroupOwner=Migrar propietario de grupo para jerarquía +errorDeletingUserGroup=Error al borrar el grupo de usuarios {0}: {1} +enterAtLeastOneOwner=Indique como mínimo un propietario +removeRole=Eliminar rol +addRole=Añadir rol +couldNotAddRoleToGroup=El rol {0} no se ha podido añadir al grupo {1}. +roleName=Nombre de rol +enabledForAllUsers=Activado para todos los usuarios +couldNotDeleteRole=No se ha podido borrar el rol {0}. +couldNotLoadRoles=No se han podido cargar los roles. +enterUsername=Indicar nombre de usuario +addUser=Añadir usuario +couldNotLoadUsers=No se han podido cargar los usuarios. +couldNotRemoveRoleFromUser=No se ha podido eliminar el rol ''{1}'' del usuario ''{0}''. +couldNotRemovePermissionFromUser=No se ha podido eliminar el permiso ''{1}'' del usuario ''{0}''. +filterPermission=Autorización de filtro +permission=Autorización nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=Editar roles y autorizaciones para el usuario {0} +permissionType:Tipo de autorización diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_fr.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_fr.properties index 2955becce4f..1f9d3054654 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_fr.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_fr.properties @@ -34,7 +34,7 @@ userDetails=Détails utilisateur deleteUser=Supprimer utilisateur deleteUserQuestion=Supprimer l''utilisateur ? doYouReallyWantToDeleteUser=Souhaitez-vous vraiment supprimer l''utilisateur {0} ? -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers=Voulez-vous vraiment supprimer {0} utilisateur(s) ? account=Compte {0} save=Enregistrer remove=Supprimer @@ -108,85 +108,85 @@ settingsRemovedError=Impossible de restaurer les options par défaut showMoreInfoLogin=Connectez-vous pour accéder à davantage de fonctionnalités dismiss=Plus tard moreInfo=Plus d''infos -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... +editACL=Modifier la liste de contrôle d''accès (ACL) +errorEditingACL=Erreur lors de la modification de la liste de contrôle d''accès (ACL) +editAnACL=Modifier une liste de contrôle d''accès (ACL) +createUserGroup=Créer groupe d''utilisateurs +removeUserGroup=Supprimer groupe d''utilisateurs +youHaveToSelectAUserGroup=Vous devez sélectionner un groupe d''utilisateurs. +doYouReallyWantToRemoveUserGroup=Souhaitez-vous vraiment supprimer le groupe d''utilisateurs {0} ? +couldNotDeleteUserGroup=Impossible de supprimer le groupe d''utilisateurs +filterUserGroups=Filtrer les groupes d''utilisateurs... id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles +displayName=Nom d''affichage +userGroups=Groupes d''utilisateurs +errorTryingToLoadRoles=Erreur lors du chargement de rôles : {0} +filterRoles=Filtrer rôles actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes -no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -errorDeletingUser=Une erreur s''est produite lors de la suppression de l''utilisateur {0}: {1} -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +actionEdit=Modifier +actionRemove=Supprimer +actionChangeOwnership=Modifier détention +errorUpdatingOwnership=Erreur lors de la mise à jour de la détention de l''objet {0}. +doYouReallyWantToRemoveRole=Souhaitez-vous vraiment supprimer le(s) rôle(s) "{0}" ? +errorTryingToDeleteRole=Erreur lors de la suppression du rôle"{0}" : {1} +pleaseEnterARoleName=Saisissez un nom de rôle. +editRoles=Modifier rôles +otherRoleWithNameAlreadyExists=Un rôle du nom "{0}" existe déjà. +errorCreatingRole=Erreur lors de la création du rôle "{0}" : {1} +errorEditingRoles=Erreur lors de la modification du rôle "{0}" : {1} +usersInUserGroup=Utilisateurs dans le groupe d''utilisateurs +allUsers=Tous les utilisateurs +couldNotAddUserToUserGroup=Impossible d''ajouter l''utilisateur {0} au groupe d''utilisateurs {1} : {2} +couldNotRemoveUserFromUserGroup=Impossible de supprimer l''utilisateur {0} du groupe d''utilisateurs {1} : {2} +enterUserGroupName=Saisissez le nom du groupe d''utilisateurs. +defaultTenant=Groupe d''utilisateurs à utiliser pour la création d''objet +pagerStateInfo={0,number,#,###}-{1,number,#,###} sur {2,number,#,###} +pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} sur plus de {2,number,#,###} +yes=Oui +no=Non +editUser=Modifier utilisateur +validated=Validé +groups=Groupes +group=Groupe +errorDeletingUser=Erreur lors de la suppression de l''utilisateur {0} : {1} +doYouReallyWantToRemoveUser=Supprimer vraiment l''utilisateur {0} ? +errorTryingToUpdateUser=Erreur lors de la mise à jour de l''utilisateur {0} : {1} +ownership=Détention +editObjectOwnership=Modifier la détention de l''objet +errorObtainingUser=Erreur lors de l''accès à l''utilisateur : {0} +errorObtainingUserGroup=Erreur lors de l''accès au groupe d''utilisateurs : {0} +pleaseWaitUntilUsernameIsResolved=Veuillez patienter jusqu''à la résolution du nom d''utilisateur. +pleaseWaitUntilUserGroupNameIsResolved=Veuillez patienter jusqu''à la résolution du nom du groupe d''utilisateurs. +userNotFound=Utilisateur {0} introuvable +usergroupNotFound=Groupe d''utilisateurs {0} introuvable +actionChangeACL=Modifier l''ACL +editACLForObject=Modifier l''ACL en ''{0}'' acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=Erreur lors de la mise à jour de l''ACL : {0} +allowedActionName=Nom de l''action autorisée +deniedActionName=Nom de l''action refusée +errorMessageUserGroupNameNotFound=La recherche du groupe d''utilisateurs par le nom {0} a échoué. +successMessageAddedUserGroup=Groupe d''utilisateurs {0} ajouté +successMessageRemovedUserGroup=Groupe d''utilisateurs {0} supprimé +allowedActions=Actions autorisées +deniedActions=Actions refusées +migrateGroupOwner=Migrer détenteur de groupe pour hiérarchie +errorDeletingUserGroup=Erreur lors de la suppression du groupe d''utilisateurs {0} : {1} +enterAtLeastOneOwner=Veuillez saisir au moins un détenteur. +removeRole=Supprimer rôle +addRole=Ajouter rôle +couldNotAddRoleToGroup=Le rôle {0} n''a pas pu être ajouté au groupe {1}. +roleName=Nom du rôle +enabledForAllUsers=Activé pour tous les utilisateurs +couldNotDeleteRole=Impossible de supprimer le rôle {0} +couldNotLoadRoles=Impossible de charger les rôles +enterUsername=Saisissez le nom d''utilisateur. +addUser=Ajouter utilisateur +couldNotLoadUsers=Impossible de charger les utilisateurs +couldNotRemoveRoleFromUser=Impossible de supprimer le rôle ''{1}'' de l''utilisateur ''{0}''. +couldNotRemovePermissionFromUser=Impossible de supprimer l''autorisation ''{1}'' de l''utilisateur ''{0}''. +filterPermission=Filtrer autorisations +permission=Autorisation nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=Modifier les rôles et autorisations pour l''utilisateur {0} +permissionType:Type d''autorisation diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ja.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ja.properties index c6feaad4fcb..9ed1601907d 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ja.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ja.properties @@ -34,7 +34,7 @@ userDetails=ユーザ詳細 deleteUser=ユーザ削除 deleteUserQuestion=ユーザを削除しますか doYouReallyWantToDeleteUser=ユーザ {0} を削除しますか -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers={0} ユーザを削除しますか account={0} アカウント save=保存 remove=削除 @@ -108,84 +108,85 @@ settingsRemovedError=デフォルト設定を復元できませんでした showMoreInfoLogin=サインインして追加機能へのアクセス権限を取得してください dismiss=後で moreInfo=追加情報 -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... +editACL=ACL 編集 +errorEditingACL=アクセスコントロールリスト (ACL) の編集でエラーが発生しました。 +editAnACL=アクセスコントロールリスト (ACL) を編集 +createUserGroup=ユーザグループを作成 +removeUserGroup=ユーザグループを削除 +youHaveToSelectAUserGroup=ユーザグループを選択する必要があります。 +doYouReallyWantToRemoveUserGroup=ユーザグループ {0} を削除しますか。 +couldNotDeleteUserGroup=ユーザグループを削除できませんでした。 +filterUserGroups=ユーザグループのフィルタリング... id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles -actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes -no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +displayName=表示名 +userGroups=ユーザグループ +errorTryingToLoadRoles=ロールをロードしようとしてエラーが発生: {0} +filterRoles=ロールのフィルタリング +actions=アクション +actionEdit=編集 +actionRemove=削除 +actionChangeOwnership=オーナーシップ変更 +errorUpdatingOwnership=オブジェクト {0} のオーナーシップ更新でエラーが発生しました +doYouReallyWantToRemoveRole=ロール "{0}" を削除しますか +errorTryingToDeleteRole=ロール "{0}" を削除しようとしてエラーが発生: {1} +pleaseEnterARoleName=ロール名を入力してください +editRoles=ロール編集 +otherRoleWithNameAlreadyExists="{0}" という名前の別のロールはすでに存在しています。 +errorCreatingRole=ロール "{0}" の作成でエラー発生: {1} +errorEditingRoles=ロール "{0}" の編集でエラー発生: {1} +usersInUserGroup=ユーザグループにあるユーザ +allUsers=すべてのユーザ +couldNotAddUserToUserGroup=ユーザ {0} をユーザグループ {1} に追加できませんでした: {2} +couldNotRemoveUserFromUserGroup=ユーザ {0} をユーザグループ {1} から削除できませんでした: {2} +enterUserGroupName=ユーザグループ名を入力 +defaultTenant=オブジェクト作成に使用するユーザグループ +pagerStateInfo={2,number,#,###} の {0,number,#,###} ~ {1,number,#,###} +pagerStateInfo[false]={2,number,#,###} 上の {0,number,#,###} ~ {1,number,#,###} +yes=はい +no=いいえ +editUser=ユーザ編集 +validated=チェック済 +groups=グループ +group=グループ +errorDeletingUser=ユーザ {0} の削除でエラー発生: {1} +doYouReallyWantToRemoveUser=ユーザ {0} を削除しますか +errorTryingToUpdateUser=ユーザ {0} を更新しようとしてエラーが発生: {1} +ownership=オーナーシップ +editObjectOwnership=オブジェクトオーナーシップ編集 +errorObtainingUser=ユーザの取得でエラーが発生: {0} +errorObtainingUserGroup=ユーザグループの取得でエラーが発生: {0} +pleaseWaitUntilUsernameIsResolved=ユーザ名が削除されるまで待ってください +pleaseWaitUntilUserGroupNameIsResolved=ユーザグループ名が解決されるまで待ってください +userNotFound=ユーザ {0} は見つかりませんでした +usergroupNotFound=ユーザグループ {0} は見つかりませんでした +actionChangeACL=ACL 変更 +editACLForObject=''{0}'' の ACL を編集 acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=ACL の更新でエラー発生: {0} +allowedActionName=許可されたアクション名 +deniedActionName=拒否されたアクション名 +errorMessageUserGroupNameNotFound={0} という名前でユーザグループが見つかりませんでした。 +successMessageAddedUserGroup=ユーザグループ {0} が追加されました。 +successMessageRemovedUserGroup=ユーザグループ {0} が削除されました。 +allowedActions=許可されたアクション +deniedActions=拒否されたアクション +migrateGroupOwner=階層のグループオーナーを移行 +errorDeletingUserGroup=ユーザグループ {0} の削除でエラーが発生: {1} +enterAtLeastOneOwner=少なくとも 1 名のオーナーを入力してください +removeRole=ロール削除 +addRole=ロール追加 +couldNotAddRoleToGroup=ロール {0} をグループ {1} に追加できませんでした。 +roleName=ロール名 +enabledForAllUsers=すべてのユーザに対して有効化されました +couldNotDeleteRole=ロール {0} を削除できませんでした。 +couldNotLoadRoles=ロールをロードできませんでした。 +enterUsername=ユーザ名を入力してください +addUser=ユーザ追加 +couldNotLoadUsers=ユーザをロードできませんでした。 +couldNotRemoveRoleFromUser=ロール ''{1}'' をユーザ ''{0}'' から削除できませんでした。 +couldNotRemovePermissionFromUser=権限 ''{1}'' をユーザ ''{0}'' から削除できませんでした。 +filterPermission=権限のフィルタリング +permission=権限 nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=ユーザ {0} のロールと権限を編集 +permissionType:権限タイプ diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_pt.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_pt.properties index 5594b54ee6b..019347dd719 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_pt.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_pt.properties @@ -34,7 +34,7 @@ userDetails=Detalhes do usuário deleteUser=Eliminar usuário deleteUserQuestion=Eliminar usuário? doYouReallyWantToDeleteUser=Eliminar o usuário {0}? -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers=Eliminar {0} usuários? account=Conta {0} save=Gravar remove=Eliminar @@ -108,85 +108,85 @@ settingsRemovedError=Não foi possível restaurar configurações padrão showMoreInfoLogin=Efetue o logon para obter acesso a mais funcionalidades dismiss=Mais tarde moreInfo=Mais informação -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... +editACL=Processar ACL +errorEditingACL=Erro ao processar lista de controle de acesso (ACL). +editAnACL=Processar uma lista de controle de acesso (ACL) +createUserGroup=Criar grupo de usuários +removeUserGroup=Remover grupo de usuários +youHaveToSelectAUserGroup=Você precisa selecionar um grupo de usuários. +doYouReallyWantToRemoveUserGroup=Remover o grupo de usuários {0}? +couldNotDeleteUserGroup=Não foi possível eliminar grupo de usuários. +filterUserGroups=Filtrar grupos de usuários... id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles -actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes -no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -errorDeletingUser=Ocorreu um erro ao eliminar o usuário {0}: {1} -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +displayName=Nome de exibição +userGroups=Grupos de usuários +errorTryingToLoadRoles=Erro ao tentar carregar funções: {0} +filterRoles=Filtrar funções +actions=Ações +actionEdit=Processar +actionRemove=Remover +actionChangeOwnership=Modificar propriedade +errorUpdatingOwnership=Erro ao atualizar propriedade de objeto {0} +doYouReallyWantToRemoveRole=Remover funções "{0}"? +errorTryingToDeleteRole=Erro ao tentar eliminar função "{0}": {1} +pleaseEnterARoleName=Inserir um nome de função +editRoles=Processar funções +otherRoleWithNameAlreadyExists=Já existe outra função com o nome "{0}". +errorCreatingRole=Erro ao criar função "{0}": {1} +errorEditingRoles=Erro ao processar função "{0}": {1} +usersInUserGroup=Usuários no grupo de usuários +allUsers=Todos os usuários +couldNotAddUserToUserGroup=Não foi possível adicionar o usuário {0} ao grupo de usuários {1}: {2} +couldNotRemoveUserFromUserGroup=Não foi possível remover o usuário {0} do grupo de usuários {1}: {2} +enterUserGroupName=Inserir nome do grupo de usuários +defaultTenant=Grupo de usuários para utilizar para criação de objeto +pagerStateInfo={0,number,#,###}-{1,number,#,###} de {2,number,#,###} +pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} de mais de {2,number,#,###} +yes=Sim +no=Não +editUser=Processar usuário +validated=Validado +groups=Grupos +group=Grupo +errorDeletingUser=Erro ao eliminar usuário {0}: {1} +doYouReallyWantToRemoveUser=Remover usuário {0}? +errorTryingToUpdateUser=Erro ao tentar atualizar usuário {0}: {1} +ownership=Propriedade +editObjectOwnership=Processar propriedade de objeto +errorObtainingUser=Erro ao obter usuário: {0} +errorObtainingUserGroup=Erro ao obter grupo de usuários: {0} +pleaseWaitUntilUsernameIsResolved=Aguardar até nome de usuário estar resolvido +pleaseWaitUntilUserGroupNameIsResolved=Aguardar até nome de grupo de usuários estar resolvido +userNotFound=Usuário {0} não encontrado +usergroupNotFound=Grupo de usuários {0} não encontrado +actionChangeACL=Modificar ACL +editACLForObject=Processar ACL para ''{0}'' acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=Erro ao atualizar ACL: {0}. +allowedActionName=Nome de ação permitido +deniedActionName=Nome de ação recusado +errorMessageUserGroupNameNotFound=Grupo de usuários não encontrado por nome {0}. +successMessageAddedUserGroup=Grupo de usuários adicionado {0}. +successMessageRemovedUserGroup=Grupo de usuários removido {0}. +allowedActions=Ações permitidas +deniedActions=Ações recusadas +migrateGroupOwner=Migrar proprietário do objeto para hierarquia +errorDeletingUserGroup=Erro ao eliminar grupo de usuários {0}: {1} +enterAtLeastOneOwner=Inserir pelo menos um proprietário +removeRole=Remover função +addRole=Adicionar função +couldNotAddRoleToGroup=Não foi possível adicionar função {0} ao grupo {1}. +roleName=Nome da função +enabledForAllUsers=Ativado para todos os usuários +couldNotDeleteRole=Não foi possível eliminar função {0}. +couldNotLoadRoles=Não foi possível carregar funções. +enterUsername=Inserir nome do usuário +addUser=Adicionar usuário +couldNotLoadUsers=Não foi possível carregar usuários. +couldNotRemoveRoleFromUser=Não foi possível remover função ''{1}'' de usuário ''{0}''. +couldNotRemovePermissionFromUser=Não foi possível remover autorização ''{1}'' do usuário ''{0}''. +filterPermission=Filtrar autorização +permission=Autorização nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=Processar funções e autorizações para usuário {0} +permissionType:Tipo de autorização diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ru.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ru.properties index 7cab3ce359f..8d76edd19ee 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ru.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ru.properties @@ -34,7 +34,7 @@ userDetails=Данные пользователя deleteUser=Удалить пользователя deleteUserQuestion=Удалить пользователя? doYouReallyWantToDeleteUser=Действительно удалить пользователя {0}? -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers=Удалить {0} пользователей? account={0}-учетная запись save=Сохранить remove=Удалить @@ -108,85 +108,85 @@ settingsRemovedError=Не удалось восстановить настрой showMoreInfoLogin=Выполните вход, чтобы получить доступ к дополнительным функциям dismiss=Позже moreInfo=Дополнительная информация -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... -id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles -actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes -no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -errorDeletingUser=При удалении пользователя {0} возникла ошибка: {1} -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +editACL=Редактировать ACL +errorEditingACL=Ошибка при редактировании списка контроля доступа (ACL) +editAnACL=Редактировать список контроля доступа (ACL) +createUserGroup=Создать группу пользователей +removeUserGroup=Удалить группу пользователей +youHaveToSelectAUserGroup=Необходимо выбрать группу пользователей. +doYouReallyWantToRemoveUserGroup=Действительно удалить группу пользователей {0}? +couldNotDeleteUserGroup=Не удалось удалить группу пользователей. +filterUserGroups=Фильтровать группы пользователей... +id=Ид. +displayName=Отображаемое имя +userGroups=Группы пользователей +errorTryingToLoadRoles=Ошибка при попытке загрузки ролей: {0} +filterRoles=Фильтровать роли +actions=Операции +actionEdit=Редактировать +actionRemove=Удалить +actionChangeOwnership=Изменить владение +errorUpdatingOwnership=Ошибка при обновлении владения для объекта {0} +doYouReallyWantToRemoveRole=Действительно удалить роль/роли "{0}"? +errorTryingToDeleteRole=Ошибка при попытке удаления роли "{0}": {1} +pleaseEnterARoleName=Введите имя роли +editRoles=Редактировать роли +otherRoleWithNameAlreadyExists=Уже существует другая роль с именем "{0}". +errorCreatingRole=Ошибка при создании роли "{0}": {1} +errorEditingRoles=Ошибка при редактировании роли "{0}": {1} +usersInUserGroup=Пользователи в группе пользователей +allUsers=Все пользователи +couldNotAddUserToUserGroup=Не удалось добавить пользователя {0} в группу пользователей {1}: {2} +couldNotRemoveUserFromUserGroup=Не удалось удалить пользователя {0} из группы пользователей {1}: {2} +enterUserGroupName=Введите имя группы пользователей +defaultTenant=Группа пользователей для создания объекта +pagerStateInfo={0,number,#,###}-{1,number,#,###} из {2,number,#,###} +pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} из более чем {2,number,#,###} +yes=Да +no=Нет +editUser=Редактировать пользователя +validated=Проверено +groups=Группы +group=Группа +errorDeletingUser=Ошибка при удалении пользователя {0}: {1} +doYouReallyWantToRemoveUser=Действительно удалить пользователя {0}? +errorTryingToUpdateUser=Ошибка при попытке обновления пользователя {0}: {1} +ownership=Владение +editObjectOwnership=Редактировать владение объектом +errorObtainingUser=Ошибка при получении пользователя: {0} +errorObtainingUserGroup=Ошибка при получении группы пользователей: {0} +pleaseWaitUntilUsernameIsResolved=Дождитесь разрешения имени пользователя +pleaseWaitUntilUserGroupNameIsResolved=Дождитесь разрешения имени группы пользователей +userNotFound=Пользователь {0} не найден +usergroupNotFound=Группа пользователей {0} не найдена +actionChangeACL=Изменить ACL +editACLForObject=Редактировать ACL для ''{0}'' acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=Ошибка при обновлении ACL: {0}. +allowedActionName=Имя разрешенной операции +deniedActionName=Имя отклоненной операции +errorMessageUserGroupNameNotFound=Не удалось найти группу пользователей пл имени {0}. +successMessageAddedUserGroup=Группа пользователей {0} добавлена. +successMessageRemovedUserGroup=Группа пользователей {0} удалена. +allowedActions=Разрешенные операции +deniedActions=Отклоненные операции +migrateGroupOwner=Выполнить миграцию владельца группы для иерархии +errorDeletingUserGroup=Ошибка при удалении группы пользователей {0}: {1} +enterAtLeastOneOwner=Введите хотя бы одного владельца +removeRole=Удалить роль +addRole=Добавить роль +couldNotAddRoleToGroup=Роль {0} не удалось добавить в группу {1} +roleName=Имя роли +enabledForAllUsers=Активировано для всех пользователей +couldNotDeleteRole=Не удалось удалить роль {0}. +couldNotLoadRoles=Не удалось загрузить роли. +enterUsername=Введите имя пользователя +addUser=Добавить пользователя +couldNotLoadUsers=Не удалось загрузить пользователей. +couldNotRemoveRoleFromUser=Не удалось удалить роль ''{1}'' у пользователя ''{0}''. +couldNotRemovePermissionFromUser=Не удалось удалить полномочия ''{1}'' у пользователя ''{0}''. +filterPermission=Фильтр полномочий +permission=Полномочия nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=Редактировать роли и полномочия для пользователя {0} +permissionType:Тип полномочий diff --git a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh.properties b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh.properties index 080e3cae6a3..fc6e820456d 100644 --- a/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh.properties +++ b/java/com.sap.sse.security.ui/src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh.properties @@ -34,7 +34,7 @@ userDetails=用户详细信息 deleteUser=删除用户 deleteUserQuestion=是否删除用户? doYouReallyWantToDeleteUser=是否确定删除用户{0}? -doYouReallyWantToDeleteNUsers=Do you really want to delete {0} users? +doYouReallyWantToDeleteNUsers=是否确定删除{0}个用户? account={0} 账户 save=保存 remove=移除 @@ -108,84 +108,85 @@ settingsRemovedError=无法恢复默认设置 showMoreInfoLogin=请登录,以访问更多功能 dismiss=稍后 moreInfo=更多信息 -editACL=Edit ACL -errorEditingACL=Error editing access control list (ACL). -editAnACL=Edit an access control list (ACL) -createUserGroup=Create user group -removeUserGroup=Remove user group -youHaveToSelectAUserGroup=You have to select a user group. -doYouReallyWantToRemoveUserGroup=Do you really want to remove user group {0}? -couldNotDeleteUserGroup=Could not delete user group. -filterUserGroups=Filter user groups... -id=ID -displayName=Display Name -userGroups=User Groups -errorTryingToLoadRoles=Error trying to load roles: {0} -filterRoles=Filter roles -actions=Actions -actionEdit=Edit -actionRemove=Remove -actionChangeOwnership=Change ownership -errorUpdatingOwnership=Error updating ownership of object {0} -doYouReallyWantToRemoveRole=Do you really want to remove role(s) "{0}"? -errorTryingToDeleteRole=Error trying to delete role "{0}": {1} -pleaseEnterARoleName=Please enter a role name -editRoles=Edit Roles -otherRoleWithNameAlreadyExists=Another role with name "{0}" already exists. -errorCreatingRole=Error creating role "{0}": {1} -errorEditingRoles=Error editing role "{0}": {1} -usersInUserGroup=Users in user group -allUsers=All users -couldNotAddUserToUserGroup=Could not add user {0} to user group {1}: {2} -couldNotRemoveUserFromUserGroup=Could not remove user {0} from user group {1}: {2} -enterUserGroupName=Enter user group name -defaultTenant=User group to use for object creation -pagerStateInfo={0,number,#,###}-{1,number,#,###} of {2,number,#,###} -pagerStateInfo[false]={0,number,#,###}-{1,number,#,###} of over {2,number,#,###} -yes=Yes -no=No -editUser=Edit User -validated=Validated -groups=Groups -group=Group -doYouReallyWantToRemoveUser=Really remove user {0}? -errorTryingToUpdateUser=Error trying to update user {0}: {1} -ownership=Ownership -editObjectOwnership=Edit object ownership -errorObtainingUser=Error obtaining user: {0} -errorObtainingUserGroup=Error obtaining user group: {0} -pleaseWaitUntilUsernameIsResolved=Please wait until user name is resolved -pleaseWaitUntilUserGroupNameIsResolved=Please wait until user group name is resolved -userNotFound=User {0} not found -usergroupNotFound=User group {0} not found -actionChangeACL=Change ACL -editACLForObject=Edit ACL for ''{0}'' +editACL=编辑 ACL +errorEditingACL=编辑访问控制清单 (ACL) 出错。 +editAnACL=编辑访问控制清单 (ACL) +createUserGroup=创建用户组 +removeUserGroup=移除用户组 +youHaveToSelectAUserGroup=需要选择用户组。 +doYouReallyWantToRemoveUserGroup=是否确定移除用户组{0}? +couldNotDeleteUserGroup=无法删除用户组。 +filterUserGroups=筛选用户组... +id=编号 +displayName=显示名称 +userGroups=用户组 +errorTryingToLoadRoles=尝试加载角色出错:{0} +filterRoles=筛选角色 +actions=操作 +actionEdit=编辑 +actionRemove=移除 +actionChangeOwnership=更改所有权 +errorUpdatingOwnership=更新对象{0}的所有权出错 +doYouReallyWantToRemoveRole=是否确定移除角色 "{0}"? +errorTryingToDeleteRole=尝试删除角色 "{0}" 出错:{1} +pleaseEnterARoleName=请输入角色名称 +editRoles=编辑角色 +otherRoleWithNameAlreadyExists=名称为 "{0}" 的另一个角色已存在。 +errorCreatingRole=创建角色 "{0}" 出错:{1} +errorEditingRoles=编辑角色 "{0}" 出错:{1} +usersInUserGroup=用户组中的用户 +allUsers=所有用户 +couldNotAddUserToUserGroup=无法将用户{0}添加到用户组{1}:{2} +couldNotRemoveUserFromUserGroup=无法将用户{0}从用户组{1}中移除:{2} +enterUserGroupName=输入用户组名称 +defaultTenant=用户对象创建的用户组 +pagerStateInfo={0,number,#,###}-{1,number,#,###}/{2,number,#,###} +pagerStateInfo[false]={0,number,#,###}-{1,number,#,###}(超过{2,number,#,###}) +yes=是 +no=否 +editUser=编辑用户 +validated=已验证 +groups=组 +group=组 +errorDeletingUser=删除用户{0}时出错:{1} +doYouReallyWantToRemoveUser=是否确定移除用户{0}? +errorTryingToUpdateUser=尝试更新用户{0}时出错:{1} +ownership=所有权 +editObjectOwnership=编辑对象所有权 +errorObtainingUser=获取用户出错:{0} +errorObtainingUserGroup=获取用户组出错:{0} +pleaseWaitUntilUsernameIsResolved=请等待解析用户名称 +pleaseWaitUntilUserGroupNameIsResolved=请等待解析用户组名称 +userNotFound=未找到用户{0} +usergroupNotFound=未找到用户组{0} +actionChangeACL=更改 ACL +editACLForObject=为 ''{0}'' 编辑 ACL acl=ACL -errorUpdatingAcl=Error updating ACL: {0}. -allowedActionName=Allowed action name -deniedActionName=Denied action name -errorMessageUserGroupNameNotFound=Did not find user group by name {0}. -successMessageAddedUserGroup=Added user group {0}. -successMessageRemovedUserGroup=Removed user group {0}. -allowedActions=Allowed Actions -deniedActions=Denied Actions -migrateGroupOwner=Migrate group owner for hierarchy -errorDeletingUserGroup=Error deleting user group {0}: {1} -enterAtLeastOneOwner=Please enter at least one owner -removeRole=Remove role -addRole=Add role -couldNotAddRoleToGroup=Role {0} could not be added to group {1}. -roleName=Role Name -enabledForAllUsers=Enabled for all users -couldNotDeleteRole=Could not delete role {0}. -couldNotLoadRoles=Could not load roles. -enterUsername=Enter user name -addUser=Add user -couldNotLoadUsers=Could not load users. -couldNotRemoveRoleFromUser=Could not remove role ''{1}'' from user ''{0}''. -couldNotRemovePermissionFromUser=Could not remove permission ''{1}'' from user ''{0}''. -filterPermission=Filter permission -permission=Permission +errorUpdatingAcl=更新 ACL 出错:{0}。 +allowedActionName=允许的操作名称 +deniedActionName=拒绝的操作名称 +errorMessageUserGroupNameNotFound=按名称{0}未找到用户组。 +successMessageAddedUserGroup=已添加用户组{0}。 +successMessageRemovedUserGroup=已移除用户组{0}。 +allowedActions=允许的操作 +deniedActions=拒绝的操作 +migrateGroupOwner=为层次结构迁移组所有者 +errorDeletingUserGroup=删除用户组{0}出错:{1} +enterAtLeastOneOwner=请至少输入一个所有者 +removeRole=移除角色 +addRole=添加角色 +couldNotAddRoleToGroup=无法将角色{0}添加到组{1}。 +roleName=角色名称 +enabledForAllUsers=已为所有用户启用 +couldNotDeleteRole=无法删除角色{0}。 +couldNotLoadRoles=无法加载角色。 +enterUsername=输入用户名称 +addUser=添加用户 +couldNotLoadUsers=无法加载用户。 +couldNotRemoveRoleFromUser=无法将角色 ''{1}'' 从用户 ''{0}'' 中移除。 +couldNotRemovePermissionFromUser=无法将权限 ''{1}'' 从用户 ''{0}'' 中移除。 +filterPermission=筛选权限 +permission=权限 nullUserGroup= -editRolesAndPermissionsForUser=Edit roles and permissions for user {0} -permissionType:Permission Type +editRolesAndPermissionsForUser=为用户{0}编辑角色和权限 +permissionType:权限类型 From 5a310dab3a3224d6ca5be022a13001d474ffe780 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Thu, 16 May 2019 20:20:18 +0200 Subject: [PATCH 03/24] fixed path for datamining translation collection# Change-Id: I41874c774e215f6371c3e0b3a4828c9e1e258f44 --- translation_v2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translation_v2.json b/translation_v2.json index 1f487c83550..f23a0d77c1f 100644 --- a/translation_v2.json +++ b/translation_v2.json @@ -22,7 +22,7 @@ "collectionName":"DataMiningGwtUi", "folders":[ { - "startingFolderPath":"java/com.sap.sse.datamining.ui/java/com/sap/sse/datamining/ui/client", + "startingFolderPath":"java/com.sap.sse.datamining.ui/src/com/sap/sse/datamining/ui/client", "sourceFilters":[ "**/StringMessages*.properties" ], From f7e776f48e6a96ab053be00f587e070068ad3f94 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 17 May 2019 11:17:35 +0200 Subject: [PATCH 04/24] fixed datamining translation source folder location for good Change-Id: I49e7961a17ba9c98d7291ead77c30fdd6198a6e1 --- translation_v2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translation_v2.json b/translation_v2.json index 1f487c83550..406daf32288 100644 --- a/translation_v2.json +++ b/translation_v2.json @@ -22,7 +22,7 @@ "collectionName":"DataMiningGwtUi", "folders":[ { - "startingFolderPath":"java/com.sap.sse.datamining.ui/java/com/sap/sse/datamining/ui/client", + "startingFolderPath":"java/com.sap.sse.datamining.ui/src/main/java/com/sap/sse/datamining/ui/client", "sourceFilters":[ "**/StringMessages*.properties" ], From f273a3cce830c08e16c1b5fd0d60db8fb15bbc13 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 17 May 2019 12:30:41 +0200 Subject: [PATCH 05/24] adjusted onboarding document to include hints for ignoring uncritical warnings/errors in Eclipse Change-Id: If7508fec3ef6be4b18ec2d30770e7831b520d252 --- wiki/howto/ignore-missing-gwt-sdk-warning.png | Bin 0 -> 46685 bytes .../ignore-package-not-exported-warning.png | Bin 0 -> 64467 bytes wiki/howto/onboarding.md | 16 ++++++++++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100755 wiki/howto/ignore-missing-gwt-sdk-warning.png create mode 100755 wiki/howto/ignore-package-not-exported-warning.png diff --git a/wiki/howto/ignore-missing-gwt-sdk-warning.png b/wiki/howto/ignore-missing-gwt-sdk-warning.png new file mode 100755 index 0000000000000000000000000000000000000000..a7ffdcd56ec15c331fb6b8c62a0427804875d879 GIT binary patch literal 46685 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

4exD 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 NcGS;{eE7`O{{vt~lGgwL literal 0 HcmV?d00001 diff --git a/wiki/howto/ignore-package-not-exported-warning.png b/wiki/howto/ignore-package-not-exported-warning.png new file mode 100755 index 0000000000000000000000000000000000000000..5de05de1b3d3f568390d12ad896c6b72f97a12af GIT binary patch literal 64467 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 a^8W#S`+glS5D&Eg0000` From 40e35db29a65c92d87f119680bd442a7bf50f2fc Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 17 May 2019 15:01:43 +0200 Subject: [PATCH 06/24] fixed assertion performance in MPC Change-Id: Id11ea5e508d79c511cca51d2273ba4254a40d5d9 --- .../impl/StationarySequenceBasedFilter.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java index b51a8a00627..e2b94245c2b 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequenceBasedFilter.java @@ -235,10 +235,11 @@ public class StationarySequenceBasedFilter { */ private void addCandidate(Candidate newCandidate, Set candidatesEffectivelyAdded, Set candidatesEffectivelyRemoved) { - if (candidates.contains(newCandidate)) { + boolean candidatesContainsNewCandidate = candidates.contains(newCandidate); + if (candidatesContainsNewCandidate) { logger.severe("Candidates "+candidates+" already contain "+newCandidate+" which is to be added."); } - final boolean assertion = !candidates.contains(newCandidate) && isCandidatesConsistent(); + final boolean assertion = !candidatesContainsNewCandidate && isCandidatesConsistent(); assert assertion; if (newCandidate == startProxyCandidate) { candidatesEffectivelyAdded.add(newCandidate); @@ -539,13 +540,10 @@ public class StationarySequenceBasedFilter { } } if (fixesReplacingExistingOnes != null) { + Set newFixesHashedForQuickAssertionCheck = new HashSet<>(); + assert Util.addAll(newFixes, newFixesHashedForQuickAssertionCheck) != null; for (final GPSFixMoving fixReplacingExistingOne : fixesReplacingExistingOnes) { - final boolean assertion2 = Util.contains(newFixes, fixReplacingExistingOne); - if (!assertion2) { - logger.severe("Expected "+fixesReplacingExistingOnes+" to be part of the "+Util.size(newFixes)+ - " new fixes, but it wasn't"); - } - assert assertion2; + assert newFixesHashedForQuickAssertionCheck.contains(fixReplacingExistingOne); final Candidate dummyCandidateForReplacementFix = StationarySequence.createDummyCandidate(fixReplacingExistingOne.getTimePoint()); final StationarySequence dummyStationarySequenceForFix = createStationarySequence(dummyCandidateForReplacementFix); final StationarySequence lastSequenceStartingAtOrBeforeFix = stationarySequences.floor(dummyStationarySequenceForFix); From 3fd6d7e444e08dae340757ecf0398bb2ec077a22 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Fri, 17 May 2019 18:18:37 +0200 Subject: [PATCH 07/24] made installPluginsForEclipse2019-03.sh executable --- configuration/installPluginsForEclipse2019-03.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 configuration/installPluginsForEclipse2019-03.sh diff --git a/configuration/installPluginsForEclipse2019-03.sh b/configuration/installPluginsForEclipse2019-03.sh old mode 100644 new mode 100755 From 1e7588876dfa03e1e5431e1253a12bfca89de86d Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 11:41:45 +0200 Subject: [PATCH 08/24] bug 5043: using raw fixes for bounding box construction in StationarySequence; this makes it consistent with the fact that also raw fixes are being forwarded currently through the event handler and the MPC queue --- .../sap/sailing/domain/tracking/Track.java | 11 +++ .../domain/tracking/impl/TrackImpl.java | 38 ++++++---- .../MarkPassingCalculator.java | 10 ++- .../impl/StationarySequence.java | 2 +- .../domain/tracking/impl/TrackedRaceImpl.java | 6 +- .../SailingServer (No Proxy).launch | 74 +++++++++---------- 6 files changed, 83 insertions(+), 58 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java index 21f32616e4c..75010f03c8d 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java @@ -201,6 +201,17 @@ public interface Track extends Serializable { */ Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); + /** + * Returns an iterator starting at the first raw fix after startingAt (or "at or after" in case + * startingAtInclusive is true) and ending at the endingAt time point or just before + * in case endingAtIncluive is false. The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive); + /** * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see 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 4cc8b21766c..d0d9cf9b890 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 @@ -335,25 +335,14 @@ public class TrackImpl implements Track { @Override public Iterator getFixesIterator(TimePoint startingAt, boolean inclusive) { assertReadLock(); - Iterator result = (Iterator) getInternalFixes().tailSet( - getDummyFix(startingAt), inclusive).iterator(); - return result; + return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false); } @Override public Iterator getFixesIterator(TimePoint startingAt, boolean startingAtInclusive, TimePoint endingAt, boolean endingAtInclusive) { assertReadLock(); - NavigableSet set = getInternalFixes(); - if (startingAt != null && endingAt != null) { - set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive); - } else if (endingAt != null) { - set = set.headSet(getDummyFix(endingAt), endingAtInclusive); - } else if (startingAt != null) { - set = set.tailSet(getDummyFix(startingAt), startingAtInclusive); - } - Iterator result = set.iterator(); - return result; + return getTimeConstrainedFixesIterator(getInternalFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive); } @Override @@ -380,10 +369,29 @@ public class TrackImpl implements Track { @Override public Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive) { assertReadLock(); - Iterator result = (Iterator) getInternalRawFixes().tailSet( - getDummyFix(startingAt), inclusive).iterator(); + return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, inclusive, /* endingAt */ null, /* endingAtInclusive */ false); + } + + private Iterator getTimeConstrainedFixesIterator(NavigableSet set, TimePoint startingAt, boolean startingAtInclusive, + TimePoint endingAt, boolean endingAtInclusive) { + assertReadLock(); + if (startingAt != null && endingAt != null) { + set = set.subSet(getDummyFix(startingAt), startingAtInclusive, getDummyFix(endingAt), endingAtInclusive); + } else if (endingAt != null) { + set = set.headSet(getDummyFix(endingAt), endingAtInclusive); + } else if (startingAt != null) { + set = set.tailSet(getDummyFix(startingAt), startingAtInclusive); + } + Iterator result = set.iterator(); return result; } + + @Override + public Iterator getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, + TimePoint endingAt, boolean endingAtInclusive) { + assertReadLock(); + return getTimeConstrainedFixesIterator(getInternalRawFixes(), startingAt, startingAtInclusive, endingAt, endingAtInclusive); + } @Override public Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 9207ebfdd46..16c5d08278e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.markpassingcalculation; +import java.lang.Thread.State; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -507,8 +508,13 @@ public class MarkPassingCalculator { queue.add(update); // regardless of whether the queue is empty or not, launch the thread if it doesn't run yet and we are not suspended; // the queue may have filled up while we were suspended - if (!suspended && listenerThread == null) { - listenerThread = createAndStartListenerThread(); + if (!suspended) { + if (listenerThread == null) { + listenerThread = createAndStartListenerThread(); + } else if (listenerThread.getState() == State.TERMINATED) { + logger.severe("Listener thread of MarkPassingCalculator (MPC) for race "+race.getRace().getName()+ + " terminated but not null. Why are we still receiving updates? We must have been stopped before!"); + } } } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java index 4f8808edcbc..50e87a6c0b0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java @@ -108,7 +108,7 @@ public class StationarySequence { if (bounds != null) { track.lockForRead(); try { - final Iterator iter = track.getFixesIterator(start.getTimePoint(), /* inclusive */ true, + final Iterator iter = track.getRawFixesIterator(start.getTimePoint(), /* inclusive */ true, end.getTimePoint(), /* inclusive */ true); while (iter.hasNext()) { final GPSFixMoving fix = iter.next(); 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 d8297647876..239fa7dfc9e 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 @@ -557,9 +557,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public void raceRemoved(TrackedRace trackedRace) { if (trackedRace == TrackedRaceImpl.this) { - // stop mark passing calculator when tracked race is removed: - markPassingCalculator.stop(); - } + // stop mark passing calculator when tracked race is removed: + markPassingCalculator.stop(); + } } }, /* Not relevant For replication */ Optional.empty(), /* synchronous */ false); } else { diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 183250fecdf..38159b06b76 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -1,40 +1,40 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8109d94ddf4433fbae2cdf23c3754331bdda060e Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 12:00:54 +0200 Subject: [PATCH 09/24] fixed compilation error after introducing new method on TrackImpl --- .../abstractlog/race/impl/NoAddingRaceLogWrapper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java index c6ade560840..0fb879831f5 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java @@ -159,6 +159,12 @@ public class NoAddingRaceLogWrapper implements RaceLog { return innerRaceLog.getRawFixesIterator(startingAt, inclusive); } + @Override + public Iterator getRawFixesIterator(TimePoint startingAt, boolean startingAtInclusive, + TimePoint endingAt, boolean endingAtInclusive) { + return innerRaceLog.getRawFixesIterator(startingAt, startingAtInclusive, endingAt, endingAtInclusive); + } + @Override public Serializable getId() { return innerRaceLog; From 9ec982c19f901466ade29eeaabd77d8a5afa8bd5 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 18:47:30 +0200 Subject: [PATCH 10/24] bug5043: avoid filtering out candidates for fixed mark passings in StationarySequence --- ...eCandidatesInSmallTimeRangeFilterTest.java | 14 ++++----- .../markpassingcalculation/Candidate.java | 7 +++++ .../impl/CandidateChooserImpl.java | 2 +- .../CandidateForFixedMarkPassingImpl.java | 17 +++++++++++ ...bableCandidatesInSmallTimeRangeFilter.java | 6 ++-- .../impl/StationarySequence.java | 29 ++++++++++++++----- 6 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateForFixedMarkPassingImpl.java diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilterTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilterTest.java index 9753bb6abdd..ea52e1ccb0a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilterTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilterTest.java @@ -19,26 +19,26 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest extends AbstractCa @Test public void testGetTimeWiseContiguousDistanceCandidates() { - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c3, /* includeStartFrom */ true); + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, c3, /* includeStartFrom */ true); assertContainsExactly(result, c1, c2, c3, c4, c5); } @Test public void testGetTimeWiseContiguousDistanceCandidatesOnRightBorder() { - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c5, /* includeStartFrom */ true); + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, c5, /* includeStartFrom */ true); assertContainsExactly(result, c1, c2, c3, c4, c5); } @Test public void testGetTimeWiseContiguousDistanceCandidatesOnLeftBorder() { - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, c6, /* includeStartFrom */ true); + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, c6, /* includeStartFrom */ true); assertContainsExactly(result, c6, c7, c8, c9, c10); } @Test public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapNotInserted() { final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap"); - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap, + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, middleOfGap, /* includeStartFrom */ true); assertContainsExactly(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10); } @@ -46,7 +46,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest extends AbstractCa @Test public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapNotInsertedDontIncludeStartFrom() { final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap"); - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap, + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, middleOfGap, /* includeStartFrom */ false); assertContainsExactly(result, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10); } @@ -55,7 +55,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest extends AbstractCa public void testGetTimeWiseContiguousDistanceCandidatesInMiddleOfGapInserted() { final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap"); competitorCandidates.add(middleOfGap); - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap, + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, middleOfGap, /* includeStartFrom */ true); assertContainsExactly(result, c1, c2, c3, c4, c5, middleOfGap, c6, c7, c8, c9, c10); } @@ -65,7 +65,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilterTest extends AbstractCa competitorCandidates.remove(c5); competitorCandidates.remove(c6); final Candidate middleOfGap = candidate(c5.getTimePoint().plus(c5.getTimePoint().until(c6.getTimePoint()).divide(2)), "gap"); - final NavigableSet result = filter.getTimeWiseContiguousDistanceCandidates(competitorCandidates, middleOfGap, + final NavigableSet result = filter.getTimeWiseContiguousCandidates(competitorCandidates, middleOfGap, /* includeStartFrom */ true); assertContainsExactly(result, middleOfGap); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java index b3e60dec544..a3f12426eb4 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java @@ -20,6 +20,13 @@ import com.sap.sse.common.Timed; public interface Candidate extends Comparable, Timed { Waypoint getWaypoint(); + + /** + * Is this a candidate for a fixed mark passing? + */ + default boolean isFixed() { + return false; + } int getOneBasedIndexOfWaypoint(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java index bf771b3f642..8fd60738224 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -474,7 +474,7 @@ public class CandidateChooserImpl implements CandidateChooser { public void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t) { LockUtil.lockForWrite(perCompetitorLocks.get(c)); try { - Candidate fixedCan = new CandidateImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint)); + Candidate fixedCan = new CandidateForFixedMarkPassingImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint)); NavigableSet fixed = fixedPassings.get(c); if (fixed != null) { // can only set the mark passing if the competitor is still part of this race if (!fixed.add(fixedCan)) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateForFixedMarkPassingImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateForFixedMarkPassingImpl.java new file mode 100644 index 00000000000..7c1f9b3d268 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateForFixedMarkPassingImpl.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.markpassingcalculation.impl; + +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sse.common.TimePoint; + +public class CandidateForFixedMarkPassingImpl extends CandidateImpl { + private static final long serialVersionUID = 3657502693032556232L; + + public CandidateForFixedMarkPassingImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w) { + super(oneBasedIndexOfWaypoint, p, probability, w); + } + + @Override + public boolean isFixed() { + return true; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilter.java index 1e37227931e..10a450605bb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilter.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/MostProbableCandidatesInSmallTimeRangeFilter.java @@ -102,7 +102,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter { while (!newCandidatesModifiableCopy.isEmpty()) { final Candidate nextNewCandidate = newCandidatesModifiableCopy.iterator().next(); newCandidatesModifiableCopy.remove(nextNewCandidate); - final NavigableSet contiguousSequenceForNextNewCandidate = getTimeWiseContiguousDistanceCandidates(competitorCandidates, nextNewCandidate, /* includeStartFrom */ true); + final NavigableSet contiguousSequenceForNextNewCandidate = getTimeWiseContiguousCandidates(competitorCandidates, nextNewCandidate, /* includeStartFrom */ true); disjointSequencesAffectedByNewAndRemovedCandidates.add(contiguousSequenceForNextNewCandidate); // remove the candidate grouped in the sequence from the remaining new candidates to consider // because they have been "scooped up" by this sequence already and need no further consideration: @@ -122,7 +122,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter { while (!removedCandidatesModifiableCopy.isEmpty()) { final Candidate nextRemovedCandidate = removedCandidatesModifiableCopy.iterator().next(); removedCandidatesModifiableCopy.remove(nextRemovedCandidate); - final NavigableSet contiguousSequenceForNextRemovedCandidate = getTimeWiseContiguousDistanceCandidates(competitorCandidates, nextRemovedCandidate, /* includeStartFrom */ false); + final NavigableSet contiguousSequenceForNextRemovedCandidate = getTimeWiseContiguousCandidates(competitorCandidates, nextRemovedCandidate, /* includeStartFrom */ false); removeAllRemovedCandidatesInOrNearSequence(contiguousSequenceForNextRemovedCandidate, removedCandidatesModifiableCopy); // If adjacent sequences were added before and after the removed candidate, // we need to distinguish whether the gap around the removed candidate was greater @@ -233,7 +233,7 @@ public class MostProbableCandidatesInSmallTimeRangeFilter { * @param includeStartFrom * whether or not to include {@code startFrom} in the resulting set */ - public NavigableSet getTimeWiseContiguousDistanceCandidates(NavigableSet competitorCandidates, Candidate startFrom, boolean includeStartFrom) { + public NavigableSet getTimeWiseContiguousCandidates(NavigableSet competitorCandidates, Candidate startFrom, boolean includeStartFrom) { final NavigableSet result = new TreeSet<>(candidateComparator); if (includeStartFrom) { result.add(startFrom); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java index 50e87a6c0b0..f479930b149 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/StationarySequence.java @@ -89,7 +89,7 @@ public class StationarySequence { // borders of this sequence: final TimeRange timeRangeNoLongerPassingFilter = getExtendedEndBorderRange(extendedBy); if (timeRangeNoLongerPassingFilter != null) { - removeFromFilterResult(timeRangeNoLongerPassingFilter, candidatesEffectivelyAdded, candidatesEffectivelyRemoved); + removeFromFilterResultUnlessFixed(timeRangeNoLongerPassingFilter, candidatesEffectivelyAdded, candidatesEffectivelyRemoved); } } return bounds != null; @@ -158,7 +158,7 @@ public class StationarySequence { // borders of this sequence: final TimeRange timeRangeNoLongerPassingFilter = getExtendedStartBorderRange(extendedBy); if (timeRangeNoLongerPassingFilter != null) { - removeFromFilterResult(timeRangeNoLongerPassingFilter, candidatesEffectivelyAdded, candidatesEffectivelyRemoved); + removeFromFilterResultUnlessFixed(timeRangeNoLongerPassingFilter, candidatesEffectivelyAdded, candidatesEffectivelyRemoved); } } return bounds != null; @@ -204,11 +204,15 @@ public class StationarySequence { return result; } - private void removeFromFilterResult(final TimeRange timeRangeNoLongerPassingFilter, Set candidatesEffectivelyAdded, + private void removeFromFilterResultUnlessFixed(final TimeRange timeRangeNoLongerPassingFilter, Set candidatesEffectivelyAdded, Set candidatesEffectivelyRemoved) { final SortedSet candidatesNoLongerPassingFilter = getCandidatesInTimeRange(timeRangeNoLongerPassingFilter); - candidatesEffectivelyAdded.removeAll(candidatesNoLongerPassingFilter); - candidatesEffectivelyRemoved.addAll(candidatesNoLongerPassingFilter); + for (final Candidate candidate : candidatesNoLongerPassingFilter) { + if (!candidate.isFixed()) { + candidatesEffectivelyAdded.removeAll(candidatesNoLongerPassingFilter); + candidatesEffectivelyRemoved.addAll(candidatesNoLongerPassingFilter); + } + } } private SortedSet getCandidatesInTimeRange(final TimeRange timeRange) { @@ -219,7 +223,16 @@ public class StationarySequence { } public Iterable getValidCandidates() { - return ()->candidates.stream().filter(c->isCloseEnoughToSequenceBorder(c)).iterator(); + return ()->candidates.stream().filter(c->isValidCandidate(c)).iterator(); + } + + /** + * A candidate that is part of this stationary sequence is considered valid if it is time-wise close + * enough to one of the sequence's borders (see {@link #CANDIDATE_FILTER_TIME_WINDOW}) or if it is + * a candidate for a {@link Candidate#isFixed() fixed mark passing}. + */ + private boolean isValidCandidate(Candidate c) { + return c.isFixed() || isCloseEnoughToSequenceBorder(c); } private boolean isCloseEnoughToSequenceBorder(Candidate candidate) { @@ -246,7 +259,7 @@ public class StationarySequence { assert !candidates.contains(candidate) && !getFirst().getTimePoint().after(candidate.getTimePoint()) && !getLast().getTimePoint().before(candidate.getTimePoint()); candidates.add(candidate); - if (isCloseEnoughToSequenceBorder(candidate)) { + if (isValidCandidate(candidate)) { candidatesEffectivelyAdded.add(candidate); candidatesEffectivelyRemoved.remove(candidate); } @@ -269,7 +282,7 @@ public class StationarySequence { void remove(Candidate candidate, Set candidatesEffectivelyAdded, Set candidatesEffectivelyRemoved, NavigableSet stationarySequenceSetToUpdate) { assert candidates.contains(candidate); - final boolean wasValidCandidate = isCloseEnoughToSequenceBorder(candidate); + final boolean wasValidCandidate = isValidCandidate(candidate); final boolean wasFirst = candidate == getFirst(); final boolean wasLast = candidate == getLast(); if (wasFirst || size() == 2) { // if size() == 2 then it will shrink to 1 and this sequence shall be removed From d1266b24ab2ea0464947bc8a6d5741aa0419d752 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 21:40:35 +0200 Subject: [PATCH 11/24] bug5044: rather create an empty TimeRange instead of provoking exception; furthermore, I've added logging for uncaught GWT RPC exceptions --- .../analyzing/impl/RegattaLogDeviceMappingFinder.java | 3 ++- .../gwt/server/DelegatingProxiedRemoteServiceServlet.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java index 983684147c1..cdbcfe09f7a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java @@ -49,7 +49,8 @@ public class RegattaLogDeviceMappingFinder protected DeviceMappingWithRegattaLogEvent createMapping(DeviceIdentifier device, ItemT item, TimePoint from, TimePoint toInclusive, Serializable originalEventId, RegattaLogDeviceMappingEvent event) { - return new DeviceMappingWithRegattaLogEventImpl(item, device, new TimeRangeImpl(from, toInclusive, /* inclusive */ true), + return new DeviceMappingWithRegattaLogEventImpl(item, device, new TimeRangeImpl(from, + toInclusive.before(from) ? from : toInclusive, /* inclusive */ true), Collections.singletonList(originalEventId), event); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/server/DelegatingProxiedRemoteServiceServlet.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/server/DelegatingProxiedRemoteServiceServlet.java index 662eabba162..358c62965a7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/server/DelegatingProxiedRemoteServiceServlet.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/server/DelegatingProxiedRemoteServiceServlet.java @@ -3,6 +3,8 @@ package com.sap.sse.gwt.server; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; import com.google.gwt.user.client.rpc.RpcTokenException; @@ -14,6 +16,8 @@ import com.google.gwt.user.server.rpc.SerializationPolicy; import com.sap.sse.common.TimePoint; public abstract class DelegatingProxiedRemoteServiceServlet extends ProxiedRemoteServiceServlet { + private static final Logger logger = Logger.getLogger(DelegatingProxiedRemoteServiceServlet.class.getName()) + ; private static final long serialVersionUID = -5543378343472849437L; /** @@ -73,6 +77,7 @@ public abstract class DelegatingProxiedRemoteServiceServlet extends ProxiedRemot } catch (InvocationTargetException e) { // Try to encode the caught exception Throwable cause = e.getCause(); + logger.log(Level.SEVERE, "Uncaught exception, forwarded to client", cause); responsePayload = RPC.encodeResponseForFailure(serviceMethod, cause, serializationPolicy, flags); } return responsePayload; From 6ff0641cab0fa1f63ca89107cd6358e47e33b176 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 21:49:54 +0200 Subject: [PATCH 12/24] fixed launch config, removing replicaSet Mongo settings again --- java/com.sap.sailing.server/SailingServer (No Proxy).launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 38159b06b76..7093aba42f1 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -22,7 +22,7 @@ - + From c94c093058ee3492d8819e797dc40673201854b0 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sat, 18 May 2019 23:01:10 +0200 Subject: [PATCH 13/24] post-fix for bug5044, avoiding NPE in case of open-ended interval --- .../tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java index cdbcfe09f7a..4a7b9da2d40 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java @@ -50,7 +50,7 @@ public class RegattaLogDeviceMappingFinder protected DeviceMappingWithRegattaLogEvent createMapping(DeviceIdentifier device, ItemT item, TimePoint from, TimePoint toInclusive, Serializable originalEventId, RegattaLogDeviceMappingEvent event) { return new DeviceMappingWithRegattaLogEventImpl(item, device, new TimeRangeImpl(from, - toInclusive.before(from) ? from : toInclusive, /* inclusive */ true), + toInclusive != null && toInclusive.before(from) ? from : toInclusive, /* inclusive */ true), Collections.singletonList(originalEventId), event); } From 894efc5bb4dc496d27c0bd39f3070f04a46951d9 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Sun, 19 May 2019 01:54:14 +0200 Subject: [PATCH 14/24] in case one side of a line doesn't have a position, guess the orientation of "BOAT"-type marks based on position of next mark --- .../sailing/gwt/ui/server/SailingServiceImpl.java | 1 - .../ui/shared/racemap/BoatMarkVectorGraphics.java | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) 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 25065fa252e..a4ee5565d2f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -2382,7 +2382,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet public CoursePositionsDTO getCoursePositions(RegattaAndRaceIdentifier raceIdentifier, Date date) { CoursePositionsDTO result = new CoursePositionsDTO(); TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier); - getSecurityService().checkCurrentUserReadPermission(trackedRace); if (trackedRace != null) { getSecurityService().checkCurrentUserReadPermission(trackedRace); final TimePoint dateAsTimePoint; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/BoatMarkVectorGraphics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/BoatMarkVectorGraphics.java index 8068496973a..ff8fc2174b0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/BoatMarkVectorGraphics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/BoatMarkVectorGraphics.java @@ -196,10 +196,17 @@ public class BoatMarkVectorGraphics extends AbstractMarkVectorGraphics { private Bearing bearingOrReverseDependingOnXTEToOther(Position pos, Bearing bearing, Position other) { final Bearing result; - if (pos.crossTrackError(other, bearing).compareTo(Distance.NULL) > 0) { - result = bearing.reverse(); + if (bearing == null) { + // no start line bearing is known; assume that the start boat is on the + // starboard side of the line and that the line is approximately + // perpendicular to the bearing to the next mark: + result = pos.getBearingGreatCircle(other).add(new DegreeBearingImpl(-90)); } else { - result = bearing; + if (pos.crossTrackError(other, bearing).compareTo(Distance.NULL) > 0) { + result = bearing.reverse(); + } else { + result = bearing; + } } return result; } From 213109a42e85ed8af08dc493f8f4fd9b3a90ea47 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Mon, 20 May 2019 14:41:12 +0200 Subject: [PATCH 15/24] added property rankingMetric to the RegattaJsonSerializer Change-Id: I50044904e73e5566292a9a2362ec56f6baee5f4f --- .../gateway/serialization/impl/RegattaJsonSerializer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java index a1fca0bd109..c8e9358fced 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/RegattaJsonSerializer.java @@ -17,6 +17,7 @@ public class RegattaJsonSerializer implements JsonSerializer { public static final String FIELD_START_DATE = "startDate"; public static final String FIELD_END_DATE = "endDate"; public static final String FIELD_SCORINGSYSTEM = "scoringSystem"; + public static final String FIELD_RANKINGMETRIC = "rankingMetric"; public static final String FIELD_SERIES = "series"; public static final String FIELD_COMPETITORS = "competitors"; public static final String FIELD_BOATS = "boats"; @@ -49,6 +50,7 @@ public class RegattaJsonSerializer implements JsonSerializer { result.put(FIELD_START_DATE, regatta.getStartDate() != null ? regatta.getStartDate().asMillis() : null); result.put(FIELD_END_DATE, regatta.getEndDate() != null ? regatta.getEndDate().asMillis() : null); result.put(FIELD_SCORINGSYSTEM, regatta.getScoringScheme().getType().name()); + result.put(FIELD_RANKINGMETRIC, regatta.getRankingMetricType().name()); result.put(FIELD_BOATCLASS, regatta.getBoatClass() != null ? regatta.getBoatClass().getName() : null); result.put(FIELD_COURSE_AREA_ID, regatta.getDefaultCourseArea() != null ? regatta.getDefaultCourseArea().getId().toString() : null); From 0e9bf6cf1a96c376a5102d67fe0a13d14ee31409 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 12:23:07 +0200 Subject: [PATCH 16/24] bug4717: added failing test cases Change-Id: I4712236aafaaa83a764201e63a4ae1bbd4988ad6 --- .../LeaderboardScoringAndRankingTest.java | 127 ++++++++++++++++++ .../domain/leaderboard/ScoringScheme.java | 10 +- .../impl/AbstractScoringSchemeImpl.java | 14 +- 3 files changed, 146 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index e9dd2a19c63..25ee9222672 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1150,6 +1150,133 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki assertTrue(rankedCompetitors.indexOf(c[0]) == rankedCompetitors.indexOf(c[1])-1); } + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a + * leaderboard with a qualification and a final series such that the competitors have equal points but different + * number of wins in the final series, leading to a different order than it would if sorting by the total number + * of wins. + */ + @Test + public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal() throws NoWindException { + Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] wins no race in the qualification, c[1] wins two, + Competitor[] q2 = new Competitor[] { c[1], c[0], c[2] }; // but that doesn't help c[1] because c[0] wins one in the final + Competitor[] f1 = new Competitor[] { c[2], c[0], c[1] }; // where c[1] wins none. The tie is broken in favor of c[0]. + Competitor[] f2 = new Competitor[] { c[0], c[1], c[2] }; + Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); + TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); + assertTrue(leaderboard.getNetPoints(c[0], later) < leaderboard.getNetPoints(c[2], later)); + assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); + } + + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a + * leaderboard with a qualification and a final series such that the competitors have equal points but different + * number of wins in the final series, leading to a different order than it would if sorting by the total number + * of wins. + */ + @Test + public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDecisionByLastRace() throws NoWindException { + Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have equal wins and scores, + Competitor[] q2 = new Competitor[] { c[0], c[1], c[2] }; // but c[1] has won the last race + Competitor[] f1 = new Competitor[] { c[0], c[1], c[2] }; + Competitor[] f2 = new Competitor[] { c[1], c[0], c[2] }; + Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); + TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); // based on last race f2 + } + + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a + * leaderboard with a qualification and a final series such that the competitors have equal points, and equal + * scores in the final series, but one of them having more wins than the other in the qualification. In this + * case, the qualification counts. + */ + @Test + public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDefaultingToQualification() throws NoWindException { + Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have an equal number of wins in the final series and equal total points; + Competitor[] q2 = new Competitor[] { c[2], c[0], c[1] }; // tie breaking is expected to then take the wins in the qualification series into account + Competitor[] f1 = new Competitor[] { c[0], c[1], c[2] }; // where c[1] has one more than c[0] + Competitor[] f2 = new Competitor[] { c[1], c[0], c[2] }; // tie breaking will have to resort to the qualification, finding the win of c[1] + Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDefaultingToQualification", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); + TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[1]) < rankedCompetitors.indexOf(c[0])); + } + + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a + * leaderboard with a qualification and a final series as well as a medal race such that the competitors have equal + * points but different numbers of wins in the final series + */ + @Test + public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal() throws NoWindException { + Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] wins no race in the qualification, c[1] wins two, + Competitor[] q2 = new Competitor[] { c[1], c[0], c[2] }; // but that doesn't help c[1] because c[0] wins one in the final + Competitor[] f1 = new Competitor[] { c[2], c[0], c[1] }; // where c[1] wins none. The tie is broken in favor of c[0]. + Competitor[] f2 = new Competitor[] { c[0], c[1], c[2] }; + Competitor[] m = new Competitor[] { c[0], c[1] }; // c[0] and c[1] got promoted to the medal race, both get disqualified; but in f2 c[2] won, so has to win overall due to count-back + Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ true, /* medal */ 1, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); + createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + TimePoint later = createAndAttachTrackedRaces(series.get(2), "Medal", /* withScores */ true, m); + leaderboard.getScoreCorrection().setMaxPointsReason(c[0], series.get(2).getRaceColumns().iterator().next(), MaxPointsReason.DSQ); + leaderboard.getScoreCorrection().setMaxPointsReason(c[1], series.get(2).getRaceColumns().iterator().next(), MaxPointsReason.DSQ); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); // due to more wins in final series + } + + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a + * leaderboard with a qualification and a final series as well as a medal race such that the competitors have equal + * points but different numbers of wins in the final series + */ + @Test + public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedalDecisionByLastRace() throws NoWindException { + Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have equal wins and scores, + Competitor[] q2 = new Competitor[] { c[0], c[1], c[2] }; // but c[1] has won the last race before the medal + Competitor[] f1 = new Competitor[] { c[0], c[1], c[2] }; + Competitor[] f2 = new Competitor[] { c[1], c[0], c[2] }; + Competitor[] m = new Competitor[] { c[0], c[1] }; // c[0] and c[1] got promoted to the medal race, both get disqualified; but in f2 c[2] won, so has to win overall due to count-back + Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ true, /* medal */ 1, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); + createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + TimePoint later = createAndAttachTrackedRaces(series.get(2), "Medal", /* withScores */ true, m); + leaderboard.getScoreCorrection().setMaxPointsReason(c[0], series.get(2).getRaceColumns().iterator().next(), MaxPointsReason.DSQ); + leaderboard.getScoreCorrection().setMaxPointsReason(c[1], series.get(2).getRaceColumns().iterator().next(), MaxPointsReason.DSQ); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[1]) < rankedCompetitors.indexOf(c[0])); // due to last non-medal race + } + /** * Reported by Clemens Fackeldey:

* 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 8964a073e76..583bc74487a 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 @@ -103,9 +103,13 @@ public interface ScoringScheme extends Serializable { ScoringSchemeType getType(); /** - * Usually, when all other sorting criteria end up in a tie, the last race sailed is used to decide. - * @param o1 TODO - * @param o2 TODO + * Usually, when all other sorting criteria end up in a tie, the last race sailed is used to decide, and from there + * backwards. This implements Racing Rules of Sailing (RRS) rule A8.2: + *

+ * + * "A8.2 If a tie remains between two or more boats, they shall be ranked in order of their scores in the last + * race. Any remaining ties shall be broken by using the tied boats’ scores in the next-to-last race and so on until + * all ties are broken. These scores shall be used even if some of them are excluded scores." */ int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter, Competitor o1, Competitor o2); 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 cf443cfb28c..8b913934df1 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 @@ -161,8 +161,18 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { } /** - * @param o1ScoresIncludingDiscarded scores that include the points for those races that have been discarded (total points) - * @param o2ScoresIncludingDiscarded scores that include the points for those races that have been discarded (total points) + * Looks backwards starting at the last race until the first score difference is found, including the discarded + * scored. This implements Racing Rules of Sailing (RRS) section A8.2: + *

+ * + * "A8.2 If a tie remains between two or more boats, they shall be ranked in order of their scores in the last race. + * Any remaining ties shall be broken by using the tied boats’ scores in the next-to-last race and so on until all + * ties are broken. These scores shall be used even if some of them are excluded scores." + * + * @param o1ScoresIncludingDiscarded + * scores that include the points for those races that have been discarded (total points) + * @param o2ScoresIncludingDiscarded + * scores that include the points for those races that have been discarded (total points) */ @Override public int compareByLastRace(List> o1ScoresIncludingDiscarded, From 5768265da716e60d57736aca42c817b3e23f2ef1 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 16:35:51 +0200 Subject: [PATCH 17/24] bug4717: added more failing test cases, also for corner cases Change-Id: Idf35c38fadc494f4a6b9953a45534bbcf695b316 --- .../LeaderboardScoringAndRankingTest.java | 90 ++++++++++++++++--- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index 25ee9222672..e22dfe754e2 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1157,7 +1157,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki * of wins. */ @Test - public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal() throws NoWindException { + public void testTieBreakBasedOnLastNonMedalSeriesNoMedal() throws NoWindException { Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] wins no race in the qualification, c[1] wins two, Competitor[] q2 = new Competitor[] { c[1], c[0], c[2] }; // but that doesn't help c[1] because c[0] wins one in the final @@ -1165,7 +1165,8 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki Competitor[] f2 = new Competitor[] { c[0], c[1], c[2] }; Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", - DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); @@ -1175,6 +1176,71 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); } + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule. c[0] and c[2] have + * equal scores after excluding their worst score each. They both exclude a score from the final series. c[1]'s and + * c[3]'s net points sum is worse than that of c[0] and c[2], so c[1] is not tied. Comparing c[0]'s and c[2]'s + * non-excluded final series scores does not break the tie. They both have as their only non-excluded race a win in + * the final series. So the tie break needs to continue by comparing the sorted non-excluded scores from the + * qualification series. c[2]'s excluded score from the final series is worse than the one c[0] excludes in the + * final series. Yet, in the qualification, c[2] has the better ordered scores, so is expected to be found the + * winner. + */ + @Test + public void testTieBreakBasedOnLastNonMedalSeriesNoMedalOneDiscard() throws NoWindException { + Competitor[] c = createCompetitors(4).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[0], c[1], c[2], c[3] }; // c[2]'s net points: 6.0; c[0]'s net points: 6.0 + Competitor[] q2 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[2] has two wins in the qualification, c[0] only one + Competitor[] q3 = new Competitor[] { c[2], c[0], c[1], c[3] }; + Competitor[] f1 = new Competitor[] { c[2], c[1], c[0], c[3] }; // c[0] discards this race; remaining is one win in the final series + Competitor[] f2 = new Competitor[] { c[0], c[1], c[3], c[2] }; // c[2] discards this race; remaining is one win in the final series + Regatta regatta = createRegatta(/* qualifying */3, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, + /* discarding thresholds: one discard when four races have been completed */ new int[] { 4 }); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2, q3); + TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[2], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[2]) < rankedCompetitors.indexOf(c[0])); + } + + /** + * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule. c[0] and c[2] have + * equal scores after excluding their worst score each. c[0] excludes a score from the final series, c[2] from the + * qualification. The way we understand the rules up to now (see also + * https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4717#c4) we have to sort by series as the primary and by + * score as the secondary criterion, making c[0]'s second score from the top the best score from the qualification, + * whereas for c[2] the second score from the top will be the worst score from the final series. This will break the + * tie in c[0]'s favor. + *

+ * + * c[1]'s and c[3]'s net points sum is worse than that of c[0] and c[2], so c[1] is not tied. + */ + @Test + public void testTieBreakBasedOnLastNonMedalSeriesNoMedalDiscardsInDifferentSeries() throws NoWindException { + Competitor[] c = createCompetitors(4).toArray(new Competitor[0]); + Competitor[] q1 = new Competitor[] { c[0], c[1], c[3], c[2] }; // c[2] discards this race + Competitor[] q2 = new Competitor[] { c[2], c[0], c[1], c[3] }; // c[2] net points: 6.0; c[0] net points: 6.0 + Competitor[] q3 = new Competitor[] { c[2], c[0], c[1], c[3] }; + Competitor[] f1 = new Competitor[] { c[2], c[1], c[0], c[3] }; // c[0] discards this race + Competitor[] f2 = new Competitor[] { c[0], c[1], c[2], c[3] }; // c[0] and c[2] won one race in the final series; next, c[2]'s 3.0 points in f2 compare to c[0]'s win in q1 + Regatta regatta = createRegatta(/* qualifying */3, new String[] { "Default" }, /* final */2, new String[] { "Default" }, + /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + Leaderboard leaderboard = createLeaderboard(regatta, + /* discarding thresholds: one discard when four races have been completed */ new int[] { 4 }); + createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2, q3); + TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[2], later), 0.000000001); + assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); + } + + /** * Tests the {@link LowPointTieBreakBasedOnLastSeriesOnly} scoring scheme's tie breaking rule by setting up a * leaderboard with a qualification and a final series such that the competitors have equal points but different @@ -1182,7 +1248,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki * of wins. */ @Test - public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDecisionByLastRace() throws NoWindException { + public void testTieBreakBasedOnLastNonMedalSeriesNoMedalDecisionByLastRace() throws NoWindException { Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have equal wins and scores, Competitor[] q2 = new Competitor[] { c[0], c[1], c[2] }; // but c[1] has won the last race @@ -1190,7 +1256,8 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki Competitor[] f2 = new Competitor[] { c[1], c[0], c[2] }; Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedal", - DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); @@ -1206,7 +1273,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki * case, the qualification counts. */ @Test - public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDefaultingToQualification() throws NoWindException { + public void testTieBreakBasedOnLastNonMedalSeriesNoMedalDefaultingToQualification() throws NoWindException { Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have an equal number of wins in the final series and equal total points; Competitor[] q2 = new Competitor[] { c[2], c[0], c[1] }; // tie breaking is expected to then take the wins in the qualification series into account @@ -1214,7 +1281,8 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki Competitor[] f2 = new Competitor[] { c[1], c[0], c[2] }; // tie breaking will have to resort to the qualification, finding the win of c[1] Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, /* medal */ false, /* medal */ 0, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesNoMedalDefaultingToQualification", - DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); @@ -1229,7 +1297,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki * points but different numbers of wins in the final series */ @Test - public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal() throws NoWindException { + public void testTieBreakBasedOnLastNonMedalSeriesWithMedal() throws NoWindException { Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] wins no race in the qualification, c[1] wins two, Competitor[] q2 = new Competitor[] { c[1], c[0], c[2] }; // but that doesn't help c[1] because c[0] wins one in the final @@ -1238,7 +1306,8 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki Competitor[] m = new Competitor[] { c[0], c[1] }; // c[0] and c[1] got promoted to the medal race, both get disqualified; but in f2 c[2] won, so has to win overall due to count-back Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, /* medal */ true, /* medal */ 1, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal", - DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); @@ -1256,7 +1325,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki * points but different numbers of wins in the final series */ @Test - public void testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedalDecisionByLastRace() throws NoWindException { + public void testTieBreakBasedOnLastNonMedalSeriesWithMedalDecisionByLastRace() throws NoWindException { Competitor[] c = createCompetitors(3).toArray(new Competitor[0]); Competitor[] q1 = new Competitor[] { c[1], c[0], c[2] }; // c[0] and c[1] have equal wins and scores, Competitor[] q2 = new Competitor[] { c[0], c[1], c[2] }; // but c[1] has won the last race before the medal @@ -1265,7 +1334,8 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki Competitor[] m = new Competitor[] { c[0], c[1] }; // c[0] and c[1] got promoted to the medal race, both get disqualified; but in f2 c[2] won, so has to win overall due to count-back Regatta regatta = createRegatta(/* qualifying */2, new String[] { "Default" }, /* final */2, new String[] { "Default" }, /* medal */ true, /* medal */ 1, "testTieBreakWithTieBreakBasedOnLastNonMedalSeriesWithMedal", - DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY)); Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); createAndAttachTrackedRaces(series.get(0), "Default", /* withScores */ true, q1, q2); createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); From c0b060af606243d7df34f6de82cb3e64f75bf3de Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 16:37:00 +0200 Subject: [PATCH 18/24] bug4717: fixed the test verdict of a supposed-to-fail test that didn't fail but now does Change-Id: I6a958ae5122904e5ea144fd3d345e5e0bd3e73f9 --- .../sailing/domain/test/LeaderboardScoringAndRankingTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index e22dfe754e2..d7798e81aa7 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1237,7 +1237,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[2], later), 0.000000001); - assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); + assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[2])); } From a56de83df09a62d2797b8c465911bcaab4e2d88f Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 16:37:54 +0200 Subject: [PATCH 19/24] fixed a tool-tip text for pairing list creation Change-Id: I2451f1c6ec703fa591445c8c52fe9f8abaeacc8d --- .../com/sap/sailing/gwt/ui/client/StringMessages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5a836b6a992..5934b29ae46 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 @@ -267,7 +267,7 @@ boatChanges=Boat Changes pairingListTemplate=Pairing list template printHint=Prints the applied version blockedApplyButton=Registered Competitors are unequal to Competitors from Pairinglist! -multiplierInfo=Multipes flights and create them next to each other so that a competition with less boatchanges is possible +multiplierInfo=Multiplies flights and creates them next to each other so that a competition with fewer boat changes is possible noPairingListAvailable=The print function is only available when a pairing list has already been applied to the selected leaderboards race logs. noEventsFound=No events were found noEventSelected=No event selected From 013b4af418063649a44989e16018425f1995e9be Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 17:34:56 +0200 Subject: [PATCH 20/24] implemented LowPointTieBreakBasedOnLastSeriesOnly such that all formerly failing test cases pass now Change-Id: I5625692eb0cd0d10b10536fa26cd707a390615d1 --- .../LeaderboardScoringAndRankingTest.java | 2 +- .../domain/leaderboard/ScoringScheme.java | 19 ++++-- ...tHighPointExtremeSailingSeriesOverall.java | 4 +- ...ointFirstGetsFixedOr8AndLastBreaksTie.java | 4 +- .../impl/AbstractScoringSchemeImpl.java | 48 ++++++++++---- .../HighPointFirstGets10LastBreaksTie.java | 4 +- .../HighPointFirstGets1LastBreaksTie.java | 3 +- .../impl/HighPointLastBreaksTie.java | 4 +- .../impl/LeaderboardTotalRankComparator.java | 12 ++-- .../LowPointForLeagueOverallLeaderboard.java | 4 +- ...LowPointTieBreakBasedOnLastSeriesOnly.java | 62 +++++++++++++++++++ 11 files changed, 138 insertions(+), 28 deletions(-) diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index d7798e81aa7..c57aa6b3718 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -1263,7 +1263,7 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki TimePoint later = createAndAttachTrackedRaces(series.get(1), "Default", /* withScores */ true, f1, f2); List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(leaderboard.getNetPoints(c[0], later), leaderboard.getNetPoints(c[1], later), 0.000000001); - assertTrue(rankedCompetitors.indexOf(c[0]) < rankedCompetitors.indexOf(c[1])); // based on last race f2 + assertTrue(rankedCompetitors.indexOf(c[1]) < rankedCompetitors.indexOf(c[0])); // based on last race f2 } /** 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 583bc74487a..546e4464f58 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 @@ -3,6 +3,8 @@ package com.sap.sailing.domain.leaderboard; import java.io.Serializable; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import com.sap.sailing.domain.base.Competitor; @@ -86,12 +88,19 @@ public interface ScoringScheme extends Serializable { TimePoint timePoint, Leaderboard leaderboard); /** - * @param competitor1Scores scores of the first competitor, in the order of race columns in the leaderboard - * @param competitor2Scores scores of the second competitor, in the order of race columns in the leaderboard - * @param leaderboard TODO + * @param competitor1Scores + * scores of the first competitor, in the order of race columns in the leaderboard + * @param competitor2Scores + * scores of the second competitor, in the order of race columns in the leaderboard + * @param discardedRaceColumnsPerCompetitor + * for each competitor holds the result of {@link Leaderboard#getResultDiscardingRule() + * Leaderborad.getResultDiscardingRule()}{@code .}{@link ResultDiscardingRule#getDiscardedRaceColumns(Competitor, Leaderboard, Iterable, TimePoint) + * getDiscardedRaceColumns(...)}. This accelerates things considerable because we do not have to make this expensive calculation + * for each competitor again. */ - int compareByBetterScore(Competitor o1, - List> competitor1Scores, Competitor o2, List> competitor2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard); + int compareByBetterScore(Competitor o1, List> competitor1Scores, Competitor o2, + List> competitor2Scores, boolean nullScoresAreBetter, TimePoint timePoint, + Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor); /** * In case two competitors scored in different numbers of races, this scoring scheme decides whether this 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 48d0964a3b9..9cdfd4edc82 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 @@ -3,6 +3,8 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import com.sap.sailing.domain.base.Competitor; @@ -55,7 +57,7 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP * Implements rule 13.5 of the Extreme Sailing Series notice of race as of August 2012. */ @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { int o1Wins = getWins(o1Scores); int o2Wins = getWins(o2Scores); int result = o2Wins - o1Wins; 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 800f4125459..5b62ef9bedf 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 @@ -1,6 +1,8 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Callable; import com.sap.sailing.domain.abstractlog.race.RaceLog; @@ -37,7 +39,7 @@ public abstract class AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie extends } @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { Double o1Highest = getHighestScore(o1Scores); Double o2Highest = getHighestScore(o2Scores); return o2Highest.compareTo(o1Highest); 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 8b913934df1..f30a10e2c0a 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 @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.Set; import com.sap.sailing.domain.base.Competitor; @@ -121,28 +122,41 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { } /** - * Assuming both competitors scored in the same number of races, compares the sorted scores. + * Assuming both competitors scored in the same number of races, compares the sorted scores based on World Sailing's + * Racing Rules of Sailing (RRS) addendum A8.1:

+ * + * "A8.1 If there is a series-score tie between two or more boats, each boat’s race scores shall be listed in + * order of best to worst, and at the first point(s) where there is a difference the tie shall be broken in favour + * of the boat(s) with the best score(s). No excluded scores shall be used." */ @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { - final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); + public int compareByBetterScore(Competitor o1, List> o1Scores, + Competitor o2, List> o2Scores, boolean nullScoresAreBetter, + TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { + final Comparator> ruleA8_1ScoreComparator = getRuleA8_1ScoreComparator(nullScoresAreBetter); // needs to compare net points; therefore, divide the total points by the column factor for comparison: - List o1NetScores = new ArrayList<>(); + List> o1NetScores = new ArrayList<>(); + final Set o1Discards = discardedRaceColumnsPerCompetitor.get(o1); for (com.sap.sse.common.Util.Pair o1ColumnAndScore : o1Scores) { - o1NetScores.add(o1ColumnAndScore.getB() / getScoreFactor(o1ColumnAndScore.getA())); + if (!o1Discards.contains(o1ColumnAndScore.getA())) { + o1NetScores.add(new Pair<>(o1ColumnAndScore.getA(), o1ColumnAndScore.getB() / getScoreFactor(o1ColumnAndScore.getA()))); + } } - List o2NetScores = new ArrayList<>(); + List> o2NetScores = new ArrayList<>(); + final Set o2Discards = discardedRaceColumnsPerCompetitor.get(o2); for (com.sap.sse.common.Util.Pair o2ColumnAndScore : o2Scores) { - o2NetScores.add(o2ColumnAndScore.getB() / getScoreFactor(o2ColumnAndScore.getA())); + if (!o2Discards.contains(o2ColumnAndScore.getA())) { + o2NetScores.add(new Pair<>(o2ColumnAndScore.getA(), o2ColumnAndScore.getB() / getScoreFactor(o2ColumnAndScore.getA()))); + } } - Collections.sort(o1NetScores, pureScoreComparator); - Collections.sort(o2NetScores, pureScoreComparator); + Collections.sort(o1NetScores, ruleA8_1ScoreComparator); + Collections.sort(o2NetScores, ruleA8_1ScoreComparator); // now both lists are sorted from best to worst score - Iterator o1Iter = o1NetScores.iterator(); - Iterator o2Iter = o2NetScores.iterator(); + Iterator> o1Iter = o1NetScores.iterator(); + Iterator> o2Iter = o2NetScores.iterator(); int result = 0; while (result == 0 && o1Iter.hasNext() && o2Iter.hasNext()) { - result = pureScoreComparator.compare(o1Iter.next(), o2Iter.next()); + result = getScoreComparator(nullScoresAreBetter).compare(o1Iter.next().getB(), o2Iter.next().getB()); } if (o1Iter.hasNext() != o2Iter.hasNext()) { // if, as may be allowed by some scoring scheme variants, competitors with different numbers of scored races are compared @@ -152,6 +166,16 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { return result; } + /** + * Obtains a comparator that compares two non-discarded scores according the rule A8.1, or any + * modification thereof (which subclasses can provide by overriding this method). This default implementation + * simply uses the {@link #getScoreComparator(boolean) score comparator} to compare the net scores + * directly. + */ + protected Comparator> getRuleA8_1ScoreComparator(boolean nullScoresAreBetter) { + return (p1, p2)->getScoreComparator(nullScoresAreBetter).compare(p1.getB(), p2.getB()); + } + /** * By default, this scoring scheme implementation does not compare the numbers of races scored. */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java index 81bff24d097..3ea20a098f3 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.List; +import java.util.Map; +import java.util.Set; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; @@ -22,7 +24,7 @@ public class HighPointFirstGets10LastBreaksTie extends HighPointFirstGetsFixedSc } @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { return 0; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java index ae12da23843..970b5ec1da2 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java @@ -6,6 +6,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import com.sap.sailing.domain.base.Competitor; @@ -55,7 +56,7 @@ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedSco @Override public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, - List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { // Construct a graph in which all competitors with equal points are represented as nodes; // edges represent a RaceColumn/Fleet where the "from" node in the edge has won over the "to" // node in the edge. This graph will be sorted such that competitors precede those over which diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java index 59d5ea4d725..2647af65ab5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.List; +import java.util.Map; +import java.util.Set; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; @@ -21,7 +23,7 @@ public class HighPointLastBreaksTie extends HighPoint { private static final long serialVersionUID = -5338636946886101669L; @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { return 0; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java index 5a94b862a3e..567c007a681 100644 --- 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 @@ -55,6 +55,7 @@ public class LeaderboardTotalRankComparator implements Comparator { private final ScoringScheme scoringScheme; private final Map, Double> netPointsCache; private final Map, Double> totalPointsCache; + private final Map> discardedRaceColumnsPerCompetitor; private final boolean nullScoresAreBetter; private final TimePoint timePoint; @@ -84,11 +85,13 @@ public class LeaderboardTotalRankComparator implements Comparator { this.timePoint = timePoint; this.scoringScheme = scoringScheme; this.nullScoresAreBetter = nullScoresAreBetter; - netPointsCache = new HashMap, Double>(); - totalPointsCache = new HashMap, Double>(); + netPointsCache = new HashMap<>(); + totalPointsCache = new HashMap<>(); + discardedRaceColumnsPerCompetitor = new HashMap<>(); for (Competitor competitor : leaderboard.getCompetitors()) { Set discardedRaceColumns = leaderboard.getResultDiscardingRule().getDiscardedRaceColumns( competitor, leaderboard, raceColumnsToConsider, timePoint); + this.discardedRaceColumnsPerCompetitor.put(competitor, discardedRaceColumns); for (RaceColumn raceColumn : raceColumnsToConsider) { Pair key = new Util.Pair(competitor, raceColumn); netPointsCache.put(key, leaderboard.getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns)); @@ -471,11 +474,12 @@ public class LeaderboardTotalRankComparator implements Comparator { /** * Assuming both competitors scored in the same number of races, and assuming they scored the same net score, * break the tie according to the {@link #scoringScheme scoring scheme} set for this comparator. - * @see ScoringScheme#compareByBetterScore(Competitor, List, Competitor, List, boolean, TimePoint, Leaderboard) + * @see ScoringScheme#compareByBetterScore(Competitor, List, Competitor, List, boolean, TimePoint, Leaderboard, Map) */ protected int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, TimePoint timePoint) { - return scoringScheme.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard); + return scoringScheme.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard, + Collections.unmodifiableMap(discardedRaceColumnsPerCompetitor)); } /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java index 1f99e5b47f0..c7fcbd49728 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.leaderboard.impl; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,7 +43,7 @@ public class LowPointForLeagueOverallLeaderboard extends LowPoint { */ @Override public int compareByBetterScore(Competitor o1, List> o1Scores, - Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard, Map> discardedRaceColumnsPerCompetitor) { final Double o1ScoreSum = o1Scores.stream().collect(Collectors.summingDouble(rcAndScore->rcAndScore.getB())); final Double o2ScoreSum = o2Scores.stream().collect(Collectors.summingDouble(rcAndScore->rcAndScore.getB())); assert Math.abs(o1ScoreSum - o2ScoreSum) < 0.00001; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointTieBreakBasedOnLastSeriesOnly.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointTieBreakBasedOnLastSeriesOnly.java index 419526398d3..31d6b91ad3d 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointTieBreakBasedOnLastSeriesOnly.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointTieBreakBasedOnLastSeriesOnly.java @@ -1,6 +1,13 @@ package com.sap.sailing.domain.leaderboard.impl; +import java.util.Comparator; + +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sse.common.Util.Pair; public class LowPointTieBreakBasedOnLastSeriesOnly extends LowPoint { private static final long serialVersionUID = -2837189757937229157L; @@ -9,4 +16,59 @@ public class LowPointTieBreakBasedOnLastSeriesOnly extends LowPoint { public ScoringSchemeType getType() { return ScoringSchemeType.LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY; } + + /** + * Obtains a comparator that compares two non-discarded, non medal-race scores according the rule 21.9: + *

+ * + * "21.9 RRS A8 is changed as follows: 21.9.1 For boats assigned to compete in a medal race, ties in the regatta + * score will be broken by the medal race score. This changes RRS A8. 21.9.2 For tied boats with the same points + * score in the medal race, ties will be broken applying RRS A8 to the opening series scores. 21.9.3 For boats that + * sailed a Qualifying Series and a Final Series, ties will be broken using A8 but using the scores in the Final + * Series before using any scores in the Qualifying Series when applying A8.1. This applies also to boats that were + * tied after the medal race and had the same score in the medal race." + *

+ * + * For this, the position of the series in the regatta is determined, and if one score comes from the last non-medal + * series and the other does not, the one from the last non-medal series is considered better. Otherwise, the + * {@link #getScoreComparator(boolean) score comparator} is used to compare the scores numerically. + */ + @Override + protected Comparator> getRuleA8_1ScoreComparator(boolean nullScoresAreBetter) { + return (Pair p1, Pair p2)->{ + final int result; + if (isFromLastNonMedalSeries(p1) != isFromLastNonMedalSeries(p2)) { + result = isFromLastNonMedalSeries(p1) ? -1 // p1 is better, so less, than p2 + : 1; // p1 is worse, so greater, than p2 + } else { + result = getScoreComparator(nullScoresAreBetter).compare(p1.getB(), p2.getB()); + } + return result; + }; + } + + private boolean isFromLastNonMedalSeries(Pair raceColumnAndNetPoints) { + final RaceColumn raceColumn = raceColumnAndNetPoints.getA(); + final boolean result; + if (raceColumn instanceof RaceColumnInSeries) { + final Series series = ((RaceColumnInSeries) raceColumn).getSeries(); + final Regatta regatta = series.getRegatta(); + boolean found = false; + boolean lastNonMedal = true; + for (final Series seriesInRegatta : regatta.getSeries()) { + if (!found) { + if (seriesInRegatta == series) { + found = true; + } + } else if (!seriesInRegatta.isMedal()) { + lastNonMedal = false; // already found, and seriesInRegatta is a non-medal series that comes after series + } + } + assert found; + result = lastNonMedal; + } else { + result = !raceColumn.isMedalRace(); + } + return result; + } } From 109aa1b2039082f90519101c508fa6cbbf2f2d52 Mon Sep 17 00:00:00 2001 From: Steffen Jacobs Date: Tue, 21 May 2019 17:38:57 +0200 Subject: [PATCH 21/24] Bug 5046: changed default value of serializeNonPublicFields to true to let the server store email addresses again --- .../gateway/serialization/impl/CompetitorJsonSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java index 7983eb3413c..16696dd0541 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java @@ -57,7 +57,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { */ public static CompetitorJsonSerializer create(boolean serializeBoat) { return new CompetitorJsonSerializer(TeamJsonSerializer.create(), serializeBoat?BoatJsonSerializer.create():null, - /* serializeNonPublicFields */ false); + /* serializeNonPublicFields */ true); } /** From 5e341f4b77941a85340c8fa153a275e096f70d11 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 23:32:36 +0200 Subject: [PATCH 22/24] Revert "Bug 5046: changed default value of serializeNonPublicFields to true to" This reverts commit 109aa1b2039082f90519101c508fa6cbbf2f2d52. --- .../gateway/serialization/impl/CompetitorJsonSerializer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java index 16696dd0541..7983eb3413c 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/CompetitorJsonSerializer.java @@ -57,7 +57,7 @@ public class CompetitorJsonSerializer implements JsonSerializer { */ public static CompetitorJsonSerializer create(boolean serializeBoat) { return new CompetitorJsonSerializer(TeamJsonSerializer.create(), serializeBoat?BoatJsonSerializer.create():null, - /* serializeNonPublicFields */ true); + /* serializeNonPublicFields */ false); } /** From 774884f5c69b2d55156294b6c2f8d7a7fa55d984 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 21 May 2019 23:38:41 +0200 Subject: [PATCH 23/24] explicitly request the serialization of a competitor's private fields in the MongoObjectFactory for persistence --- .../sailing/domain/persistence/impl/MongoObjectFactoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index ae837909ddb..5de9ea5edeb 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -143,7 +143,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); private final MongoDatabase database; private final CompetitorWithBoatRefJsonSerializer competitorWithBoatRefSerializer = CompetitorWithBoatRefJsonSerializer.create(); - private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create(); + private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create(/* serialize boat */ true, /* serializeNonPublicCompetitorFields */ true); private final BoatJsonSerializer boatSerializer = BoatJsonSerializer.create(); private final TypeBasedServiceFinder deviceIdentifierServiceFinder; private final TypeBasedServiceFinder raceTrackingConnectivityParamsServiceFinder; From 051793813538d88dd509cb15ad7118d5728ca301 Mon Sep 17 00:00:00 2001 From: Steffen Schaefer Date: Wed, 22 May 2019 14:31:56 +0200 Subject: [PATCH 24/24] Removed manual installation of BIRT charting because a working version is automatically included when installing MAT --- configuration/installPluginsForEclipse2019-03.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/configuration/installPluginsForEclipse2019-03.sh b/configuration/installPluginsForEclipse2019-03.sh index b8805ab4bf0..266d020cc8d 100755 --- a/configuration/installPluginsForEclipse2019-03.sh +++ b/configuration/installPluginsForEclipse2019-03.sh @@ -36,9 +36,6 @@ installPlugins http://p2.sapsailing.com/p2/sdbg com.github.sdbg.feature.feature. echo "Installing EasyShell..." installPlugins http://anb0s.github.io/EasyShell de.anbos.eclipse.easyshell.feature.feature.group -echo "Installing BIRT charts (requirement for MAT)..." -installPlugins https://download.eclipse.org/birt/update-site/photon-interim/ org.eclipse.birt.chart.feature.group - echo "Installing Memory Analyzer..." installPlugins http://download.eclipse.org/releases/2019-03 org.eclipse.mat.feature.feature.group,org.eclipse.mat.chart.feature.feature.group