From ef621126f9af160a23b899109c54dcae207db915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 15 Apr 2019 16:33:27 +0200 Subject: [PATCH 001/235] bug4887 events delete REST call --- .../gateway/jaxrs/api/EventsResource.java | 34 +++++++++++++ .../webservices/api/v1/eventDeleteDoc.html | 49 +++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index acd992a7fee..6a4a2d48c36 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -25,6 +25,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -80,11 +81,15 @@ import com.sap.sailing.server.gateway.serialization.impl.EventRaceStatesSerializ import com.sap.sailing.server.gateway.serialization.impl.LeaderboardGroupBaseJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.VenueJsonSerializer; import com.sap.sailing.server.hierarchy.SailingHierarchyOwnershipUpdater; +import com.sap.sailing.server.interfaces.RacingEventService; import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; import com.sap.sailing.server.operationaltransformation.AddCourseAreas; import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; import com.sap.sailing.server.operationaltransformation.CreateEvent; import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; +import com.sap.sailing.server.operationaltransformation.RemoveEvent; +import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; +import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.UpdateSeries; import com.sap.sailing.server.security.SailingViewerRole; @@ -96,6 +101,7 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; import com.sap.sse.common.impl.MillisecondsDurationImpl; import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.security.SecurityService; import com.sap.sse.security.shared.HasPermissions.DefaultActions; import com.sap.sse.security.shared.RoleDefinition; import com.sap.sse.security.shared.impl.User; @@ -113,6 +119,34 @@ public class EventsResource extends AbstractSailingServerResource { public EventsResource() { } + @DELETE + @Path("/{eventId}/") + public Response delete(@PathParam("eventId") UUID eventId, + @QueryParam("withLeaderboardGroupsAndOverallLeaderboard") Boolean withLeaderboardGroupsAndOverallLeaderboard) + throws ParseException, JsonDeserializationException { + Event event = getService().getEvent(eventId); + RacingEventService racingEventService = getService(); + SecurityService securityService = racingEventService.getSecurityService(); + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { + getService().apply(new RemoveEvent(event.getId())); + if (withLeaderboardGroupsAndOverallLeaderboard == Boolean.TRUE) { + for (LeaderboardGroup group : event.getLeaderboardGroups()) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { + Leaderboard overallLeaderboard = group.getOverallLeaderboard(); + if (overallLeaderboard != null) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, + () -> { + racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); + }); + } + racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); + }); + } + } + }); + return Response.ok().build(); + } + @POST @Path("/{eventId}/migrate") public Response migrateOwnershipForEvent(@PathParam("eventId") UUID eventId, diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html new file mode 100644 index 00000000000..64ece6d3232 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html @@ -0,0 +1,49 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/events/{event-id}/

+ +Description: +

Deletes and Event, optionally also allows to delete the LeaderboardGroup this event is part of and the linked OverallLeaderboard

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Webservice Type:PUT
Output format:empty
Mandatory parameters:none; when no parameters are provided the call is a no-op
Optional parameters: +
withLeaderboardGroupsAndOverallLeaderboard If true, the LeaderBoardGroup and the OverallLeaderboard will be deleted as well
+
Request method:DELETE
Example:curl -X DELETE -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/events/0065b870-9ddb-42be-a7d4-6be2081cb63e?withLeaderboardGroupsAndOverallLeaderboard=true"
+
+
+Back to Web Service Overview + + From c7214b39aef0a8bb67f3f3ffbfd981d59047efa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 15 Apr 2019 16:33:43 +0200 Subject: [PATCH 002/235] bug4887 regattas delete REST call --- .../gateway/jaxrs/api/RegattasResource.java | 45 +++++++++++++++++ .../webservices/api/v1/regattaDeleteDoc.html | 48 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 java/com.sap.sailing.server.gateway/webservices/api/v1/regattaDeleteDoc.html diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index 97a7b6c34f1..298a5d363ea 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -19,6 +19,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -66,6 +67,7 @@ import com.sap.sailing.domain.common.CompetitorRegistrationType; import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.RegattaName; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.TargetTimeInfo; @@ -76,6 +78,7 @@ import com.sap.sailing.domain.common.security.SecuredDomainType; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; @@ -131,6 +134,7 @@ import com.sap.sailing.server.gateway.serialization.impl.TeamJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.TrackedRaceJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.WindJsonSerializer; import com.sap.sailing.server.operationaltransformation.AddColumnToSeries; +import com.sap.sailing.server.operationaltransformation.RemoveRegatta; import com.sap.sailing.server.operationaltransformation.UpdateSeries; import com.sap.sse.InvalidDateException; import com.sap.sse.common.Color; @@ -147,6 +151,8 @@ import com.sap.sse.common.util.RoundingUtil; import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier; import com.sap.sse.security.shared.HasPermissions.DefaultActions; import com.sap.sse.security.shared.OwnershipAnnotation; +import com.sap.sse.security.shared.QualifiedObjectIdentifier; +import com.sap.sse.security.shared.TypeRelativeObjectIdentifier; import com.sap.sse.security.shared.impl.SecuredSecurityTypes; import com.sap.sse.security.shared.impl.User; @@ -263,6 +269,45 @@ public class RegattasResource extends AbstractSailingServerResource { return response; } + @DELETE + @Path("{regattaname}") + public Response delete(@PathParam("regattaname") String regattaName) { + Regatta regatta = getService().getRegattaByName(regattaName); + // TODO this is the same code, as the one used by the SailingServiceImpl, consolidate if an additional layer is + // ever wrapped around the RacingEventService + if (regatta != null) { + Set objectsThatWillBeImplicitlyCleanedByRemoveRegatta = new HashSet<>(); + objectsThatWillBeImplicitlyCleanedByRemoveRegatta.add(regatta.getIdentifier()); + for (RaceDefinition race : regatta.getAllRaces()) { + TypeRelativeObjectIdentifier typeRelativeObjectIdentifier = RegattaNameAndRaceName + .getTypeRelativeObjectIdentifier(regatta.getName(), race.getName()); + QualifiedObjectIdentifier identifier = SecuredDomainType.TRACKED_RACE + .getQualifiedObjectIdentifier(typeRelativeObjectIdentifier); + objectsThatWillBeImplicitlyCleanedByRemoveRegatta.add(identifier); + } + for (Leaderboard leaderboard : getService().getLeaderboards().values()) { + if (leaderboard instanceof RegattaLeaderboard) { + RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; + if (regattaLeaderboard.getRegatta() == regatta) { + objectsThatWillBeImplicitlyCleanedByRemoveRegatta.add(regattaLeaderboard.getIdentifier()); + } + } + } + // check if we can delete everything RemoveRegatta will remove + for (QualifiedObjectIdentifier toRemovePermissionObjects : objectsThatWillBeImplicitlyCleanedByRemoveRegatta) { + getSecurityService().checkCurrentUserDeletePermission(toRemovePermissionObjects); + } + // we have all permissions, execute + getService().apply(new RemoveRegatta(regatta.getRegattaIdentifier())); + // cleanup the Ownership and ACLs + for (QualifiedObjectIdentifier toRemovePermissionObjects : objectsThatWillBeImplicitlyCleanedByRemoveRegatta) { + getSecurityService().deleteAllDataForRemovedObject(toRemovePermissionObjects); + } + + } + return Response.ok().build(); + } + /** * Gets all entries for a regatta. * diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/regattaDeleteDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/regattaDeleteDoc.html new file mode 100644 index 00000000000..c5d005648ce --- /dev/null +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/regattaDeleteDoc.html @@ -0,0 +1,48 @@ + + + + + + + + SAP Sailing Analytics Webservices API Version 1.0 + + +

SAP Sailing Analytics Webservices API Version 1.0

+

URL: /api/v1/regattas/{name}/

+ +Description: +

Deletes the given regatta, using the same logic the AdminConsole + does. Deleting a regatta will also remove all RegattaLeaderboards + pointing to it. Additionally it will remove all related races as well.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
Webservice Type:REST
Output format:None
Mandatory parameters:None
Optional parameters:None
Request method:DELETE
Example:curl -X DELETE -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/regattas/ESS 2016 Sydney/"
+
+Back to Web Service Overview + + From 5b25d207aa76a30dd22a66106b0540a37382dbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kai=20B=C3=B6rnert?= Date: Mon, 15 Apr 2019 16:33:56 +0200 Subject: [PATCH 003/235] bug4887 adjusted index for new documentation --- .../webservices/api/v1/index.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index b36bfcfac64..2103c2f7187 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -36,6 +36,10 @@ name to make sure you get to the event you're interested in. /api/v1/events/createEvent Creates a new event + + /api/v1/events/{event-id}/ + Deletes and Event + /api/v1/events/{event-id}/update Updates an existing event @@ -64,6 +68,10 @@ name to make sure you get to the event you're interested in. /api/v1/regattas/{name} Gets the details of a regatta + + /api/v1/regattas/{name} + Delete the regatta and all regatta leaderboards + /api/v1/regattas/{name}/races Gets the list of races in a regatta From 855cc7cea828bbac34ba742cae2eb7212e9feee3 Mon Sep 17 00:00:00 2001 From: Alexandr Timoshenko Date: Wed, 15 Apr 2020 14:44:48 +0200 Subject: [PATCH 004/235] Bug 2902 - Navdrawer background color for races --- .../res/layout/race_list_area_header.xml | 60 +++++++++++-------- .../racelist/ManagedRaceListAdapter.java | 6 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml index ebb43a44359..db04a6ac0f7 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml @@ -10,38 +10,48 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" + android:background="@color/white" android:gravity="center_vertical" android:orientation="vertical"> - + - + + + + + + + + android:layout_gravity="center_horizontal" + android:paddingTop="3dp" + android:text="@string/protest_time" + android:textAppearance="?attr/textPanelCaption" /> - - - - + imple holder.race_scheduled.setVisibility(View.GONE); holder.race_unscheduled.setVisibility(View.VISIBLE); } - } else { - if (holder.race_name != null) { - holder.race_name.setTextColor(ThemeHelper.getColor(getContext(), R.attr.white)); - } } } @@ -393,7 +389,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple holder.race_unscheduled.setVisibility(View.GONE); } if (holder.race_name != null) { - holder.race_name.setTextColor(ThemeHelper.getColor(getContext(), R.attr.sap_light_gray)); + holder.race_name.setTextColor(ThemeHelper.getColor(getContext(), R.attr.black)); } if (holder.has_dependent_races != null) { holder.has_dependent_races.setVisibility(View.GONE); From a1737aec54985b6cd68c89b144a0e875a36e7142 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Wed, 22 Apr 2020 17:35:25 +0200 Subject: [PATCH 005/235] fix: typo --- .../abstractlog/race/state/RaceStateChangedListener.java | 2 +- .../race/state/impl/BaseRaceStateChangedListener.java | 2 +- .../race/state/impl/RaceStateChangedListeners.java | 4 ++-- .../abstractlog/race/state/impl/ReadonlyRaceStateImpl.java | 2 +- .../app/ui/fragments/panels/TimePanelFragment.java | 4 ++-- .../app/ui/fragments/raceinfo/PenaltyFragment.java | 4 ++-- .../app/ui/fragments/raceinfo/RaceSummaryFragment.java | 2 +- .../app/ui/fragments/raceinfo/TrackingListFragment.java | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java index d2b3b276790..8a9551a0fd0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceStateChangedListener.java @@ -13,7 +13,7 @@ public interface RaceStateChangedListener { * TODO: We need to recheck the AbortFinder to enable these interface methods * void onAborted(RaceState2 state); * void onGeneralRecall(RaceState2 state);*/ - void onFinishingPositioningsChanged(ReadonlyRaceState state); + void onFinishingPositionsChanged(ReadonlyRaceState state); void onFinishingPositionsConfirmed(ReadonlyRaceState state); void onCourseDesignChanged(ReadonlyRaceState state); void onWindFixChanged(ReadonlyRaceState state); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java index 5d7db3578c0..3dc7a0fc842 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/BaseRaceStateChangedListener.java @@ -44,7 +44,7 @@ public abstract class BaseRaceStateChangedListener implements RaceStateChangedLi } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + public void onFinishingPositionsChanged(ReadonlyRaceState state) { } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java index c03f3845e01..c9bbd7dcc7c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateChangedListeners.java @@ -85,9 +85,9 @@ public class RaceStateChangedListeners extends HashSet } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + public void onFinishingPositionsChanged(ReadonlyRaceState state) { for (RaceStateChangedListener listener : getWorkingCopyOfListeners()) { - listener.onFinishingPositioningsChanged(state); + listener.onFinishingPositionsChanged(state); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java index da7bdb00ec5..fcbd1a5f78f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/ReadonlyRaceStateImpl.java @@ -556,7 +556,7 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL CompetitorResults positionedCompetitors = finishPositioningListAnalyzer.analyze().getCompetitorResults(); if (!Util.equalsWithNull(cachedPositionedCompetitors, positionedCompetitors)) { cachedPositionedCompetitors = positionedCompetitors; - changedListeners.onFinishingPositioningsChanged(this); + changedListeners.onFinishingPositionsChanged(this); } CompetitorResultsAndTheirCreationTimePoints confirmedPositionedCompetitors = confirmedFinishPositioningListAnalyzer.analyze(); if (!Util.equalsWithNull(cachedConfirmedPositionedCompetitors, confirmedPositionedCompetitors)) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java index 5a3e414f039..76c5f29c440 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java @@ -290,8 +290,8 @@ public class TimePanelFragment extends BasePanelFragment { } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { - super.onFinishingPositioningsChanged(state); + public void onFinishingPositionsChanged(ReadonlyRaceState state) { + super.onFinishingPositionsChanged(state); checkWarnings(state); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PenaltyFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PenaltyFragment.java index cf3129e6e65..00f31620784 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PenaltyFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/PenaltyFragment.java @@ -854,8 +854,8 @@ public class PenaltyFragment extends BaseFragment } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { - super.onFinishingPositioningsChanged(state); + public void onFinishingPositionsChanged(ReadonlyRaceState state) { + super.onFinishingPositionsChanged(state); PenaltyFragment fragment = mReference.get(); if (fragment != null) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/RaceSummaryFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/RaceSummaryFragment.java index b3b0488c3b2..f8142c472ff 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/RaceSummaryFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/RaceSummaryFragment.java @@ -258,7 +258,7 @@ public class RaceSummaryFragment extends BaseFragment { } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + public void onFinishingPositionsChanged(ReadonlyRaceState state) { showData(); } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java index bc75fdec6ab..90a0d57faf3 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java @@ -1259,8 +1259,8 @@ public class TrackingListFragment extends BaseFragment } @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { - super.onFinishingPositioningsChanged(state); + public void onFinishingPositionsChanged(ReadonlyRaceState state) { + super.onFinishingPositionsChanged(state); TrackingListFragment fragment = mReference.get(); if (fragment != null) { From 771019551db67eb40666415e225cd9cb3886a715 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 23 Apr 2020 12:03:36 +0200 Subject: [PATCH 006/235] fix: issues regarding race list in NavigationDrawer race selection, flags and timers --- .../res/drawable/marker_race.xml | 18 +- .../res/layout/layout_flag_time.xml | 24 + .../res/layout/race_list_area_header.xml | 128 +++--- .../res/layout/race_list_area_item.xml | 203 ++++---- .../res/values/attrs.xml | 4 + .../racelist/ManagedRaceListAdapter.java | 433 +++++++----------- .../app/ui/fragments/RaceListFragment.java | 185 +++----- .../app/ui/views/BaseTimeView.java | 26 -- .../app/ui/views/FlagTimeView.java | 156 ++++++- .../app/ui/views/RaceTimeView.java | 34 +- 10 files changed, 581 insertions(+), 630 deletions(-) create mode 100644 mobile/com.sap.sailing.racecommittee.app/res/layout/layout_flag_time.xml delete mode 100644 mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/BaseTimeView.java diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable/marker_race.xml b/mobile/com.sap.sailing.racecommittee.app/res/drawable/marker_race.xml index 86afaf5bd61..49bb37b44d1 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/drawable/marker_race.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/drawable/marker_race.xml @@ -1,11 +1,9 @@ - - - - - - \ No newline at end of file + + + + + + + + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/layout_flag_time.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/layout_flag_time.xml new file mode 100644 index 00000000000..4d347b4c415 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/layout_flag_time.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml index db04a6ac0f7..b27a9b845dc 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_header.xml @@ -2,78 +2,15 @@ + android:layout_height="@dimen/navigation_drawer_cell_height"> - - - - - - - - - - - - - - - - - - - - @@ -95,4 +32,59 @@ android:textAppearance="?attr/textSmall" tools:text="Day 1\nYellow" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_item.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_item.xml index 3787826943d..2424a284074 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_item.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_area_item.xml @@ -2,92 +2,23 @@ + android:layout_height="@dimen/navigation_drawer_cell_height" + xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - - - - - - - - - - - - - - + android:duplicateParentState="true" + android:background="?attr/marker_race" /> + android:layout_toStartOf="@id/panel_right" + android:layout_toLeftOf="@id/panel_right"> - - - - - - - + tools:visibility="visible" /> + android:id="@+id/race_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="middle" + android:singleLine="true" + android:textAppearance="?attr/textNormal" + tools:text="Race 3" /> + + + + + + + + + + + + + + + + + + - + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index d34df2e47b2..acc8ef22c21 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -1,51 +1,9 @@ package com.sap.sailing.racecommittee.app.ui.adapters.racelist; -import com.sap.sailing.racecommittee.app.ui.views.FlagTimeView; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.shared.util.BitmapHelper; -import com.sap.sailing.android.shared.util.BroadcastManager; -import com.sap.sailing.android.shared.util.ViewHelper; -import com.sap.sailing.domain.abstractlog.race.CompetitorResults; -import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; -import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; -import com.sap.sailing.domain.abstractlog.race.state.RaceState; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; -import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; -import com.sap.sailing.domain.base.SeriesBase; -import com.sap.sailing.domain.base.racegroup.CurrentRaceComparator; -import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; -import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesComparator; -import com.sap.sailing.domain.common.racelog.FlagPole; -import com.sap.sailing.domain.common.racelog.Flags; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; -import com.sap.sailing.racecommittee.app.AppConstants; -import com.sap.sailing.racecommittee.app.R; -import com.sap.sailing.racecommittee.app.data.DataManager; -import com.sap.sailing.racecommittee.app.domain.ManagedRace; -import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterSubscriber; -import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources; -import com.sap.sailing.racecommittee.app.ui.views.RaceTimeView; -import com.sap.sailing.racecommittee.app.utils.RaceHelper; -import com.sap.sailing.racecommittee.app.utils.ThemeHelper; -import com.sap.sailing.racecommittee.app.utils.TimeUtils; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; - import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -56,6 +14,40 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import com.sap.sailing.android.shared.util.BroadcastManager; +import com.sap.sailing.android.shared.util.ViewHelper; +import com.sap.sailing.domain.abstractlog.race.CompetitorResults; +import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; +import com.sap.sailing.domain.abstractlog.race.state.RaceState; +import com.sap.sailing.domain.base.SeriesBase; +import com.sap.sailing.domain.base.racegroup.CurrentRaceComparator; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeries; +import com.sap.sailing.domain.base.racegroup.RaceGroupSeriesComparator; +import com.sap.sailing.domain.common.racelog.Flags; +import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; +import com.sap.sailing.racecommittee.app.AppConstants; +import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.data.DataManager; +import com.sap.sailing.racecommittee.app.domain.ManagedRace; +import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterSubscriber; +import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources; +import com.sap.sailing.racecommittee.app.ui.views.FlagTimeView; +import com.sap.sailing.racecommittee.app.ui.views.RaceTimeView; +import com.sap.sailing.racecommittee.app.utils.RaceHelper; +import com.sap.sailing.racecommittee.app.utils.ThemeHelper; +import com.sap.sailing.racecommittee.app.utils.TimeUtils; +import com.sap.sse.common.Util; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + public class ManagedRaceListAdapter extends ArrayAdapter implements FilterSubscriber { private final static String TAG = ManagedRaceListAdapter.class.getName(); @@ -102,7 +94,10 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple new RaceListDataTypeHeader(new RaceGroupSeries(race), mInflater, hasConflict)); } else { if (hasConflict) { - viewItemsSeriesHeaders.get(series).setHasConflict(true); + final RaceListDataTypeHeader header = viewItemsSeriesHeaders.get(series); + if (header != null) { + header.setHasConflict(true); + } } } } @@ -115,6 +110,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple } } + @NonNull @Override public RaceFilter getFilter() { return mFilter; @@ -137,17 +133,17 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple : getItem(position) instanceof RaceListDataTypeRace ? ViewType.RACE.index : -1); } + @NonNull @Override - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(int position, View convertView, @NonNull ViewGroup parent) { final RaceListDataType raceListElement = getItem(position); ViewHolder holder; int type = getItemViewType(position); - TimePoint now = MillisecondsTimePoint.now(); if (convertView == null) { - convertView = raceListElement.getView(parent); + convertView = raceListElement == null ? mInflater.inflate(R.layout.race_list_area_item, parent, false) : raceListElement.getView(parent); holder = new ViewHolder(); holder.findViews(convertView); convertView.setTag(R.id.race_list_holder, holder); @@ -158,96 +154,93 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple if (type == ViewType.HEADER.index) { final RaceListDataTypeHeader header = (RaceListDataTypeHeader) raceListElement; - String regatta = header.getRaceGroup().getDisplayName(); - String series = RaceHelper.getSeriesName(header.getSeries(), ""); + if (header != null) { + String regatta = header.getRaceGroup().getDisplayName(); + String series = RaceHelper.getSeriesName(header.getSeries(), ""); - if (!(raceListElement).equals(convertView.getTag(R.id.race_list_header))) { - if (TextUtils.isEmpty(regatta)) { - regatta = header.getRaceGroup().getName(); - } - holder.boat_class.setText(regatta); - holder.fleet_series.setText(series); - if (holder.fleet_series.getText().length() == 0) { - holder.fleet_series.setVisibility(View.GONE); - } else { - holder.fleet_series.setVisibility(View.VISIBLE); - } - holder.protest_image - .setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), flag_size)); - holder.protest_layout.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { + if (!(raceListElement).equals(convertView.getTag(R.id.race_list_header))) { + if (TextUtils.isEmpty(regatta)) { + regatta = header.getRaceGroup().getName(); + } + holder.boat_class.setText(regatta); + holder.fleet_series.setText(series); + if (holder.fleet_series.getText().length() == 0) { + holder.fleet_series.setVisibility(View.GONE); + } else { + holder.fleet_series.setVisibility(View.VISIBLE); + } + holder.protest_image + .setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), flag_size)); + holder.protest_layout.setOnClickListener(v -> { Intent intent = new Intent(AppConstants.INTENT_ACTION_SHOW_PROTEST); // TODO don't use toString() to convey semantics and perform check; bug 3617 intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, new RaceGroupSeries(header.getRaceGroup(), header.getSeries()).getDisplayName()); BroadcastManager.getInstance(getContext()).addIntent(intent); - } - }); - holder.protest_warning_image.setVisibility(header.hasConflict() ? View.VISIBLE : View.GONE); - convertView.setTag(R.id.race_list_header, raceListElement); + }); + holder.protest_warning_image.setVisibility(header.hasConflict() ? View.VISIBLE : View.GONE); + convertView.setTag(R.id.race_list_header, raceListElement); + } } } else if (type == ViewType.RACE.index) { final RaceListDataTypeRace race = (RaceListDataTypeRace) raceListElement; - - if (mSelectedRace != null && mSelectedRace.equals(race)) { - holder.setMarker(1); - if (race.isUpdateIndicatorVisible()) { - race.setUpdateIndicatorVisible(false); - } - } else { - holder.setMarker(0); - if (race.isUpdateIndicatorVisible()) { - holder.update_badge.setVisibility(View.VISIBLE); - } - } - holder.race_name.setText(RaceHelper.getReverseRaceFleetName(race.getRace())); - RaceState state = race.getRace().getState(); - holder.time.setRaceState(state); - if (state != null) { - CompetitorResults draft = state.getFinishPositioningList(); - CompetitorResults confirmed = state.getConfirmedFinishPositioningList().getCompetitorResults(); - holder.warning_sign.setVisibility( - ((draft != null && draft.hasConflicts()) || confirmed != null && confirmed.hasConflicts()) - ? View.VISIBLE - : View.GONE); - if (state.getStartTime() != null) { - int startRes = R.string.race_started; - if (state.getFinishedTime() == null) { - startRes = R.string.race_start; + if (race != null) { + if (mSelectedRace != null && mSelectedRace.equals(race)) { + holder.itemView.setSelected(true); + if (race.isUpdateIndicatorVisible()) { + race.setUpdateIndicatorVisible(false); } - String startTime = mResources.getString(startRes, TimeUtils.formatTime(state.getStartTime(), false)); - holder.race_started.setText(startTime); + } else { + holder.itemView.setSelected(false); + if (race.isUpdateIndicatorVisible()) { + holder.update_badge.setVisibility(View.VISIBLE); + } } - if (state.getFinishedTime() != null) { - holder.time.setVisibility(View.GONE); - holder.race_finished.setVisibility(View.VISIBLE); - holder.race_finished.setText(mResources.getString(R.string.race_finished, - TimeUtils.formatTime(state.getFinishedTime(), false))); - } - setDependingText(holder, race); - if (state.getStartTime() == null && state.getFinishedTime() == null) { - switch (race.getRace().getStatus()) { - case PRESCHEDULED: - holder.panel_right.setVisibility(View.GONE); - holder.race_scheduled.setVisibility(View.GONE); - holder.race_unscheduled.setVisibility(View.GONE); - break; - - default: - holder.race_scheduled.setVisibility(View.GONE); - holder.race_unscheduled.setVisibility(View.VISIBLE); + holder.race_name.setText(RaceHelper.getReverseRaceFleetName(race.getRace())); + RaceState state = race.getRace().getState(); + holder.race_flag_new.setRaceState(state); + holder.time.setRaceState(state); + if (state != null) { + CompetitorResults draft = state.getFinishPositioningList(); + CompetitorResults confirmed = state.getConfirmedFinishPositioningList().getCompetitorResults(); + holder.warning_sign.setVisibility( + ((draft != null && draft.hasConflicts()) || confirmed != null && confirmed.hasConflicts()) + ? View.VISIBLE + : View.GONE); + if (state.getStartTime() != null) { + int startRes = R.string.race_started; + if (state.getFinishedTime() == null) { + startRes = R.string.race_start; + } + String startTime = mResources.getString(startRes, TimeUtils.formatTime(state.getStartTime(), false)); + holder.race_started.setText(startTime); + } + if (state.getFinishedTime() != null) { + holder.race_flag_new.setVisibility(View.GONE); + holder.time.setVisibility(View.GONE); + holder.race_finished.setVisibility(View.VISIBLE); + holder.race_finished.setText(mResources.getString(R.string.race_finished, + TimeUtils.formatTime(state.getFinishedTime(), false))); + } + setDependingText(holder, race); + if (state.getStartTime() == null && state.getFinishedTime() == null) { + if (race.getRace().getStatus() == RaceLogRaceStatus.PRESCHEDULED) { + holder.panel_right.setVisibility(View.GONE); + holder.race_scheduled.setVisibility(View.GONE); + holder.race_unscheduled.setVisibility(View.GONE); + } else { + holder.race_scheduled.setVisibility(View.GONE); + holder.race_unscheduled.setVisibility(View.VISIBLE); + } } } - } - Double factor = race.getRace().getExplicitFactor(); - if (factor != null) { - holder.explicit_factor.setText(factor_format.format(factor)); - holder.explicit_factor.setVisibility(View.VISIBLE); + Double factor = race.getRace().getExplicitFactor(); + if (factor != null) { + holder.explicit_factor.setText(factor_format.format(factor)); + holder.explicit_factor.setVisibility(View.VISIBLE); + } } - - updateFlag(holder, race.getRace(), now); } return convertView; } @@ -311,24 +304,25 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple }); for (final RaceListDataTypeHeader header : headers) { result.add(header); - final List raceItems = new ArrayList<>(raceItemsByHeader.get(header)); - Collections.sort(raceItems, new Comparator() { - final CurrentRaceComparator c = new CurrentRaceComparator(); + final List items = raceItemsByHeader.get(header); + if (items != null) { + final List raceItems = new ArrayList<>(items); + Collections.sort(raceItems, new Comparator() { + final CurrentRaceComparator c = new CurrentRaceComparator(); - @Override - public int compare(RaceListDataTypeRace lhs, RaceListDataTypeRace rhs) { - final int result; - if (lhs != null && rhs != null) { - result = c.compare(lhs.getRace(), rhs.getRace()); - } else { - Log.e(TAG, "Internal error; found null for NavDrawer item while sorting"); - result = 0; + @Override + public int compare(RaceListDataTypeRace lhs, RaceListDataTypeRace rhs) { + final int result; + if (lhs != null && rhs != null) { + result = c.compare(lhs.getRace(), rhs.getRace()); + } else { + Log.e(TAG, "Internal error; found null for NavDrawer item while sorting"); + result = 0; + } + return result; } - return result; - } - }); - for (final RaceListDataTypeRace raceItem : raceItems) { - result.add(raceItem); + }); + result.addAll(raceItems); } } return result; @@ -341,12 +335,12 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple final SeriesBase series = race.getSeries(); final RaceListDataTypeHeader seriesHeader = viewItemsSeriesHeaders.get(series); List raceItemsInSeries = raceItemsByHeader.get(seriesHeader); - if (raceItemsInSeries == null) { + if (seriesHeader != null && raceItemsInSeries == null) { raceItemsInSeries = new ArrayList<>(); raceItemsByHeader.put(seriesHeader, raceItemsInSeries); } final RaceListDataTypeRace viewItemForRace = viewItemsRaces.get(race); - if (viewItemForRace != null) { + if (raceItemsInSeries != null && viewItemForRace != null) { raceItemsInSeries.add(viewItemForRace); } else { Log.w(TAG, "A view item for race " + race + " provided by the filter could not be found", @@ -373,9 +367,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple if (holder.race_flag != null) { holder.race_flag.setVisibility(View.GONE); } - if (holder.time != null) { - holder.time.setVisibility(View.VISIBLE); - } if (holder.race_started != null) { holder.race_started.setText(""); } @@ -388,9 +379,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple if (holder.race_unscheduled != null) { holder.race_unscheduled.setVisibility(View.GONE); } - if (holder.race_name != null) { - holder.race_name.setTextColor(ThemeHelper.getColor(getContext(), R.attr.black)); - } if (holder.has_dependent_races != null) { holder.has_dependent_races.setVisibility(View.GONE); } @@ -404,90 +392,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple if (holder.warning_sign != null) { holder.warning_sign.setVisibility(View.GONE); } - holder.setMarker(0); - } - - private void updateFlag(ViewHolder holder, ManagedRace race, TimePoint now) { - RaceState state = race.getState(); - if (state == null || state.getStartTime() == null) { - return; - } - - RacingProcedure procedure = state.getTypedRacingProcedure(); - LayerDrawable flag = null; - Drawable arrow = null; - String timer = null; - if (!procedure.isIndividualRecallDisplayed()) { - FlagPoleState poleState = state.getRacingProcedure().getActiveFlags(state.getStartTime(), now); - List currentState = poleState.getCurrentState(); - List upcoming = poleState.computeUpcomingChanges(); - FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming); - TimePoint change = poleState.getNextStateValidFrom(); - Flags currentFlag; - - if (change != null) { - for (FlagPole pole : currentState) { - int isNext = 0; - - currentFlag = pole.getUpperFlag(); - if (isNextFlag(currentFlag, nextPole)) { - isNext = 1; - } else { - currentFlag = pole.getLowerFlag(); - if (!Flags.NONE.equals(currentFlag)) { - if (isNextFlag(currentFlag, nextPole)) { - isNext = 2; - } - } - } - - if (isNext != 0) { - flag = FlagsResources.getFlagDrawable(getContext(), currentFlag.name(), flag_size); - switch (isNext) { - case 1: - if (nextPole.isDisplayed()) { - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); - } else { - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); - } - break; - - case 2: - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); - break; - - default: - ExLog.i(getContext(), TAG, "unknown flag"); - } - timer = TimeUtils.formatDuration(now, poleState.getNextStateValidFrom()); - } - } - } else if (state.getStatus() == RaceLogRaceStatus.FINISHING) { - if (!currentState.isEmpty()) { - flag = FlagsResources.getFlagDrawable(getContext(), currentState.get(0).getUpperFlag().name(), - flag_size); - } else { - flag = null; - } - arrow = null; - timer = TimeUtils.formatDurationSince(now.minus(state.getFinishingTime().asMillis()).asMillis(), false); - } - } else { - TimePoint flagDown = procedure.getIndividualRecallRemovalTime(); - if (now.before(flagDown)) { - flag = FlagsResources.getFlagDrawable(getContext(), Flags.XRAY.name(), flag_size); - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); - timer = TimeUtils.formatDuration(now, flagDown); - } - } - if (timer != null) { - timer = timer.replace("-", ""); - } - holder.showFlag(flag, arrow, timer, state); - } - - private boolean isNextFlag(Flags flag, FlagPole pole) { - return pole != null && flag.equals(pole.getUpperFlag()); } public void onRacesChanged() { @@ -509,35 +413,34 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple private static class ViewHolder { - public ImageView marker; - public RaceTimeView time; - public TextView race_finished; - /* package */ ViewGroup panel_left; - /* package */ ViewGroup panel_right; - /* package */ ImageView current_flag; - /* package */ ImageView update_badge; - /* package */ LinearLayout race_flag; - /* package */ TextView race_name; - /* package */ TextView race_started; - /* package */ LinearLayout race_scheduled; - /* package */ TextView race_unscheduled; - /* package */ FlagTimeView flag_timer; - /* package */ View protest_layout; - /* package */ ImageView protest_image; - /* package */ ImageView protest_warning_image; - /* package */ TextView boat_class; - /* package */ TextView fleet_series; - /* package */ ImageView has_dependent_races; - /* package */ TextView depends_on; - /* package */ TextView explicit_factor; - /* package */ ImageView warning_sign; + private View itemView; + private RaceTimeView time; + private TextView race_finished; + private ViewGroup panel_left; + private ViewGroup panel_right; + private ImageView update_badge; + private FlagTimeView race_flag_new; + private LinearLayout race_flag; + private TextView race_name; + private TextView race_started; + private LinearLayout race_scheduled; + private TextView race_unscheduled; + private View protest_layout; + private ImageView protest_image; + private ImageView protest_warning_image; + private TextView boat_class; + private TextView fleet_series; + private ImageView has_dependent_races; + private TextView depends_on; + private TextView explicit_factor; + private ImageView warning_sign; - /* package */ void findViews(View layout) { + private void findViews(View layout) { + itemView = layout; panel_left = ViewHelper.get(layout, R.id.panel_left); panel_right = ViewHelper.get(layout, R.id.panel_right); - marker = ViewHelper.get(layout, R.id.race_marker); - current_flag = ViewHelper.get(layout, R.id.current_flag); update_badge = ViewHelper.get(layout, R.id.update_badge); + race_flag_new = ViewHelper.get(layout, R.id.race_flag); race_flag = ViewHelper.get(layout, R.id.race_flag); time = ViewHelper.get(layout, R.id.time); race_name = ViewHelper.get(layout, R.id.race_name); @@ -545,7 +448,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple race_started = ViewHelper.get(layout, R.id.race_started); race_scheduled = ViewHelper.get(layout, R.id.race_scheduled); race_unscheduled = ViewHelper.get(layout, R.id.race_unscheduled); - flag_timer = ViewHelper.get(layout, R.id.flag_timer); protest_layout = ViewHelper.get(layout, R.id.protest_layout); protest_image = ViewHelper.get(layout, R.id.protest_image); protest_warning_image = ViewHelper.get(layout, R.id.protest_warning_image); @@ -556,24 +458,5 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple explicit_factor = ViewHelper.get(layout, R.id.explicit_factor); warning_sign = ViewHelper.get(layout, R.id.panel_additional_image); } - - /* package */ void showFlag(final LayerDrawable flag, final Drawable arrow, final String timer, final RaceState state) { - if (flag != null && timer != null) { - current_flag.setImageDrawable(flag); - flag_timer.setRaceState(state); - flag_timer.setTimer(timer); - flag_timer.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null, null); - race_flag.setVisibility(View.VISIBLE); - } - } - - /* package */ void setMarker(int level) { - if (marker != null) { - Drawable drawable = marker.getDrawable(); - if (drawable != null) { - drawable.setLevel(level); - } - } - } } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 2e941e61732..699f599eb1d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -1,6 +1,5 @@ package com.sap.sailing.racecommittee.app.ui.fragments; -import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -8,22 +7,19 @@ import android.content.IntentFilter; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; -import android.support.v7.widget.Toolbar; import android.text.SpannableString; import android.text.Spanned; import android.text.style.StyleSpan; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; @@ -61,13 +57,14 @@ import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Set; -public class RaceListFragment extends LoggableFragment - implements OnItemClickListener, OnItemSelectedListener, OnScrollListener { +public class RaceListFragment extends LoggableFragment implements OnItemClickListener, OnItemSelectedListener { private final static String TAG = RaceListFragment.class.getName(); private final static String LAYOUT = "layout"; + private ManagedRaceListAdapter mAdapter; private RaceListCallbacks mCallbacks; private Button mCurrentRacesButton; @@ -77,21 +74,21 @@ public class RaceListFragment extends LoggableFragment private ImageView mRefresh; private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; + private Runnable mRunnable; private FilterMode mFilterMode; private ListView mListView; private LinkedHashMap mManagedRacesById; private LinkedHashMap> mRacesByGroup; private ManagedRace mSelectedRace; private IntentReceiver mReceiver; - private boolean mUpdateList = true; private View mProgress; private final Set mAllRaces; private BaseRaceStateChangedListener stateListener = new BaseRaceStateChangedListener() { @Override - public void onFinishingPositioningsChanged(ReadonlyRaceState state) { - super.onFinishingPositioningsChanged(state); + public void onFinishingPositionsChanged(ReadonlyRaceState state) { + super.onFinishingPositionsChanged(state); update(state); } @@ -167,24 +164,20 @@ public class RaceListFragment extends LoggableFragment mAdapter.notifyDataSetChanged(); if (mCurrentRacesButton != null && mAllRacesButton != null) { - int colorGrey = ThemeHelper.getColor(getActivity(), R.attr.sap_light_gray); - int colorOrange = ThemeHelper.getColor(getActivity(), R.attr.sap_yellow_1); + int colorGrey = ThemeHelper.getColor(requireContext(), R.attr.sap_light_gray); + int colorOrange = ThemeHelper.getColor(requireContext(), R.attr.sap_yellow_1); mCurrentRacesButton.setTextColor(colorGrey); mAllRacesButton.setTextColor(colorGrey); BitmapHelper.setBackground(mCurrentRacesButton, null); BitmapHelper.setBackground(mAllRacesButton, null); - Drawable drawable = ContextCompat.getDrawable(getActivity(), R.drawable.nav_drawer_tab_button); - switch (getFilterMode()) { - case ALL: + Drawable drawable = ContextCompat.getDrawable(requireContext(), R.drawable.nav_drawer_tab_button); + if (getFilterMode() == FilterMode.ALL) { mAllRacesButton.setTextColor(colorOrange); BitmapHelper.setBackground(mAllRacesButton, drawable); - break; - - default: + } else { mCurrentRacesButton.setTextColor(colorOrange); BitmapHelper.setBackground(mCurrentRacesButton, drawable); - break; } } } @@ -212,18 +205,18 @@ public class RaceListFragment extends LoggableFragment } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); try { - mCallbacks = (RaceListCallbacks) activity; + mCallbacks = (RaceListCallbacks) context; } catch (ClassCastException ex) { ExLog.ex(getActivity(), TAG, ex); } } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { int layout = R.layout.race_list_vertical; if (getArguments() != null && getArguments().getInt(LAYOUT) != 0) { layout = getArguments().getInt(LAYOUT); @@ -231,48 +224,29 @@ public class RaceListFragment extends LoggableFragment View view = inflater.inflate(layout, container, false); mReceiver = new IntentReceiver(); - mListView = (ListView) view.findViewById(R.id.listView); - mListView.setOnScrollListener(this); + mListView = view.findViewById(R.id.listView); mProgress = view.findViewById(R.id.progress); - mCurrentRacesButton = (Button) view.findViewById(R.id.races_current); + mCurrentRacesButton = view.findViewById(R.id.races_current); if (mCurrentRacesButton != null) { mCurrentRacesButton.setTypeface(Typeface.DEFAULT_BOLD); - mCurrentRacesButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - setFilterMode(FilterMode.ACTIVE); - } - }); + mCurrentRacesButton.setOnClickListener(v -> setFilterMode(FilterMode.ACTIVE)); } - mAllRacesButton = (Button) view.findViewById(R.id.races_all); + mAllRacesButton = view.findViewById(R.id.races_all); if (mAllRacesButton != null) { mAllRacesButton.setTypeface(Typeface.DEFAULT_BOLD); - mAllRacesButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - setFilterMode(FilterMode.ALL); - } - }); + mAllRacesButton.setOnClickListener(v -> setFilterMode(FilterMode.ALL)); } - mCourse = (TextView) view.findViewById(R.id.regatta_course); - mData = (TextView) view.findViewById(R.id.regatta_data); + mCourse = view.findViewById(R.id.regatta_course); + mData = view.findViewById(R.id.regatta_data); - mRefresh = (ImageView) view.findViewById(R.id.nav_button); + mRefresh = view.findViewById(R.id.nav_button); if (mRefresh != null) { - mRefresh.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - BroadcastManager.getInstance(getActivity()) - .addIntent(new Intent(AppConstants.INTENT_ACTION_RELOAD_RACES)); - } - }); + mRefresh.setOnClickListener(v -> BroadcastManager.getInstance(getActivity()) + .addIntent(new Intent(AppConstants.INTENT_ACTION_RELOAD_RACES))); } view.setClickable(true); @@ -289,11 +263,15 @@ public class RaceListFragment extends LoggableFragment @Override public void onItemClick(AdapterView parent, View view, int position, long id) { - ExLog.i(getActivity(), TAG, "Touched " + mAdapter.getItem(position).toString()); + final RaceListDataType item = mAdapter.getItem(position); + if (item != null) { + ExLog.i(getActivity(), TAG, "Touched " + item.toString()); + mAdapter.setSelectedRace(item); + mAdapter.notifyDataSetChanged(); + mRunnable = () -> mCallbacks.onRaceListItemSelected(mAdapter.getItem(position)); + } mDrawerLayout.closeDrawers(); - mAdapter.setSelectedRace(mAdapter.getItem(position)); - mCallbacks.onRaceListItemSelected(mAdapter.getItem(position)); } @Override @@ -310,7 +288,7 @@ public class RaceListFragment extends LoggableFragment public void onPause() { super.onPause(); - LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mReceiver); + LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver); } @Override @@ -319,26 +297,7 @@ public class RaceListFragment extends LoggableFragment IntentFilter filter = new IntentFilter(); filter.addAction(AppConstants.INTENT_ACTION_SHOW_PROTEST); - LocalBroadcastManager.getInstance(getActivity()).registerReceiver(mReceiver, filter); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - // TODO Auto-generated method stub - - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - switch (scrollState) { - case SCROLL_STATE_FLING: - case SCROLL_STATE_TOUCH_SCROLL: - mUpdateList = false; - break; - - default: - mUpdateList = true; - } + LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filter); } @Override @@ -371,30 +330,25 @@ public class RaceListFragment extends LoggableFragment public void setUp(DrawerLayout drawerLayout, String course, String author) { mDrawerLayout = drawerLayout; - mDrawerLayout.setStatusBarBackgroundColor(ThemeHelper.getColor(getActivity(), R.attr.colorPrimaryDark)); - mDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, - (Toolbar) getActivity().findViewById(R.id.toolbar), R.string.nav_drawer_open, + mDrawerLayout.setStatusBarBackgroundColor(ThemeHelper.getColor(requireContext(), R.attr.colorPrimaryDark)); + mDrawerToggle = new ActionBarDrawerToggle(requireActivity(), mDrawerLayout, + requireActivity().findViewById(R.id.toolbar), R.string.nav_drawer_open, R.string.nav_drawer_close) { + @Override public void onDrawerClosed(View drawerView) { super.onDrawerClosed(drawerView); - mUpdateList = false; - } - - @Override - public void onDrawerOpened(View drawerView) { - super.onDrawerOpened(drawerView); - mUpdateList = true; + if (mRunnable != null) { + drawerLayout.post(() -> { + mRunnable.run(); + mRunnable = null; + }); + } } }; - mDrawerLayout.post(new Runnable() { - @Override - public void run() { - mDrawerToggle.syncState(); - } - }); - mDrawerLayout.setDrawerListener(mDrawerToggle); + mDrawerLayout.post(() -> mDrawerToggle.syncState()); + mDrawerLayout.addDrawerListener(mDrawerToggle); if (mCourse != null) { SpannableString text = new SpannableString(course); StyleSpan spanBold = new StyleSpan(Typeface.BOLD); @@ -407,8 +361,7 @@ public class RaceListFragment extends LoggableFragment } public void setupOn(Iterable races) { - ExLog.i(getActivity(), TAG, - String.format("Setting up %s with %d races.", this.getClass().getSimpleName(), Util.size(races))); + ExLog.i(getActivity(), TAG, String.format(Locale.getDefault(), "Setting up %s with %d races.", this.getClass().getSimpleName(), Util.size(races))); unregisterOnAllRaces(); mManagedRacesById.clear(); mRacesByGroup.clear(); @@ -428,7 +381,7 @@ public class RaceListFragment extends LoggableFragment // prepare views and do initial filtering; update the adapter first, so it can create the new // view items for the new races; then trigger the filter mAdapter.onRacesChanged(); - mAdapter.notifyDataSetChanged(); + resetSelectedRace(); filterChanged(); updateConflictSign(); } @@ -467,6 +420,8 @@ public class RaceListFragment extends LoggableFragment public void resetSelectedRace() { mSelectedRace = null; + final int position = mListView.getCheckedItemPosition(); + mListView.setItemChecked(position, false); mAdapter.setSelectedRace(null); mAdapter.notifyDataSetChanged(); } @@ -481,30 +436,37 @@ public class RaceListFragment extends LoggableFragment // Find the race group for which the List races = new ArrayList<>(); for (RaceGroupSeriesFleet raceGroupSeriesFleet : mRacesByGroup.keySet()) { - Boolean matchingRaceGroup = raceGroupSeriesDisplayName + boolean matchingRaceGroup = raceGroupSeriesDisplayName .equals(new RaceGroupSeries(raceGroupSeriesFleet.getRaceGroup(), raceGroupSeriesFleet.getSeries()) .getDisplayName()); if (matchingRaceGroup) { - if (!isRaceListDirty(races)) { + if (races != null && !isRaceListDirty(races)) { // collect all races for a single fragment in case of portrait mode; // show multiple fragments after one another in case of non-portrait (landscape) mode - View view = getActivity().findViewById(R.id.protest_time_fragment); + View view = requireActivity().findViewById(R.id.protest_time_fragment); if (AppUtils.with(getActivity()).isPortrait() && view != null) { - races.addAll(mRacesByGroup.get(raceGroupSeriesFleet)); + final List racesByGroup = mRacesByGroup.get(raceGroupSeriesFleet); + if (racesByGroup != null) { + races.addAll(racesByGroup); + } } else { races = mRacesByGroup.get(raceGroupSeriesFleet); - ProtestTimeDialogFragment fragment = ProtestTimeDialogFragment.newInstance(races); - fragment.show(getFragmentManager(), null); + if (races != null) { + ProtestTimeDialogFragment fragment = ProtestTimeDialogFragment.newInstance(races); + fragment.show(requireFragmentManager(), null); + } } } } } if (AppUtils.with(getActivity()).isPortrait() - && (getActivity().findViewById(R.id.protest_time_fragment)) != null) { - ProtestTimeDialogFragment fragment = ProtestTimeDialogFragment.newInstance(races); - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - transaction.replace(R.id.protest_time_fragment, fragment); - transaction.commit(); + && (requireActivity().findViewById(R.id.protest_time_fragment)) != null) { + if (races != null) { + ProtestTimeDialogFragment fragment = ProtestTimeDialogFragment.newInstance(races); + FragmentTransaction transaction = requireFragmentManager().beginTransaction(); + transaction.replace(R.id.protest_time_fragment, fragment); + transaction.commit(); + } } } @@ -513,7 +475,7 @@ public class RaceListFragment extends LoggableFragment for (ManagedRace race : races) { // check for data consistency if race is still in data store and not only in fragment if (manager.getDataStore().getRace(race.getId()) == null) { - SessionActivity sessionActivity = (SessionActivity) getActivity(); + SessionActivity sessionActivity = (SessionActivity) requireActivity(); sessionActivity.forceLogout(); return true; } @@ -527,12 +489,10 @@ public class RaceListFragment extends LoggableFragment mRefresh.setEnabled(false); mProgress.setVisibility(View.VISIBLE); mListView.setVisibility(View.GONE); - mUpdateList = false; } else { mRefresh.setEnabled(true); mProgress.setVisibility(View.GONE); mListView.setVisibility(View.VISIBLE); - mUpdateList = true; } } } @@ -546,6 +506,7 @@ public class RaceListFragment extends LoggableFragment this.displayName = RaceApplication.getStringContext().getString(resId); } + @NonNull @Override public String toString() { return displayName; @@ -561,9 +522,9 @@ public class RaceListFragment extends LoggableFragment @Override public void onReceive(Context context, Intent intent) { if (AppConstants.INTENT_ACTION_SHOW_PROTEST.equals(intent.getAction())) { - String raceGroupSeriesDisplayName = intent.getExtras().getString(AppConstants.INTENT_ACTION_EXTRA); + String raceGroupSeriesDisplayName = intent.getStringExtra(AppConstants.INTENT_ACTION_EXTRA); if (raceGroupSeriesDisplayName != null) { - showProtestTimeDialog(raceGroupSeriesDisplayName); + mRunnable = () -> showProtestTimeDialog(raceGroupSeriesDisplayName); mDrawerLayout.closeDrawers(); } else { ExLog.e(getActivity(), TAG, diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/BaseTimeView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/BaseTimeView.java deleted file mode 100644 index 5a49770ed2d..00000000000 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/BaseTimeView.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.sap.sailing.racecommittee.app.ui.views; - -import android.content.Context; -import android.support.annotation.Nullable; -import android.util.AttributeSet; -import com.sap.sailing.racecommittee.app.utils.TickListener; -import com.sap.sailing.racecommittee.app.utils.TickSingleton; - -abstract public class BaseTimeView extends android.support.v7.widget.AppCompatTextView implements TickListener { - - public BaseTimeView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - TickSingleton.INSTANCE.registerListener(this); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - TickSingleton.INSTANCE.unregisterListener(this); - } -} diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java index cc480a8e5a7..50c4c7c507b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java @@ -1,41 +1,163 @@ package com.sap.sailing.racecommittee.app.ui.views; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.text.TextUtils; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; -import android.util.TypedValue; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.shared.util.BitmapHelper; import com.sap.sailing.domain.abstractlog.race.state.RaceState; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; +import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; +import com.sap.sailing.domain.common.racelog.FlagPole; +import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources; +import com.sap.sailing.racecommittee.app.utils.TickListener; +import com.sap.sailing.racecommittee.app.utils.TickSingleton; import com.sap.sailing.racecommittee.app.utils.TimeUtils; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +import java.util.List; + +public class FlagTimeView extends LinearLayout implements TickListener { + + private static final String TAG = FlagTimeView.class.getSimpleName(); + + private final ImageView imageView; + private final TextView textView; + + private final int flagSize; -public class FlagTimeView extends BaseTimeView { private RaceState state; - private String timer; - public FlagTimeView(@NonNull final Context context, final @Nullable AttributeSet attrs) { + public FlagTimeView(Context context, AttributeSet attrs) { super(context, attrs); + setOrientation(LinearLayout.VERTICAL); + inflate(context, R.layout.layout_flag_time, this); + imageView = findViewById(R.id.imageView); + textView = findViewById(R.id.textView); + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlagTimeView, 0, 0); + try { + flagSize = a.getInteger(R.styleable.FlagTimeView_flagSize, 0); + } finally { + a.recycle(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + TickSingleton.INSTANCE.registerListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + TickSingleton.INSTANCE.unregisterListener(this); } public void setRaceState(RaceState state) { this.state = state; - } - - public void setTimer(String timer) { - this.timer = timer; + notifyTick(MillisecondsTimePoint.now()); } @Override public void notifyTick(TimePoint now) { - if (state == null) { return; } - if (state.getStatus() == RaceLogRaceStatus.FINISHING) { - final String time = TimeUtils.formatDurationSince(now.minus(state.getFinishingTime().asMillis()).asMillis()); - setText(time); - } else { - setText(timer); + if (state == null || state.getStartTime() == null || state.getFinishedTime() != null) { + setVisibility(View.GONE); + return; } + + RacingProcedure procedure = state.getTypedRacingProcedure(); + LayerDrawable flag = null; + Drawable arrow = null; + String duration = null; + if (!procedure.isIndividualRecallDisplayed()) { + FlagPoleState poleState = state.getRacingProcedure().getActiveFlags(state.getStartTime(), now); + List currentState = poleState.getCurrentState(); + List upcoming = poleState.computeUpcomingChanges(); + FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming); + TimePoint change = poleState.getNextStateValidFrom(); + Flags currentFlag; + + if (change != null) { + for (FlagPole pole : currentState) { + int isNext = 0; + + currentFlag = pole.getUpperFlag(); + if (isNextFlag(currentFlag, nextPole)) { + isNext = 1; + } else { + currentFlag = pole.getLowerFlag(); + if (!Flags.NONE.equals(currentFlag)) { + if (isNextFlag(currentFlag, nextPole)) { + isNext = 2; + } + } + } + + if (isNext != 0) { + flag = FlagsResources.getFlagDrawable(getContext(), currentFlag.name(), flagSize); + switch (isNext) { + case 1: + if (nextPole.isDisplayed()) { + arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); + } else { + arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); + } + break; + + case 2: + arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); + break; + + default: + ExLog.i(getContext(), TAG, "unknown flag"); + } + duration = TimeUtils.formatDuration(now, poleState.getNextStateValidFrom()); + } + } + } else if (state.getStatus() == RaceLogRaceStatus.FINISHING) { + if (!currentState.isEmpty()) { + flag = FlagsResources.getFlagDrawable(getContext(), currentState.get(0).getUpperFlag().name(), + flagSize); + } + arrow = null; + duration = TimeUtils.formatDurationSince(now.minus(state.getFinishingTime().asMillis()).asMillis(), false); + } + } else { + TimePoint flagDown = procedure.getIndividualRecallRemovalTime(); + if (now.before(flagDown)) { + flag = FlagsResources.getFlagDrawable(getContext(), Flags.XRAY.name(), flagSize); + arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); + duration = TimeUtils.formatDuration(now, flagDown); + } + } + if (duration != null) { + duration = duration.replace("-", ""); + } + + if (flag == null) { + setVisibility(View.GONE); + return; + } + + setVisibility(View.VISIBLE); + imageView.setImageDrawable(flag); + textView.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null, null); + textView.setText(duration); + } + + private boolean isNextFlag(Flags flag, FlagPole pole) { + return pole != null && flag.equals(pole.getUpperFlag()); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java index 17a48e1f029..48f1cb972d3 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java @@ -2,16 +2,17 @@ package com.sap.sailing.racecommittee.app.ui.views; import android.content.Context; import android.support.annotation.Nullable; -import android.text.TextUtils; import android.util.AttributeSet; -import android.util.TypedValue; +import android.view.View; import com.sap.sailing.domain.abstractlog.race.state.RaceState; -import com.sap.sailing.racecommittee.app.R; +import com.sap.sailing.racecommittee.app.utils.TickListener; +import com.sap.sailing.racecommittee.app.utils.TickSingleton; import com.sap.sailing.racecommittee.app.utils.TimeUtils; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; -public class RaceTimeView extends BaseTimeView { +public class RaceTimeView extends android.support.v7.widget.AppCompatTextView implements TickListener { private RaceState state; @@ -19,24 +20,31 @@ public class RaceTimeView extends BaseTimeView { super(context, attrs); } + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + TickSingleton.INSTANCE.registerListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + TickSingleton.INSTANCE.unregisterListener(this); + } + public void setRaceState(RaceState state) { this.state = state; + notifyTick(MillisecondsTimePoint.now()); } @Override public void notifyTick(TimePoint now) { - if (state == null) { - return; - } - if (state.getStartTime() == null) { + if (state == null || state.getStartTime() == null) { + setVisibility(View.GONE); return; } + setVisibility(View.VISIBLE); String duration = TimeUtils.formatDuration(now, state.getStartTime()); setText(duration); - float textSize = getContext().getResources().getDimension(R.dimen.textSize_40); - if (!TextUtils.isEmpty(duration) && duration.length() >= 6) { - textSize = getContext().getResources().getDimension(R.dimen.textSize_32); - } - setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } } From cb6f6da9a6dd958706f31ec5c574f4f97317b71b Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 23 Apr 2020 14:06:04 +0200 Subject: [PATCH 007/235] style: reformat layout --- .../res/layout/race_list_header_content.xml | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_header_content.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_header_content.xml index 30368b5a106..e5ae403ac70 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_header_content.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_list_header_content.xml @@ -1,21 +1,22 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:parentTag="android.widget.RelativeLayout"> + android:src="@drawable/nav_drawer_header" /> + android:src="@drawable/gradient" /> + android:src="@drawable/ic_refresh_48dp" /> + tools:text="Marina Bay / Viewer" /> - - \ No newline at end of file + tools:text="Extreme Sailing Series 2015 Act 1 Singapore" /> + From a621808a6d7dfbdde4c3faeb7acccd7ee4075464 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:03:28 +0200 Subject: [PATCH 008/235] style: clean up and reformat app resources --- .../res/layout/mark_listview_row.xml | 6 +-- .../res/layout-land/fragment_home.xml | 5 +- .../res/layout/fragment_home.xml | 5 +- .../res/layout/ragatta_listview_row.xml | 10 ++-- .../res/values-de/strings.xml | 5 +- .../res/values-es/strings.xml | 32 +++--------- .../res/values-fr/strings.xml | 31 +++--------- .../res/values-it/strings.xml | 33 +++---------- .../res/values-ja/strings.xml | 33 +++---------- .../res/values-pt/strings.xml | 33 +++---------- .../res/values-ru/strings.xml | 33 +++---------- .../res/values-zh/strings.xml | 33 +++---------- .../res/values/strings.xml | 6 +-- .../res/values-de/strings.xml | 4 -- .../res/values-es/strings.xml | 47 +++++++----------- .../res/values-fr/strings.xml | 49 +++++++------------ .../res/values-it/strings.xml | 47 +++++++----------- .../res/values-ja/strings.xml | 33 +++++-------- .../res/values-pt/strings.xml | 47 +++++++----------- .../res/values-ru/strings.xml | 37 +++++--------- .../res/values-zh/strings.xml | 33 +++++-------- .../res/values/strings.xml | 9 +--- .../res/xml/preference_course_designer.xml | 23 ++++----- .../res/xml/preference_general.xml | 29 ++++++----- .../res/xml/preference_regatta_defaults.xml | 30 +++--------- .../res/xml/preference_regatta_specific.xml | 8 +-- .../fragments/panels/TimePanelFragment.java | 11 ++--- .../fragments/raceinfo/StartTimeFragment.java | 13 ++--- 28 files changed, 213 insertions(+), 472 deletions(-) diff --git a/mobile/com.sap.sailing.android.buoy.positioning.app/res/layout/mark_listview_row.xml b/mobile/com.sap.sailing.android.buoy.positioning.app/res/layout/mark_listview_row.xml index 8663f78eeac..ff2037fe4c1 100644 --- a/mobile/com.sap.sailing.android.buoy.positioning.app/res/layout/mark_listview_row.xml +++ b/mobile/com.sap.sailing.android.buoy.positioning.app/res/layout/mark_listview_row.xml @@ -18,7 +18,7 @@ android:layout_marginBottom="10dp" android:layout_marginEnd="@dimen/list_arrow_right_margin" android:layout_marginRight="@dimen/list_arrow_right_margin" - android:contentDescription="@string/empty" + android:contentDescription="@null" android:src="@drawable/white_arrow_right" android:textColor="@color/fiori_text_color" /> @@ -49,7 +49,5 @@ android:maxLines="1" android:textColor="@color/fiori_text_color" android:textSize="14sp" /> - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/layout-land/fragment_home.xml b/mobile/com.sap.sailing.android.shared/res/layout-land/fragment_home.xml index 69fde6d5383..25d6d819a51 100644 --- a/mobile/com.sap.sailing.android.shared/res/layout-land/fragment_home.xml +++ b/mobile/com.sap.sailing.android.shared/res/layout-land/fragment_home.xml @@ -10,7 +10,7 @@ android:layout_height="match_parent" android:layout_above="@+id/two_buttons_layout" android:layout_alignParentTop="true" - android:contentDescription="@string/empty" + android:contentDescription="@null" android:scaleType="centerCrop" android:src="@drawable/working_title" /> @@ -67,5 +67,4 @@ android:layout_alignParentTop="true" android:divider="@color/separator_color" android:dividerHeight="1dp" /> - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/layout/fragment_home.xml b/mobile/com.sap.sailing.android.shared/res/layout/fragment_home.xml index 6b5d6ff4268..ce28b25e5ae 100644 --- a/mobile/com.sap.sailing.android.shared/res/layout/fragment_home.xml +++ b/mobile/com.sap.sailing.android.shared/res/layout/fragment_home.xml @@ -9,7 +9,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/scanQr" - android:contentDescription="@string/empty" + android:contentDescription="@null" android:scaleType="centerCrop" android:src="@drawable/working_title" /> @@ -70,5 +70,4 @@ android:layout_alignParentTop="true" android:divider="@color/separator_color" android:dividerHeight="1dp" /> - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/layout/ragatta_listview_row.xml b/mobile/com.sap.sailing.android.shared/res/layout/ragatta_listview_row.xml index 4f5b4365599..a9a1d2cb7c0 100644 --- a/mobile/com.sap.sailing.android.shared/res/layout/ragatta_listview_row.xml +++ b/mobile/com.sap.sailing.android.shared/res/layout/ragatta_listview_row.xml @@ -1,11 +1,10 @@ - + tools:ignore="Overdraw"> - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml index 6b5907753ff..68f4a0d0d6d 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-de/strings.xml @@ -1,6 +1,5 @@ - - + Verbindungsinformationen Entfernen Neues Element eingeben @@ -60,4 +59,4 @@ Gestern Heute Morgen - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-es/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-es/strings.xml index fbdfab4bbcd..ce082c81b78 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-es/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-es/strings.xml @@ -1,7 +1,5 @@ - - - - + + "Información de conexión" "Eliminar" "Introducir posición nueva" @@ -9,17 +7,9 @@ "Todavía no hay posiciones" - - - - - - - - + - "Error" "Info" @@ -45,9 +35,7 @@ "Se ha producido un error al ponerse en contacto con el servidor. Inténtelo de nuevo." "Espere" "Código QR no válido" - - - + "EULA" @@ -61,10 +49,7 @@ "Información de licencia" - - - - + "Sí" @@ -83,9 +68,4 @@ "Ayer" "Hoy" "Mañana" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-fr/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-fr/strings.xml index a75b592f2ea..bff070ed388 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-fr/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-fr/strings.xml @@ -1,7 +1,5 @@ - - - - + + "Informations de connexion" "Effacer" "Entrez un nouvel élément." @@ -9,17 +7,10 @@ "Il n\'y a pas encore d\'élément." - - - - - - - - + - + "Erreur" "Informations" @@ -45,9 +36,7 @@ "Une erreur s\'est produite lors de l\'appel du serveur. Veuillez réessayer." "Veuillez patienter..." "Code QR non valide" - - - + "Contrat de licence utilisateur final" @@ -61,10 +50,7 @@ "Informations sur la licence" - - - - + "Oui" @@ -83,9 +69,4 @@ "Hier" "Aujourd\'hui" "Demain" - - - - - \ No newline at end of file diff --git a/mobile/com.sap.sailing.android.shared/res/values-it/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-it/strings.xml index 94aea70a31d..2e6cb063fc9 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-it/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-it/strings.xml @@ -1,7 +1,5 @@ - - - - + + "Informazioni sulla connessione" "Rimuovi" "Inserisci nuovo elemento" @@ -9,17 +7,10 @@ "Non c\'è ancora nessun elemento." - - - - - - - - + - + "Errore" "Info" @@ -45,9 +36,7 @@ "Si è verificato un errore nel tentativo di contattare il server. Riprovare." "Attendi" "Codice QR non valido" - - - + "Licenza per utente finale" @@ -61,10 +50,7 @@ "Informazioni sulla licenza" - - - - + "Sì" @@ -83,9 +69,4 @@ "Ieri" "Oggi" "Domani" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-ja/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-ja/strings.xml index 8a0413d3438..56139a5c0b0 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-ja/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-ja/strings.xml @@ -1,7 +1,5 @@ - - - - + + "接続情報" "削除" "新規アイテム入力" @@ -9,17 +7,10 @@ "アイテムがまだありません。" - - - - - - - - + - + "エラー" "情報" @@ -45,9 +36,7 @@ "サーバとの接続中にエラーが発生しました。再試行してください。" "お待ちください" "無効な QR コード" - - - + "EULA" @@ -61,10 +50,7 @@ "ライセンス情報" - - - - + "はい" @@ -83,9 +69,4 @@ "昨日" "本日" "明日" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-pt/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-pt/strings.xml index e8fb73cca96..ac380b72d17 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-pt/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-pt/strings.xml @@ -1,7 +1,5 @@ - - - - + + "Informação de conexão" "Remover" "Inserir item novo" @@ -9,17 +7,10 @@ "Ainda não existem itens." - - - - - - - - + - + "Erro" "Info" @@ -45,9 +36,7 @@ "Ocorreu um erro ao contatar o servidor, tente novamente." "Aguarde" "Código QR inválido" - - - + "EULA" @@ -61,10 +50,7 @@ "Informações sobre a licença" - - - - + "Sim" @@ -83,9 +69,4 @@ "Ontem" "Hoje" "Amanhã" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-ru/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-ru/strings.xml index 2d41571b399..20d27cfe4b0 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-ru/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-ru/strings.xml @@ -1,7 +1,5 @@ - - - - + + "Информация о соединении" "Удалить" "Введите новую позицию" @@ -9,17 +7,10 @@ "Позиций пока нет." - - - - - - - - + - + "Ошибка" "Инфо" @@ -45,9 +36,7 @@ "При обращении к серверу возникла ошибка. Повторите попытку." "Подождите" "Недействительный QR-код" - - - + "Лицензионное соглашение с конечным пользователем" @@ -61,10 +50,7 @@ "Информация о лицензии" - - - - + "Да" @@ -83,9 +69,4 @@ "Вчера" "Сегодня" "Завтра" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values-zh/strings.xml b/mobile/com.sap.sailing.android.shared/res/values-zh/strings.xml index bd5284c2183..1432a8e9c06 100644 --- a/mobile/com.sap.sailing.android.shared/res/values-zh/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values-zh/strings.xml @@ -1,7 +1,5 @@ - - - - + + "连接信息" "移除" "输入新项目" @@ -9,17 +7,10 @@ "尚无任何项目。" - - - - - - - - + - + "错误" "信息" @@ -45,9 +36,7 @@ "联系服务器时发生错误。请重试。" "请稍候" "二维码无效" - - - + "最终用户许可协议" @@ -61,10 +50,7 @@ "许可证信息" - - - - + "是" @@ -83,9 +69,4 @@ "昨天" "今天" "明天" - - - - - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.android.shared/res/values/strings.xml b/mobile/com.sap.sailing.android.shared/res/values/strings.xml index 45821b17f78..22af3478715 100644 --- a/mobile/com.sap.sailing.android.shared/res/values/strings.xml +++ b/mobile/com.sap.sailing.android.shared/res/values/strings.xml @@ -1,5 +1,5 @@ - + sap_log_%s.txt sap_crash_%s.txt @@ -20,7 +20,6 @@ Version - Error Info @@ -88,5 +87,4 @@ yyyy-MM-dd Sending Message Service - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml index 2109c07d684..5aabfe5f444 100755 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml @@ -1,6 +1,5 @@ - (Kurs %s) Kurs hat %d Wegpunkte Warte auf Rennen… @@ -47,7 +46,6 @@ Lädt … Wettfahrten auf Bahn \'%s\' (Kurs setzen) - Update Icon Rennen zurücksetzen Verschieben @@ -335,8 +333,6 @@ Nein danke Möchten Sie wirklich das Rennen inklusive aller Einstellungen und getätigten Aktionen zurücksetzen? Das kann nicht rückgängig gemacht werden! Zeit setzen - -%s - %s Beginn des Zieleinlaufs Aktuelle Zeit Individuelle Zeit diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-es/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-es/strings.xml index 34bcb858c92..b9c4e0e7c7e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-es/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-es/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(En regata %s)" "La regata tiene %d puntos de ruta" - - "Esperando a las carreras para supervisar..." + + "Esperando a las carreras para supervisar…" "Actualmente supervisando %d carreras." - + "Salida" "Reiniciar" "Seleccionar" @@ -59,7 +51,7 @@ "En finalización" "Finalizado" "Desconocido" - "Actualmente cargándose..." + "Actualmente cargándose…" "Carreras en zona de regata %s\'" "(Clic para configurar regata)" "Actualizar indicador" @@ -143,11 +135,11 @@ "en nudos" "La velocidad o la dirección del viento no es un número válido. Corrija el valor." "La ubicación, la velocidad del viento o la dirección introducidas esta vacía. Corrija los valores." - "Esperando señal de GPS..." + "Esperando señal de GPS…" "Entrada manual de posición" "Posición basada en GPS" "Añadir posición manualmente" - "Búsqueda de ubicación..." + "Búsqueda de ubicación…" "No se ha encontrado la ubicación basada en su consulta." "Utilice esta posición" "Guardar viento" @@ -194,7 +186,7 @@ "Modificar regata" "¿Desea modificar una regata y/o la zona? Las carreras que estén monitorizándose ahora se desconectarán." "Cerrar sesión" - "Cargando configuración..." + "Cargando configuración…" "Se ha producido un error al cargar la configuración" "No existe una configuración remota para este dispositivo." "Configuración remota aplicada." @@ -235,14 +227,14 @@ "Guardar %1$s configuraciones" "Esto guardará la configuración que se muestra para todas las carreras de %1$s." "Actualización automática" - "Comprobando información de versión en el servidor..." + "Comprobando información de versión en el servidor…" "Intentando descargar la información de actualización automática desde %1$s" "El enlace de archivo de versión no era válido." "Haga clic en \"Instalar\" para actualizar a la versión %1$d." "Ya ha instalado la versión que ofrecía el servidor (versión %1$d)." "Su versión actual está instalada desde %1$s. La descarga desde el servidor no se puede instalar sin desintalar la aplicación actual." "Ya está actualizado" - "Descargando APK desde el servidor..." + "Descargando APK desde el servidor…" "El enlace de descarga no era válido." "El archivo de descarga no se pudo crear." "Error al intentar realizar una actualización automática." @@ -257,8 +249,6 @@ "4 minutos" "2 minutos" "1 minuto" - "-%s" - "%s" "Fijar hora" "Reinicializar regata" "Ninguna regata activa" @@ -378,9 +368,9 @@ "Promedio de la dirección del viento" "De %.0f°" "Promedio de la velocidad del viento" - + "Recordatorios individuales" - + "Grabar voz" "Primero en meta a %1$s" "(%s)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d minutos" "%d minutos" "n/a" - - - + + "(desde %s)" "Regata anterior" "Disposición de regata" @@ -493,7 +482,7 @@ "Reinicializar" "¿Desea borrar la lista?" "¿Desea reinicializar el último estado?" - + "Escanear código QR" "Entrada manual" "Carrera de grupo" @@ -535,7 +524,7 @@ "Abortar carreras" "¿Qué carreras en ejecución desea abortar?" "Descartar" - + "Configurar factor" "Se ha producido un error al configurar el factor: %s" "Sincronizar al minuto" @@ -568,4 +557,4 @@ "Revocar" "No se puede revocar el tiempo finalizado" "No se puede revocar el tiempo de finalización" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-fr/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-fr/strings.xml index 0d4a4dc971e..b4972b96e7c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-fr/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-fr/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(sur le parcours %s)" "Le parcours comporte %d points de cheminement." - - "En attente de courses à suivre..." + + "En attente de courses à suivre…" "Actuellement, %d courses suivies." - + "Lancer" "Relancer" "Sélectionner" @@ -59,7 +51,7 @@ "En cours d\'achèvement" "Terminée" "Inconnu" - "Chargement en cours..." + "Chargement en cours…" "Courses sur la zone de course \'%s\'" "(Cliquer pour définir le parcours)" "Mettre à jour indicateur" @@ -143,11 +135,11 @@ "en nœuds" "La direction ou la vitesse du vent n\'est pas un chiffre valide. Veuillez corriger la valeur." "La position, la vitesse du vent ou la direction du vent n\'est pas correcte. Veuillez corriger les valeurs." - "En attente de signal GPS..." + "En attente de signal GPS…" "Saisie de position manuelle" "Position déterminée par GPS" "Ajouter manuellement la position" - "Recherche de localisation..." + "Recherche de localisation…" "Impossible de trouver une localisation basée sur votre requête." "Utiliser cette position" "Enregistrer vent" @@ -194,7 +186,7 @@ "Modifier la régate" "Voulez-vous modifier une régate et/ou une zone ? Les courses actuellement suivies vont être déconnectées." "Déconnexion" - "Chargement de la configuration..." + "Chargement de la configuration…" "Une erreur s\'est produite lors du chargement de la configuration." "Aucune configuration à distance n\'a été trouvée pour cet appareil." "Configuration à distance appliquée" @@ -235,14 +227,14 @@ "Sauvegarder la configuration de %1$s" "La configuration sera sauvegardée pour toutes les courses de %1$s." "Mise à jour automatique" - "Contrôle des informations de version sur le serveur..." + "Contrôle des informations de version sur le serveur…" "Tentative de téléchargement des informations de mise à jour automatique à partir de %1$s" "Le lien du fichier de version n\'était pas valide." "Cliquez sur \"Installer\" pour exécuter la mise à jour vers la version %1$d." "Vous avez déjà installé la version disponible sur le serveur (version %1$d)." "Votre version actuelle est installée depuis %1$s. Vous ne pouvez pas installer le téléchargement depuis le serveur sans désinstaller l\'application actuelle." "Vous êtes déjà à jour !" - "Téléchargement d\'APK depuis le serveur..." + "Téléchargement d\'APK depuis le serveur…" "Le lien de téléchargement n\'était pas valide." "Impossible de créer le fichier de téléchargement." "Erreur lors de la tentative de mise à jour automatique." @@ -257,8 +249,6 @@ "4 minutes" "2 minutes" "1 minute" - "-%s" - "%s" "Définir heure" "Réinitialiser parcours" "Aucun parcours actif" @@ -378,9 +368,9 @@ "Direction moyenne du vent" "vent de %.0f°" "Vitesse moyenne du vent" - + "Rappels individuels" - + "Enregistrement vocal" "Premier à l\'arrivée à %1$s" "(%s)" @@ -453,7 +443,7 @@ "Annuler" "Selon les règles de course, ces données ne sont pas modifiables au statut \nactuel de la course. Vous pouvez toutefois continuer à corriger les saisies erronées." "Avertissement" - "Envoyer e-mail..." + "Envoyer e-mail…" "Les données ont été modifiées en externe." "Les données doivent être rechargées. Perte de données possible." "Heure de départ relative (minutes)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d minutes" "%d minutes" "sans objet" - - - + + "(depuis %s)" "Parcours précédent" "Configuration du parcours" @@ -493,7 +482,7 @@ "Réinitialiser" "Voulez-vous effacer les listes ?" "Voulez-vous réinitialiser au dernier état ?" - + "Scanner code QR" "Saisie manuelle" "Course en groupe" @@ -535,7 +524,7 @@ "Annuler les courses" "Quelles courses dont le départ a été donné voulez-vous annuler ?" "Ignorer" - + "Définir facteur" "Une erreur s\'est produite lors de la définition du facteur : %s" "Synchroniser le temps" @@ -568,4 +557,4 @@ "Annuler" "Vous ne pouvez pas annuler l\'heure de fermeture de la ligne d\'arrivée." "Vous ne pouvez pas annuler l\'heure d\'arrivée du premier." - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-it/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-it/strings.xml index cdc69bffd6c..e88b1252e0f 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-it/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-it/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "?sensor=true" "(in rotta %s)" "La rotta ha %d waypoint" - + "In attesa di rotte da monitorare" "Monitoraggio attualmente in corso di %d gare." - + "Avvia" "Riavvia" "Seleziona" @@ -59,7 +51,7 @@ "Conclusione" "Concluso" "Sconosciuto" - "Caricamento in corso..." + "Caricamento in corso…" "Gare in area di percorso \"%s\"" "(fare clic per impostare la rotta)" "Aggiorna indicatore" @@ -143,11 +135,11 @@ "in nodi" "La velocità o la direzione del vento non sono un numero valido. Correggere il valore." "I campi ubicazione, velocità del vento o direzione del vento sono vuoti. Correggere i valori." - "In attesa di segnale GPS..." + "In attesa di segnale GPS…" "Inserimento posizione manuale" "Posizione GPS" "Aggiungi posizione manualmente" - "Ricerca ubicazione..." + "Ricerca ubicazione…" "Impossibile trovare un\'ubicazione in base alla query." "Utilizza questa posizione." "Memorizza vento" @@ -194,7 +186,7 @@ "Modifica regata" "Modificare una regata e/o l\'area? Le gare attualmente monitorate verranno disconnesse." "Esegui logout" - "Caricamento configurazione..." + "Caricamento configurazione…" "Si è verificato un errore nel caricamento della configurazione." "Nessuna configurazione remota trovata per questo dispositivo." "Configurazione remota applicata." @@ -235,14 +227,14 @@ "Salva configurazione %1$s" "In tal modo la configurazione visualizzata verrà salvata per tutte le gare di %1$s." "Aggiornamento automatico" - "È in corso la verifica informazioni relative alla versione sul server..." + "È in corso la verifica informazioni relative alla versione sul server…" "Tentativo di eseguire il download di informazioni sull\'aggiornamento automatico da %1$s" "Link alla versione file non valido." "Fare clic su \"Installa\" per eseguire l\'aggiornamento alla versione %1$d." "La versione offerta dal server è già stata installata (versione %1$d)." "La versione attuale è stata installata da %1$s. Il download dal server non può essere installato senza disinstallare l\'applicazione attuale." "I dati sono già aggiornati!" - "È in corso il download di APK dal server..." + "È in corso il download di APK dal server…" "Link per il download non valido." "Non è stato possibile creare il file di download." "Errore durante il tentativo di aggiornamento automatico." @@ -257,8 +249,6 @@ "4 minuti" "2 minuti" "1 minuto" - "-%s" - "%s" "Imposta ora" "Resetta rotta" "Nessuna rotta attiva" @@ -378,9 +368,9 @@ "Direzione media del vento" "da %.0f°" "Velocità media del vento" - + "Richiami individuali" - + "Registra vocale" "Primo al traguardo alle %1$s" "(%s)" @@ -453,7 +443,7 @@ "Annulla" "In base alle regole di gara questi dati non possono essere modificati nello stato attuale della gara. Tuttavia è possibile correggere gli inserimenti sbagliati." "Messaggio di avvertimento" - "Invio e-mail in corso..." + "Invio e-mail in corso…" "I dati sono stati modificati esternamente" "I dati devono essere ricaricati. Possibile perdita di dati." "Ora di partenza relativa (minuti)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d minuti" "%d minuti" "n/a" - - - + + "(da %s)" "Rotta precedente" "Progettazione rotta" @@ -493,7 +482,7 @@ "Resetta" "Annullare la lista?" "Resettare all\'ultimo stato?" - + "Scannerizza codice QR" "Input manuale" "Gara a gruppi" @@ -535,7 +524,7 @@ "Interrompi gare" "Quali gare in corso si intende interrompere?" "Ignora" - + "Imposta fattore" "Si è verificato un errore di impostazione del fattore: %s" "Sincronizza al minuto" @@ -568,4 +557,4 @@ "Revoca" "Impossibile revocare l\'ora di conclusione" "Impossibile revocare l\'ora di fine" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-ja/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-ja/strings.xml index 2ef74b2eb31..995983e4255 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-ja/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-ja/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(コース %s 上)" "コースに %d の変針点があります" - + "監視するレースを待機中…" "現在 %d レースを監視中" - + "開始" "再実行" "選択" @@ -257,8 +249,6 @@ "4 分" "2 分" "1 分" - "-%s" - "%s" "時刻設定" "コースのリセット" "有効なコースがありません" @@ -378,9 +368,9 @@ "平均風向" "%.0f° から" "平均風速" - + "個別リコール" - + "音声を録音" "最初のフィニッシュが %1$s" "(%s)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d 分" "%d 分" "N/A" - - - + + "(%s から)" "以前のコース" "コースレイアウト" @@ -493,7 +482,7 @@ "リセット" "一覧をクリアしますか" "最終ステータスにリセットしますか" - + "QR コードのスキャン" "マニュアル入力" "グループレース中" @@ -535,7 +524,7 @@ "レースについて" "中止するのは競技中のどのレースですか。" "破棄" - + "係数設定" "係数の設定でエラーが発生: %s" "分単位で同期" @@ -568,4 +557,4 @@ "取消" "フィニッシュ時刻の取消ができません" "フィニッシュ予定時刻の取消ができません" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-pt/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-pt/strings.xml index 78a0509e095..5ab4a942cb5 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-pt/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-pt/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(no percurso %s)" "O percurso tem %d waypoints" - - "Aguardando corridas para monitorar..." + + "Aguardando corridas para monitorar…" "Monitorando atualmente %d corridas." - + "Iniciar" "Reiniciar" "Selecionar" @@ -59,7 +51,7 @@ "Concluindo" "Concluído" "Desconhecido" - "Carregando atualmente..." + "Carregando atualmente…" "Corridas na área do percurso \'%s\'" "(clicar para definir percurso)" "Código de atualização" @@ -143,11 +135,11 @@ "em nós" "A velocidade ou direção do vento não é um número válido. Corrija o valor." "A localização inserida, a velocidade do vento ou a direção estão em branco. Corrija os valores." - "Aguardando sinal de GPS..." + "Aguardando sinal de GPS…" "Entrada de posição manual" "Posição com base em GPS" "Adicionar posição manualmente" - "Pesquisa de localização..." + "Pesquisa de localização…" "Não foi possível encontrar uma localização com base em sua consulta." "Utilizar esta posição" "Arquivar vento" @@ -194,7 +186,7 @@ "Modificar regata" "Você pretende modificar uma regata e/ou a área? As corridas atualmente monitoradas serão desconectadas." "Efetuar logoff" - "Carregando configuração..." + "Carregando configuração…" "Ocorreu um erro ao carregar a configuração." "Nenhuma configuração remota encontrada para este dispositivo." "Configuração remota aplicada." @@ -235,7 +227,7 @@ "Gravar %1$s configuração" "Isto irá gravar a configuração exibida para todas as corridas de %1$s." "Atualização automática" - "Verificando informações da versão no servidor..." + "Verificando informações da versão no servidor…" "Tentando baixar informações de atualização automática de %1$s" "O link do arquivo da versão não era válido." "Clique em \'Instalar\' para executar a atualização para a versão %1$d." @@ -257,8 +249,6 @@ "4 minutos" "2 minutos" "1 minuto" - "-%s" - "%s" "Definir hora" "Reinicializar percurso" "Nenhum percurso ativo" @@ -378,9 +368,9 @@ "Direção média do vento" "de %.0f°" "Velocidade média do vento" - + "Chamadas individuais" - + "Gravar voz" "Primeiro a chegar às %1$s" "(%s)" @@ -453,7 +443,7 @@ "Cancelar" "De acordo com as regras da regata, estes dados não podem ser modificados no status atual\nda regata. Mesmo assim, você pode continuar e corrigir os dados incorretos." "Advertência" - "Enviar e-mail..." + "Enviar e-mail…" "Os dados foram modificados externamente" "Os dados precisam ser recarregados. É possível perda de dados." "Hora de partida relativa (minutos)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d minutos" "%d minutos" "n/a" - - - + + "(desde %s)" "Percurso anterior" "Layout do percurso" @@ -493,7 +482,7 @@ "Reinicializar" "Você pretende anular a lista?" "Você pretende reinicializar o último status?" - + "Digitalizar código QR" "Entrada manual" "Corrida de grupo" @@ -535,7 +524,7 @@ "Cancelar corridas" "Quais as corridas em curso que você pretende cancelar?" "Rejeitar" - + "Definir fator" "Ocorreu um erro ao definir o fator: %s" "Sincronizar ao minuto" @@ -568,4 +557,4 @@ "Revogar" "Impossível revogar hora de chegada" "Impossível revogar hora de conclusão" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-ru/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-ru/strings.xml index 0fa61e835e0..c93e38eb36d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-ru/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-ru/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(на курсе %s)" "Курс содержит %d путевых точек" - + "Ожидание мониторинга гонок…" "Выполняется мониторинг %d гонок." - + "Запустить" "Перезапустить" "Выбрать" @@ -59,7 +51,7 @@ "Завершение" "Завершено" "Неизвестно" - "Сейчас загружается..." + "Сейчас загружается…" "Гонки в зоне гонки \'%s\'" "(Щелкните, чтобы задать курс)" "Обновить индикатор" @@ -143,7 +135,7 @@ "в узлах" "Недопустимое значение скорости или направления. Исправьте значение." "Не введена геопозиция, скорость ветра или направление. Исправьте значения." - "Ожидание сигнала GPS..." + "Ожидание сигнала GPS…" "Ввод позиции вручную" "Позиция по GPS" "Добавить позицию вручную" @@ -257,8 +249,6 @@ "4 минуты" "2 минуты" "1 минута" - "-%s" - "%s" "Установить время" "Сбросить курс" "Нет активного курса" @@ -378,9 +368,9 @@ "Среднее направление ветра" "с %.0f°" "Средняя скорость ветра" - + "Индивидуальные отзывы" - + "Записать голос" "Первый на финише в %1$s" "(%s)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d мин." "%d мин." "недоступно" - - - + + "(с %s)" "Предыдущий курс" "Схема курса" @@ -493,7 +482,7 @@ "Сброс" "Очистить список?" "Сбросить состояние списка?" - + "Скан. QR-код" "Ручной ввод" "Групповые гонки" @@ -535,7 +524,7 @@ "Прервать гонки" "Какие из идущих гонок вы хотите прервать?" "Отклонить" - + "Установить коэффициент" "Ошибка при установке коэффициента: %s" "Синхр. до минуты" @@ -568,4 +557,4 @@ "Отозвать" "Невозможно отозвать время финиширования" "Невозможно отозвать время финиширования" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-zh/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-zh/strings.xml index 40cf19f277f..0b1ab76649d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-zh/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-zh/strings.xml @@ -1,21 +1,13 @@ - - - - - - - - - - + + "http://maps.googleapis.com/maps/api/geocode/json?address=" "\?sensor=true" "(在场地%s上)" "场地具有%d个航路点" - + "正在等待要监控的比赛轮次…" "目前正在监控%d轮比赛。" - + "开始" "重新开始" "选择" @@ -257,8 +249,6 @@ "4 分钟" "2 分钟" "1 分钟" - "-%s" - "%s" "设置时间" "重置场地" "没有有效的场地" @@ -378,9 +368,9 @@ "平均风向" "风向:%.0f°" "平均风速" - + "个别召回" - + "录制语音" "首位结束时间:%1$s" "(%s)" @@ -480,9 +470,8 @@ "%1$d + %2$d = %3$d分钟" "%d分钟" "不适用" - - - + + "(自%s)" "先前场地" "场地设置" @@ -493,7 +482,7 @@ "重置" "是否要清除此清单?" "是否要重置上次的状态?" - + "扫描二维码" "手动输入" "小组赛" @@ -535,7 +524,7 @@ "中止比赛轮次" "要中止哪轮比赛?" "取消" - + "设置因素" "设置因素时出错:%s" "同步到分钟" @@ -568,4 +557,4 @@ "撤销" "无法撤销完成时间" "无法撤销结束时间" - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml index 2d0802f7be5..8117c1e7b98 100755 --- a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml @@ -1,8 +1,5 @@ - - - - + sap_rc_log_%s.txt sap_rc_crash_%s.txt @@ -258,8 +255,6 @@ 4 Minutes 2 Minutes 1 Minute - -%s - %s Set Time Reset Course No course active @@ -569,4 +564,4 @@ Revoke Cannot revoke finished time Cannot revoke finishing time - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_course_designer.xml b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_course_designer.xml index 7487d515c03..75b6f9cc863 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_course_designer.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_course_designer.xml @@ -1,16 +1,14 @@ - - - - - - + + + + + - + - - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_general.xml b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_general.xml index 07c8a1c0f0d..6afba3bd099 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_general.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_general.xml @@ -7,20 +7,19 @@ string the getter on AppPreferences will return the device's unique identifier --> + android:title="@string/device_identifier" /> + android:title="@string/settings_advanced_service_header" /> + android:title="@string/synchronize_connection_settings_header" /> + android:title="@string/update_app_header" /> @@ -28,48 +27,48 @@ android:defaultValue="@bool/preference_polling_active_default" android:key="@string/preference_polling_active_key" android:summary="@string/settings_polling_active_description" - android:title="@string/settings_polling_active_header"/> + android:title="@string/settings_polling_active_header" /> + android:title="@string/settings_polling_interval_header" /> + android:title="@string/settings_general_language_header" /> + android:title="@string/settings_general_manageareas_header" /> + android:title="@string/settings_advanced_mail_header" /> + android:title="@string/preference_non_public_events_header" /> + android:title="@string/preference_allow_demo_header" /> + android:title="@string/preference_wakelock_header" /> + android:title="@string/preference_offline_header" /> - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_defaults.xml b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_defaults.xml index 91e6a49858c..3cea0cfb7f4 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_defaults.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_defaults.xml @@ -45,7 +45,7 @@ android:defaultValue="@integer/preference_protest_time_duration_default" android:key="@string/preference_protest_time_duration_key" android:numeric="integer" - android:title="@string/preference_protest_time_header"/> + android:title="@string/preference_protest_time_header" /> @@ -53,7 +53,6 @@ + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> - @@ -81,7 +78,6 @@ + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> - @@ -109,7 +103,6 @@ + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> - @@ -143,7 +135,6 @@ - + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> @@ -165,7 +155,6 @@ - + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> @@ -187,7 +175,6 @@ - + android:title="@string/preference_racing_procedure_resultentryenabled_header" /> - \ No newline at end of file + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_specific.xml b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_specific.xml index faf2417fed4..94bbf3c1f90 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_specific.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/xml/preference_regatta_specific.xml @@ -1,9 +1,5 @@ - - - \ No newline at end of file + + diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java index 76c5f29c440..71823b35d97 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java @@ -166,16 +166,13 @@ public class TimePanelFragment extends BasePanelFragment { } if (mHeaderTime != null && startTime != null) { - String time; - int resId; + String duration; if (startTime.after(now)) { - resId = R.string.race_start_time_in; - time = TimeUtils.formatDurationUntil(startTime.minus(now.asMillis()).asMillis(), false); + duration = "-" + TimeUtils.formatDurationUntil(startTime.minus(now.asMillis()).asMillis(), false); } else { - resId = R.string.race_start_time_ago; - time = TimeUtils.formatDurationSince(now.minus(startTime.asMillis()).asMillis(), false); + duration = TimeUtils.formatDurationSince(now.minus(startTime.asMillis()).asMillis(), false); } - mHeaderTime.setText(getString(resId, time)); + mHeaderTime.setText(duration); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java index b5ef23a50bb..970b92a8dc9 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/StartTimeFragment.java @@ -586,23 +586,18 @@ public class StartTimeFragment extends BaseFragment super.notifyTick(now); if (mAbsolute.getVisibility() == View.VISIBLE) { - int resId; TimePoint timePoint; - String time; + String duration; if (mStartTime.after(now)) { - resId = R.string.race_start_time_in; timePoint = mStartTime.minus(now.asMillis()); - time = TimeUtils.formatDurationUntil(timePoint.asMillis()); + duration = "-" + TimeUtils.formatDurationUntil(timePoint.asMillis()); } else { - resId = R.string.race_start_time_ago; timePoint = now.minus(mStartTime.asMillis()); - time = TimeUtils.formatDurationSince(timePoint.asMillis()); + duration = TimeUtils.formatDurationSince(timePoint.asMillis()); } if (mCountdown != null) { - String countdown = getString(resId, time); - mCountdown.setText(countdown); - mCountdown.setTag(resId); + mCountdown.setText(duration); } if (mDebugTime != null) { From 2327b9112779324af44fcec61ca1d963ffcbf29c Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:33:01 +0200 Subject: [PATCH 009/235] style: rearrange code --- .../racelist/ManagedRaceListAdapter.java | 60 +++++++++---------- .../raceinfo/MainScheduleFragment.java | 58 +++--------------- 2 files changed, 38 insertions(+), 80 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java index acc8ef22c21..8b3f6063cd0 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/racelist/ManagedRaceListAdapter.java @@ -200,38 +200,36 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple RaceState state = race.getRace().getState(); holder.race_flag_new.setRaceState(state); holder.time.setRaceState(state); - if (state != null) { - CompetitorResults draft = state.getFinishPositioningList(); - CompetitorResults confirmed = state.getConfirmedFinishPositioningList().getCompetitorResults(); - holder.warning_sign.setVisibility( - ((draft != null && draft.hasConflicts()) || confirmed != null && confirmed.hasConflicts()) - ? View.VISIBLE - : View.GONE); - if (state.getStartTime() != null) { - int startRes = R.string.race_started; - if (state.getFinishedTime() == null) { - startRes = R.string.race_start; - } - String startTime = mResources.getString(startRes, TimeUtils.formatTime(state.getStartTime(), false)); - holder.race_started.setText(startTime); + CompetitorResults draft = state.getFinishPositioningList(); + CompetitorResults confirmed = state.getConfirmedFinishPositioningList().getCompetitorResults(); + holder.warning_sign.setVisibility( + ((draft != null && draft.hasConflicts()) || confirmed != null && confirmed.hasConflicts()) + ? View.VISIBLE + : View.GONE); + if (state.getStartTime() != null) { + int startRes = R.string.race_started; + if (state.getFinishedTime() == null) { + startRes = R.string.race_start; } - if (state.getFinishedTime() != null) { - holder.race_flag_new.setVisibility(View.GONE); - holder.time.setVisibility(View.GONE); - holder.race_finished.setVisibility(View.VISIBLE); - holder.race_finished.setText(mResources.getString(R.string.race_finished, - TimeUtils.formatTime(state.getFinishedTime(), false))); - } - setDependingText(holder, race); - if (state.getStartTime() == null && state.getFinishedTime() == null) { - if (race.getRace().getStatus() == RaceLogRaceStatus.PRESCHEDULED) { - holder.panel_right.setVisibility(View.GONE); - holder.race_scheduled.setVisibility(View.GONE); - holder.race_unscheduled.setVisibility(View.GONE); - } else { - holder.race_scheduled.setVisibility(View.GONE); - holder.race_unscheduled.setVisibility(View.VISIBLE); - } + String startTime = mResources.getString(startRes, TimeUtils.formatTime(state.getStartTime(), false)); + holder.race_started.setText(startTime); + } + if (state.getFinishedTime() != null) { + holder.race_flag_new.setVisibility(View.GONE); + holder.time.setVisibility(View.GONE); + holder.race_finished.setVisibility(View.VISIBLE); + holder.race_finished.setText(mResources.getString(R.string.race_finished, + TimeUtils.formatTime(state.getFinishedTime(), false))); + } + setDependingText(holder, race); + if (state.getStartTime() == null && state.getFinishedTime() == null) { + if (race.getRace().getStatus() == RaceLogRaceStatus.PRESCHEDULED) { + holder.panel_right.setVisibility(View.GONE); + holder.race_scheduled.setVisibility(View.GONE); + holder.race_unscheduled.setVisibility(View.GONE); + } else { + holder.race_scheduled.setVisibility(View.GONE); + holder.race_unscheduled.setVisibility(View.VISIBLE); } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java index cba9e5d2a8e..d2ebb72d292 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java @@ -37,7 +37,6 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; import java.util.ArrayList; -import java.util.Calendar; public class MainScheduleFragment extends BaseFragment implements View.OnClickListener, SelectionAdapter.ItemClick { @@ -61,15 +60,11 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi private RacingProcedure mRacingProcedure; private TimePoint lastTick; - private Calendar mCalendar; - private RaceStateChangedListener mStateListener; private int mFlagSize; - public MainScheduleFragment() { - mCalendar = Calendar.getInstance(); - } + public MainScheduleFragment() {} public static MainScheduleFragment newInstance() { return new MainScheduleFragment(); @@ -120,12 +115,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi } private void initCourse() { - Runnable runnable = new Runnable() { - @Override - public void run() { - openFragment(CourseFragment.newInstance(START_MODE_PRESETUP, preferences)); - } - }; + Runnable runnable = () -> openFragment(CourseFragment.newInstance(START_MODE_PRESETUP, preferences)); SelectionItem courseItem = new SelectionItem(getString(R.string.course), null, null, false, false, runnable); if (getRaceState().getCourseDesign() != null) { courseItem.setValue(getCourseName()); @@ -134,12 +124,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi } private void initWind() { - Runnable runnable = new Runnable() { - @Override - public void run() { - openFragment(WindFragment.newInstance(START_MODE_PRESETUP)); - } - }; + Runnable runnable = () -> openFragment(WindFragment.newInstance(START_MODE_PRESETUP)); mItemStartWind = new SelectionItem(getString(R.string.wind), null, null, false, false, runnable); mItems.add(mItemStartWind); } @@ -147,44 +132,24 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi private void initStartMode() { mRacingProcedure = getRaceState().getRacingProcedure(); if (mRacingProcedure != null) { - Runnable runnableProcedure = new Runnable() { - @Override - public void run() { - openFragment(StartProcedureFragment.newInstance(START_MODE_PRESETUP)); - } - }; + Runnable runnableProcedure = () -> openFragment(StartProcedureFragment.newInstance(START_MODE_PRESETUP)); mItems.add(new SelectionItem(getString(R.string.start_procedure), mRacingProcedure.getType().toString(), null, false, false, runnableProcedure)); if (mRacingProcedure instanceof ConfigurableStartModeFlagRacingProcedure) { final ConfigurableStartModeFlagRacingProcedure procedure = getRaceState().getTypedRacingProcedure(); Flags flag = procedure.getStartModeFlag(); - Runnable runnableMode = new Runnable() { - @Override - public void run() { - openFragment(StartModeFragment.newInstance(START_MODE_PRESETUP)); - } - }; + Runnable runnableMode = () -> openFragment(StartModeFragment.newInstance(START_MODE_PRESETUP)); Drawable drawable = FlagsResources.getFlagDrawable(getActivity(), flag.name(), mFlagSize); mItems.add(new SelectionItem(getString(R.string.start_mode), flag.name(), drawable, false, false, runnableMode)); } else if (mRacingProcedure instanceof GateStartRacingProcedure) { GateStartRacingProcedure procedure = getRaceState().getTypedRacingProcedure(); if (procedure != null) { - Runnable runnablePathfinder = new Runnable() { - @Override - public void run() { - openFragment(GateStartPathFinderFragment.newInstance(START_MODE_PRESETUP)); - } - }; + Runnable runnablePathfinder = () -> openFragment(GateStartPathFinderFragment.newInstance(START_MODE_PRESETUP)); mItems.add(new SelectionItem(getString(R.string.gate_start_pathfinder), procedure.getPathfinder(), null, false, false, runnablePathfinder)); - Runnable runnableTiming = new Runnable() { - @Override - public void run() { - openFragment(GateStartTimingFragment.newInstance(START_MODE_PRESETUP)); - } - }; + Runnable runnableTiming = () -> openFragment(GateStartTimingFragment.newInstance(START_MODE_PRESETUP)); mItems.add(new SelectionItem(getString(R.string.gate_start_timing), RaceHelper.getGateTiming(getActivity(), procedure, getRace().getRaceGroup()), null, false, false, runnableTiming)); @@ -225,12 +190,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi RaceHelper.getShortReverseRaceName(race, " / ", getRace())); } - Runnable runnable = new Runnable() { - @Override - public void run() { - openFragment(StartTimeFragment.newInstance(getArguments())); - } - }; + Runnable runnable = () -> openFragment(StartTimeFragment.newInstance(getArguments())); mItemStartTime = new SelectionItem(getString(R.string.start_time), mStartTimeString, null, false, false, runnable); mItems.add(mItemStartTime); @@ -333,7 +293,7 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi args.putBoolean(RACE_GROUP, mItemRaceGroup.isChecked()); } - getFragmentManager().beginTransaction() + requireFragmentManager().beginTransaction() .replace(R.id.racing_view_container, fragment) .addToBackStack(null) .commitAllowingStateLoss(); From f7dee8a5399408966581bac3592f07f38ca96849 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:34:20 +0200 Subject: [PATCH 010/235] feat: improve custom view for displaying the race time --- .../app/ui/views/RaceTimeView.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java index 48f1cb972d3..e8af4f8c0bf 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/RaceTimeView.java @@ -3,7 +3,6 @@ package com.sap.sailing.racecommittee.app.ui.views; import android.content.Context; import android.support.annotation.Nullable; import android.util.AttributeSet; -import android.view.View; import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.racecommittee.app.utils.TickListener; @@ -14,7 +13,7 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; public class RaceTimeView extends android.support.v7.widget.AppCompatTextView implements TickListener { - private RaceState state; + private TimePoint startTime; public RaceTimeView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -33,18 +32,25 @@ public class RaceTimeView extends android.support.v7.widget.AppCompatTextView im } public void setRaceState(RaceState state) { - this.state = state; - notifyTick(MillisecondsTimePoint.now()); + startTime = state.getStartTime(); + switch (state.getStatus()) { + case UNKNOWN: + case UNSCHEDULED: + case FINISHED: + setVisibility(GONE); + setText(null); + break; + default: + setVisibility(VISIBLE); + notifyTick(MillisecondsTimePoint.now()); + } } @Override public void notifyTick(TimePoint now) { - if (state == null || state.getStartTime() == null) { - setVisibility(View.GONE); - return; + if (startTime != null) { + String duration = TimeUtils.formatDuration(now, startTime); + setText(duration); } - setVisibility(View.VISIBLE); - String duration = TimeUtils.formatDuration(now, state.getStartTime()); - setText(duration); } } From 43c2124e0fc2e59ce0d2090a4caa15533e4843b3 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:35:31 +0200 Subject: [PATCH 011/235] feat: improve custom view for displaying the current flag and its timer --- .../app/ui/views/FlagTimeView.java | 193 ++++++++++-------- 1 file changed, 111 insertions(+), 82 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java index 50c4c7c507b..961520eb05d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/views/FlagTimeView.java @@ -3,15 +3,15 @@ package com.sap.sailing.racecommittee.app.ui.views; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; import android.util.AttributeSet; -import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; -import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.util.BitmapHelper; +import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; +import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; import com.sap.sailing.domain.abstractlog.race.state.RaceState; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; @@ -30,14 +30,17 @@ import java.util.List; public class FlagTimeView extends LinearLayout implements TickListener { - private static final String TAG = FlagTimeView.class.getSimpleName(); - private final ImageView imageView; private final TextView textView; private final int flagSize; private RaceState state; + private RaceLogRaceStatus status; + private RacingProcedure procedure; + private TimePoint startTime; + private TimePoint finishingTime; + private TimePoint nextTime; public FlagTimeView(Context context, AttributeSet attrs) { super(context, attrs); @@ -53,108 +56,134 @@ public class FlagTimeView extends LinearLayout implements TickListener { } } + private final RaceLogEventVisitor raceLogFlagEventListener = new BaseRaceLogEventVisitor() { + @Override + public void visit(RaceLogFlagEvent event) { + final TimePoint now = MillisecondsTimePoint.now(); + setVisibility(VISIBLE); + setProcedureFlag(now); + notifyTick(now); + } + }; + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); TickSingleton.INSTANCE.registerListener(this); + if (state != null) { + state.getRaceLog().addListener(raceLogFlagEventListener); + } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); TickSingleton.INSTANCE.unregisterListener(this); + if (state != null) { + state.getRaceLog().removeListener(raceLogFlagEventListener); + } } public void setRaceState(RaceState state) { + final TimePoint now = MillisecondsTimePoint.now(); this.state = state; - notifyTick(MillisecondsTimePoint.now()); + state.getRaceLog().addListener(raceLogFlagEventListener); + status = state.getStatus(); + procedure = state.getTypedRacingProcedure(); + startTime = state.getStartTime(); + finishingTime = state.getFinishingTime(); + switch (status) { + case SCHEDULED: + case STARTPHASE: + setVisibility(VISIBLE); + setProcedureFlag(now); + notifyTick(now); + break; + case RUNNING: + if (procedure.isIndividualRecallDisplayed() && now.before(procedure.getIndividualRecallRemovalTime())) { + setVisibility(VISIBLE); + setProcedureFlag(now); + notifyTick(now); + } + break; + case FINISHING: + setVisibility(VISIBLE); + setFinishingFlag(now); + notifyTick(now); + break; + default: + setVisibility(GONE); + clear(); + } } @Override public void notifyTick(TimePoint now) { - if (state == null || state.getStartTime() == null || state.getFinishedTime() != null) { - setVisibility(View.GONE); - return; + switch (status) { + case SCHEDULED: + case STARTPHASE: + case RUNNING: + if (nextTime != null && !now.before(nextTime)) { + setProcedureFlag(now); + } + if (nextTime != null) { + textView.setText(TimeUtils.formatDuration(now, nextTime, false)); + } + break; + case FINISHING: + textView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + textView.setText(TimeUtils.formatDurationSince(now.minus(finishingTime.asMillis()).asMillis(), false)); + break; } + } - RacingProcedure procedure = state.getTypedRacingProcedure(); - LayerDrawable flag = null; + private void setProcedureFlag(TimePoint now) { + Drawable flag = null; Drawable arrow = null; - String duration = null; - if (!procedure.isIndividualRecallDisplayed()) { - FlagPoleState poleState = state.getRacingProcedure().getActiveFlags(state.getStartTime(), now); - List currentState = poleState.getCurrentState(); - List upcoming = poleState.computeUpcomingChanges(); - FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming); - TimePoint change = poleState.getNextStateValidFrom(); - Flags currentFlag; - - if (change != null) { - for (FlagPole pole : currentState) { - int isNext = 0; - - currentFlag = pole.getUpperFlag(); - if (isNextFlag(currentFlag, nextPole)) { - isNext = 1; - } else { - currentFlag = pole.getLowerFlag(); - if (!Flags.NONE.equals(currentFlag)) { - if (isNextFlag(currentFlag, nextPole)) { - isNext = 2; - } - } - } - - if (isNext != 0) { - flag = FlagsResources.getFlagDrawable(getContext(), currentFlag.name(), flagSize); - switch (isNext) { - case 1: - if (nextPole.isDisplayed()) { - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); - } else { - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); - } - break; - - case 2: - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); - break; - - default: - ExLog.i(getContext(), TAG, "unknown flag"); - } - duration = TimeUtils.formatDuration(now, poleState.getNextStateValidFrom()); - } - } - } else if (state.getStatus() == RaceLogRaceStatus.FINISHING) { - if (!currentState.isEmpty()) { - flag = FlagsResources.getFlagDrawable(getContext(), currentState.get(0).getUpperFlag().name(), - flagSize); - } - arrow = null; - duration = TimeUtils.formatDurationSince(now.minus(state.getFinishingTime().asMillis()).asMillis(), false); - } - } else { - TimePoint flagDown = procedure.getIndividualRecallRemovalTime(); - if (now.before(flagDown)) { - flag = FlagsResources.getFlagDrawable(getContext(), Flags.XRAY.name(), flagSize); - arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down); - duration = TimeUtils.formatDuration(now, flagDown); - } - } - if (duration != null) { - duration = duration.replace("-", ""); - } - - if (flag == null) { - setVisibility(View.GONE); + final FlagPoleState poleState = procedure.getActiveFlags(startTime, now); + nextTime = poleState.getNextStateValidFrom(); + if (nextTime == null) { + setVisibility(GONE); + clear(); return; } - - setVisibility(View.VISIBLE); + final List poles = poleState.getCurrentState(); + final FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(poleState.computeUpcomingChanges()); + for (FlagPole pole : poles) { + final Flags upperFlag = pole.getUpperFlag(); + if (isNextFlag(upperFlag, nextPole)) { + flag = FlagsResources.getFlagDrawable(getContext(), upperFlag.name(), flagSize); + arrow = BitmapHelper.getAttrDrawable(getContext(), nextPole.isDisplayed() ? R.attr.arrow_up : R.attr.arrow_down); + } else { + final Flags lowerFlag = pole.getLowerFlag(); + if (lowerFlag != Flags.NONE) { + if (isNextFlag(lowerFlag, nextPole)) { + flag = FlagsResources.getFlagDrawable(getContext(), lowerFlag.name(), flagSize); + arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up); + } + } + } + } imageView.setImageDrawable(flag); textView.setCompoundDrawablesWithIntrinsicBounds(arrow, null, null, null); - textView.setText(duration); + } + + private void setFinishingFlag(TimePoint now) { + final FlagPoleState poleState = procedure.getActiveFlags(startTime, now); + final List poles = poleState.getCurrentState(); + if (poles.isEmpty()) { + clear(); + return; + } + final Drawable flag = FlagsResources.getFlagDrawable(getContext(), poles.get(0).getUpperFlag().name(), flagSize); + imageView.setImageDrawable(flag); + textView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + } + + private void clear() { + imageView.setImageDrawable(null); + textView.setCompoundDrawables(null, null, null, null); + textView.setText(null); } private boolean isNextFlag(Flags flag, FlagPole pole) { From f91ed675068511d6d23ddf958c41d75900fd8f94 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:37:46 +0200 Subject: [PATCH 012/235] refactor: singleton for tick on each second to ensure that it ticks at the beginning of a second --- .../app/utils/TickSingleton.java | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/TickSingleton.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/TickSingleton.java index aab278340ee..68c18f8f0b2 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/TickSingleton.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/utils/TickSingleton.java @@ -1,45 +1,52 @@ package com.sap.sailing.racecommittee.app.utils; -import java.util.HashSet; -import java.util.Set; +import android.os.Handler; +import android.os.Looper; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -import android.os.Handler; +import java.util.ArrayList; +import java.util.List; /** - * Reports a tick every second as a singleton - * - * TODO: make it possible to report ticks in other times (e.g. 2s...) + * Reports a tick every second as a singleton. */ -public enum TickSingleton implements Runnable { +public enum TickSingleton { INSTANCE; - private Set listeners = new HashSet<>(); - private Handler handler; - - private TickSingleton() { - handler = new Handler(); - handler.post(this); - } + private final List listeners = new ArrayList<>(); + private final Handler handler = new Handler(Looper.getMainLooper()); + private final Runnable runnable = new Runnable() { + @Override + public void run() { + TimePoint now = MillisecondsTimePoint.now(); + final long delay = now.asMillis() / 1000 * 1000 + 1000 - now.asMillis(); + handler.postDelayed(this, delay); + //Notify all listeners + for (TickListener listener : listeners) { + listener.notifyTick(now); + } + } + }; + /** + * If the {@link TickListener} has been registered, a new tick will be invoked immediately + * to ensure that {@link TickListener#notifyTick(TimePoint)} is called as soon as possible. + * + * @param toRegister The listener to register + */ public void registerListener(TickListener toRegister) { - listeners.add(toRegister); + if (listeners.add(toRegister)) { + handler.removeCallbacks(runnable); + handler.post(runnable); + } } public void unregisterListener(TickListener toUnregister) { listeners.remove(toUnregister); - } - - public void run() { - TimePoint now = MillisecondsTimePoint.now(); - for (TickListener item : listeners) { - if (item != null) { - item.notifyTick(now); - } + if (listeners.isEmpty()) { + handler.removeCallbacks(runnable); } - handler.removeCallbacks(this); - handler.postDelayed(this, 200); } } From 86d8e8fad67b2ff9f4733ed9d8ed776c3d521333 Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 14:39:17 +0200 Subject: [PATCH 013/235] fix: start timer on the race schedule screen --- .../raceinfo/MainScheduleFragment.java | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java index d2ebb72d292..21221402a5e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/MainScheduleFragment.java @@ -58,7 +58,6 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi private TimePoint mStartTime; private Duration mStartTimeDiff; private RacingProcedure mRacingProcedure; - private TimePoint lastTick; private RaceStateChangedListener mStateListener; @@ -102,7 +101,6 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi initStartMode(); initCourse(); initWind(); - lastTick = MillisecondsTimePoint.now().minus(2000); } @Override @@ -246,36 +244,33 @@ public class MainScheduleFragment extends BaseFragment implements View.OnClickLi public void notifyTick(TimePoint now) { super.notifyTick(now); - if (!now.minus(1000).before(lastTick)) { - if (mItemStartTime != null && !TextUtils.isEmpty(mStartTimeString)) { - if (mRaceId == null && mStartTimeDiff == null) { - String startTimeValue = getString(R.string.start_time_value, mStartTimeString, calcCountdown(now)); - mItemStartTime.setValue(startTimeValue); - } else { - mItemStartTime.setValue(mStartTimeString); - } + if (mItemStartTime != null && !TextUtils.isEmpty(mStartTimeString)) { + if (mRaceId == null && mStartTimeDiff == null) { + String startTimeValue = getString(R.string.start_time_value, mStartTimeString, calcCountdown(now)); + mItemStartTime.setValue(startTimeValue); + } else { + mItemStartTime.setValue(mStartTimeString); } + } - if (getRace() != null && getRaceState() != null && getRaceState().getWindFix() != null) { - Wind wind = getRaceState().getWindFix(); - String sensorData = getString(R.string.wind_sensor, TimeUtils.formatTime(wind.getTimePoint()), - wind.getFrom().getDegrees(), wind.getKnots()); - mItemStartWind.setValue(sensorData); - } + if (getRace() != null && getRaceState() != null && getRaceState().getWindFix() != null) { + Wind wind = getRaceState().getWindFix(); + String sensorData = getString(R.string.wind_sensor, TimeUtils.formatTime(wind.getTimePoint()), + wind.getFrom().getDegrees(), wind.getKnots()); + mItemStartWind.setValue(sensorData); + } - if (mAdapter != null) { - mAdapter.notifyDataSetChanged(); - } - lastTick = now; + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); } } private String calcCountdown(TimePoint timePoint) { - RacingActivity activity = (RacingActivity) getActivity(); - if (activity != null) { - return TimeUtils.formatDuration(activity.getStartTime(), timePoint, true); + if (mStartTime.before(timePoint)) { + final String duration = TimeUtils.formatDurationSince(timePoint.minus(mStartTime.asMillis()).asMillis()); + return "-" + duration; } - return ""; + return TimeUtils.formatDurationUntil(mStartTime.minus(timePoint.asMillis()).asMillis()); } private void openFragment(RaceFragment fragment) { From c982d769be363fac7f9bbf6fa2a0620f9efd708e Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Thu, 7 May 2020 15:41:57 +0200 Subject: [PATCH 014/235] fix: notify adapter when selecting race to ensure that the update badge is removed --- .../sailing/racecommittee/app/ui/fragments/RaceListFragment.java | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 4ee74ce99fa..40b53b0b5f6 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -268,6 +268,7 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis ExLog.i(getActivity(), TAG, "Touched " + item.toString()); mAdapter.setSelectedRace(item); + mAdapter.notifyDataSetChanged(); if (item instanceof RaceListDataTypeRace) { final ManagedRace race = ((RaceListDataTypeRace) item).getRace(); mRunnable = () -> mCallbacks.onRaceSelected(race); From 580023cfc6f5a66574a8e5ed0596969f86cbac8c Mon Sep 17 00:00:00 2001 From: Robert Benedikt Date: Mon, 11 May 2020 09:29:11 +0200 Subject: [PATCH 015/235] fix: observed race events to update race list accordingly --- .../app/ui/fragments/RaceListFragment.java | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index 40b53b0b5f6..657343cc400 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -85,36 +85,31 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis private final Set mAllRaces; private BaseRaceStateChangedListener stateListener = new BaseRaceStateChangedListener() { - @Override - public void onFinishingPositionsChanged(ReadonlyRaceState state) { - super.onFinishingPositionsChanged(state); - - update(state); - } - - @Override - public void onFinishingPositionsConfirmed(ReadonlyRaceState state) { - super.onFinishingPositionsConfirmed(state); - - update(state); - } - - @Override - public void onStartTimeChanged(ReadonlyRaceState state) { - super.onStartTimeChanged(state); - + public void onRacingProcedureChanged(ReadonlyRaceState state) { + super.onRacingProcedureChanged(state); update(state); } @Override public void onStatusChanged(ReadonlyRaceState state) { super.onStatusChanged(state); - update(state); } - void update(ReadonlyRaceState state) { + @Override + public void onStartTimeChanged(ReadonlyRaceState state) { + super.onStartTimeChanged(state); + update(state); + } + + @Override + public void onFinishingTimeChanged(ReadonlyRaceState state) { + super.onFinishingTimeChanged(state); + update(state); + } + + private void update(ReadonlyRaceState state) { dataChanged(state); filterChanged(); updateConflictSign(); From cc48de88590ffc1bbda7174e78ec60aff0f79aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Wed, 27 May 2020 15:29:26 +0200 Subject: [PATCH 016/235] bug5259: Created TimeRangeActionsExecutor --- .../async/TimeRangeActionsExecutor.java | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java new file mode 100644 index 00000000000..2858144337c --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -0,0 +1,201 @@ +package com.sap.sse.gwt.client.async; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.common.MultiTimeRange; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MultiTimeRangeImpl; + +public class TimeRangeActionsExecutor { + public interface TimeRangeAsyncAction extends AsyncAction { + TimeRange getTimeRange(); + void setTimeRange(TimeRange timeRange); + } + + public interface TimeRangeResultJoiner { + /** + * + * @param resultsToJoin + * @param timeRange + * @return {@code null} to indicate missing data. + */ + T join(List> resultsToJoin, TimeRange timeRange); + } + + private class Request implements AsyncCallback { + private final TimeRangeAsyncAction action; + private final AsyncCallback callback; + private T result = null; + + public Request(TimeRangeAsyncAction asyncAction, AsyncCallback asyncCallback) { + this.action = asyncAction; + this.callback = asyncCallback; + } + + @Override + public void onSuccess(T result) { + TimeRange requestedTimeRange = TimeRangeActionsExecutor.this.requestedRangeMap.get(this); + // If this requests TimeRange was trimmed or the results are already present + if (requestedTimeRange == null || !requestedTimeRange.equals(action.getTimeRange())) { + //TODO reduce amount of elements that the joiner has to search through + result = (T) TimeRangeActionsExecutor.this.joinResults((TimeRangeActionsExecutor.Request) this); //TODO Type check + } + TimeRangeActionsExecutor.this.requestedRangeMap.remove(this); + if (requestedTimeRange != null) { + action.setTimeRange(requestedTimeRange); //TODO Resetting this to what it originally was is inefficient + } + if (result != null) { + this.result = result; + TimeRangeActionsExecutor.this.receivedRequestsRingBuffer.add((TimeRangeActionsExecutor.Request) this); //TODO Type check + TimeRangeActionsExecutor.this.receivedRange = null; + } + if (callback != null) { + callback.onSuccess(result); + } + } + + @Override + public void onFailure(Throwable caught) { + TimeRangeActionsExecutor.this.requestedRangeMap.remove(this); + //TODO Check if other requests are now missing parts of their request + if (callback != null) { + callback.onFailure(caught); + } + } + + public TimeRangeAsyncAction getAction() { + return action; + } + + public T getResult() { + return result; + } + } + + + protected final AsyncActionsExecutor executor; + protected String actionCategory = MarkedAsyncCallback.CATEGORY_GLOBAL; + protected final TimeRangeResultJoiner resultJoiner; + protected final Deque> waitingForOtherRequestsQueue = new ArrayDeque<>(); + /** + * Holds {@link TimeRange}s of requests that have been given to the {@link #executor}. + */ + protected final Map, TimeRange> requestedRangeMap = new HashMap<>(); + /** + * Ring buffer holding the last successful responses. + */ + protected final Deque> receivedRequestsRingBuffer = new ArrayDeque>() { + private static final long serialVersionUID = 1L; + private final int bufferSize = 16; + @Override + public void addFirst(Request e) { + while (size() >= bufferSize) { + super.pollFirst(); + } + super.addFirst(e); + } + @Override + public void addLast(Request e) { + while (size() >= bufferSize) { + super.pollFirst(); + } + super.addLast(e); + } + }; + /** + * Get with {@link #getReceivedTimeRange()} as this will be set to {@code null} once it becomes invalid. + * {@link MultiTimeRange} containing {@link #receivedRequestsRingBuffer} element's {@link TimeRange}s. + */ + protected MultiTimeRange receivedRange; + + public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor, TimeRangeResultJoiner resultJoiner) { + this.executor = actionsExecutor; + this.resultJoiner = resultJoiner; + } + + public void execute(TimeRangeAsyncAction action, AsyncCallback callback) { + execute(action, callback, /* forceTimeRange */ false); + } + + public void execute(TimeRangeAsyncAction action, AsyncCallback callback, + boolean forceTimeRange) { + Request request = new Request<>(action, callback); + TimeRange requestedTimeRange = action.getTimeRange(); + if (!forceTimeRange) { + TimeRange missingTimeRange = trimActionTimeRange(requestedTimeRange); + // If all data is already present simply return the results + if (missingTimeRange == null) { + request.onSuccess(null); + return; + } + action.setTimeRange(missingTimeRange); + } + + requestedRangeMap.put(request, requestedTimeRange); + //TODO waitingQueue + //executor.getNumberOfPendingActionsPerType(actionCategory); + + executor.execute(action, actionCategory, request); + } + + /** + * @return trimmed {@link TimeRange} or {@code null} if the request is unnecessary since the results are already here. + */ + private TimeRange trimActionTimeRange(TimeRange timeRange) { + // Signal to return result immediately if everything is already there + if (getReceivedTimeRange().includes(timeRange)) { + return null; + } + // Trim against already fetched results and mark them + for (TimeRange rangeToTrimBy : getReceivedTimeRange()) { + timeRange = trimTimeRange(timeRange, rangeToTrimBy); //TODO Mark needed results so they don't get dropped + } + // Trim against requests already in transit + for (TimeRange rangeToTrimBy : requestedRangeMap.values()) { + timeRange = trimTimeRange(timeRange, rangeToTrimBy); + } + return timeRange; + } + + private static TimeRange trimTimeRange(TimeRange toTrim, TimeRange trimWith) { + //TODO A TimeRange which had lain within toTrim on the first pass might now be usable on a second pass + if (toTrim.intersects(trimWith) && !toTrim.liesWithin(trimWith) && !toTrim.includes(trimWith)) { + return toTrim.subtract(trimWith).iterator().next(); + } + return toTrim; + } + + private Result joinResults(Request partialResult) { + final TimeRange requestedTimeRange = requestedRangeMap.get(partialResult); + if (requestedTimeRange == null) { //TODO Maybe only log since we could recover by using the trimmed TimeRange from Request.getAction().getTimeRange()? Also this case SHOULD never occur + throw new IllegalStateException("TimeRange for Request not found in requestedRangeMap"); + } + List> results = receivedRequestsRingBuffer.stream() + .filter(r -> r.getAction().getTimeRange().intersects(requestedTimeRange)) + .map(r -> new Pair<>(r.getAction().getTimeRange(), r.getResult())) + .collect(Collectors.toList()); + Result result = resultJoiner.join(results, requestedTimeRange); + if (result == null) { + //TODO Data missing! Fire off another request or wait for it to finish? + } + return result; + } + + protected MultiTimeRange getReceivedTimeRange() { + if (receivedRange == null) { + TimeRange[] timeRanges = receivedRequestsRingBuffer.stream() + .map(Request::getAction) + .map(TimeRangeAsyncAction::getTimeRange) + .toArray(TimeRange[]::new); + receivedRange = new MultiTimeRangeImpl(timeRanges); + } + return receivedRange; + } +} From 9420c4fe7ad799147ece9e30e6d80c71ceb4cb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Wed, 27 May 2020 15:29:43 +0200 Subject: [PATCH 017/235] bug5259: Created TimeRangeActionsExecutorTest --- .../client/TimeRangeActionsExecutorTest.java | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java new file mode 100644 index 00000000000..20507f3e7a0 --- /dev/null +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java @@ -0,0 +1,202 @@ +package com.sap.sse.gwt.test.client; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.gwt.client.async.AsyncAction; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.async.MarkedAsyncCallback; +import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor; +import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor.TimeRangeAsyncAction; +import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor.TimeRangeResultJoiner; + +public class TimeRangeActionsExecutorTest { + private class MockAsyncActionsExecutor extends AsyncActionsExecutor { + @Override + public void execute(AsyncAction action, AsyncCallback callback) { + execute(action, MarkedAsyncCallback.CATEGORY_GLOBAL, callback); + } + + @Override + public void execute(AsyncAction action, String category, AsyncCallback callback) { + action.execute(callback); + } + } + + private class CounterAction implements TimeRangeAsyncAction { + protected TimeRange timeRange; + protected final boolean doSucceed; + protected final boolean doFail; + protected int counter = 0; + + public CounterAction(TimeRange timeRange, boolean doSucceed, boolean doFail) { + this.timeRange = timeRange; + this.doSucceed = doSucceed; + this.doFail = doFail; + } + + public int getCounter() { + return counter; + } + + @Override + public void execute(AsyncCallback callback) { + counter++; + if (doSucceed) callback.onSuccess(counter); + if (doFail) callback.onFailure(new RuntimeException()); + } + + @Override + public TimeRange getTimeRange() { + // TODO Auto-generated method stub + return timeRange; + } + + @Override + public void setTimeRange(TimeRange timeRange) { + this.timeRange = timeRange; + } + } + + private class NopAction implements TimeRangeAsyncAction { + private TimeRange timeRange; + + public NopAction(TimeRange timeRange) { + this.timeRange = timeRange; + } + @Override + public void execute(AsyncCallback callback) { + // NOP + } + @Override + public TimeRange getTimeRange() { + return timeRange; + } + @Override + public void setTimeRange(TimeRange timeRange) { + this.timeRange = timeRange; + } + } + + private static final AsyncCallback EMPTY_VOID_CALLBACK = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + } + @Override + public void onSuccess(Void result) { + } + }; + private static final AsyncCallback EMPTY_INTEGER_CALLBACK = new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + } + @Override + public void onSuccess(Integer result) { + } + }; + + @Test + public void testSimpleRequest() { + TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor(), null); + final long now = System.currentTimeMillis(); + CounterAction action = new CounterAction(new TimeRangeImpl(new MillisecondsTimePoint(now), new MillisecondsTimePoint(now + 1000)), true, true); + exec.execute(action, EMPTY_INTEGER_CALLBACK); + assertEquals(1, action.getCounter()); + } + + @Test + public void testTrimFromResults() { + TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor(new MockAsyncActionsExecutor(), new TimeRangeResultJoiner() { + @Override + public Integer join(List> resultsToJoin, TimeRange timeRange) { + return 0; + } + }); + final TimeRange requestRange = new TimeRangeImpl(new MillisecondsTimePoint(10_000L), new MillisecondsTimePoint(20_000L)); + final TimeRange expectedRange = new TimeRangeImpl(new MillisecondsTimePoint(15_000L), new MillisecondsTimePoint(18_000L)); + final TimeRange leftRange = new TimeRangeImpl(new MillisecondsTimePoint(5_000L), new MillisecondsTimePoint(15_000L)); + final TimeRange rightRange = new TimeRangeImpl(new MillisecondsTimePoint(18_000L), new MillisecondsTimePoint(30_000L)); + final TimeRange includedRange = new TimeRangeImpl(new MillisecondsTimePoint(16_000L), new MillisecondsTimePoint(17_000L)); + final TimeRange outsideRange = new TimeRangeImpl(new MillisecondsTimePoint(50_000L), new MillisecondsTimePoint(100_000L)); + // Prefill with trim ranges + exec.execute(new CounterAction(leftRange, true, false), EMPTY_INTEGER_CALLBACK); + exec.execute(new CounterAction(rightRange, true, false), EMPTY_INTEGER_CALLBACK); + exec.execute(new CounterAction(includedRange, true, false), EMPTY_INTEGER_CALLBACK); + exec.execute(new CounterAction(outsideRange, true, false), EMPTY_INTEGER_CALLBACK); + + List timeRangeChanges = new ArrayList<>(); + CounterAction action = new CounterAction(requestRange, true, false) { + @Override + public void setTimeRange(TimeRange timeRange) { + super.setTimeRange(timeRange); + timeRangeChanges.add(timeRange); + } + }; + List expectedRangeChanges = new ArrayList<>(); + expectedRangeChanges.add(expectedRange); + expectedRangeChanges.add(requestRange); + exec.execute(action, EMPTY_INTEGER_CALLBACK); + assertListEquals(expectedRangeChanges, timeRangeChanges); + } + + @Test + public void testTrimFromResultsMultiplePasses() { + + } + + @Test + public void testTrimFromInTransit() { + TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor(), new TimeRangeResultJoiner() { + @Override + public Integer join(List> resultsToJoin, TimeRange timeRange) { + return 0; + } + }); + final TimeRange requestRange = new TimeRangeImpl(new MillisecondsTimePoint(200_000L), new MillisecondsTimePoint(300_000L)); + final TimeRange expectedRange = new TimeRangeImpl(new MillisecondsTimePoint(210_000L), new MillisecondsTimePoint(250_000L)); + final TimeRange leftRange = new TimeRangeImpl(new MillisecondsTimePoint(100_000L), new MillisecondsTimePoint(210_000L)); + final TimeRange rightRange = new TimeRangeImpl(new MillisecondsTimePoint(250_000L), new MillisecondsTimePoint(400_000L)); + final TimeRange includedRange = new TimeRangeImpl(new MillisecondsTimePoint(220_000L), new MillisecondsTimePoint(245_000L)); + final TimeRange outsideRange = new TimeRangeImpl(new MillisecondsTimePoint(50_000L), new MillisecondsTimePoint(100_000L)); + // Put actions into transit + exec.execute(new NopAction<>(leftRange), EMPTY_INTEGER_CALLBACK); + exec.execute(new NopAction<>(rightRange), EMPTY_INTEGER_CALLBACK); + exec.execute(new NopAction<>(includedRange), EMPTY_INTEGER_CALLBACK); + exec.execute(new NopAction<>(outsideRange), EMPTY_INTEGER_CALLBACK); + + List timeRangeChanges = new ArrayList<>(); + CounterAction action = new CounterAction(requestRange, true, false) { + @Override + public void setTimeRange(TimeRange timeRange) { + super.setTimeRange(timeRange); + timeRangeChanges.add(timeRange); + } + }; + List expectedRangeChanges = new ArrayList<>(); + expectedRangeChanges.add(expectedRange); + expectedRangeChanges.add(requestRange); + exec.execute(action, EMPTY_INTEGER_CALLBACK); + assertListEquals(expectedRangeChanges, timeRangeChanges); + } + + private void assertListEquals(List expected, List actual) { + assertNotNull(expected); + assertNotNull(actual); + assertEquals(expected.size(), actual.size()); + Iterator expectedIter = expected.iterator(); + Iterator actualIter = actual.iterator(); + while (expectedIter.hasNext()) { + assertEquals(expectedIter.next(), actualIter.next()); + } + } +} From fd4e623aabf4bfed04760cf42e206cee5876dc12 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Tue, 9 Jun 2020 16:19:35 +0200 Subject: [PATCH 018/235] code refactoring and null checks --- .../gateway/jaxrs/api/EventsResource.java | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 53a05f27eec..c132b74d0f5 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.gateway.jaxrs.api; import java.io.IOException; +import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -111,6 +112,7 @@ import com.sap.sse.security.shared.impl.UserGroup; import com.sap.sse.security.shared.impl.WildcardPermissionEncoder; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.shared.media.VideoDescriptor; +import com.sap.sse.util.impl.UUIDHelper; @Path("/v1/events") public class EventsResource extends AbstractSailingServerResource { @@ -136,30 +138,40 @@ public class EventsResource extends AbstractSailingServerResource { @DELETE @Path("/{eventId}/") - public Response delete(@PathParam("eventId") UUID eventId, - @QueryParam("withLeaderboardGroupsAndOverallLeaderboard") Boolean withLeaderboardGroupsAndOverallLeaderboard) + public Response delete(@PathParam("eventId") String eventId, + @QueryParam("withLeaderboardGroupsAndOverallLeaderboard") Boolean withLeaderboardGroupsAndOverallLeaderboard, + @QueryParam("withRegattas") Boolean withRegattas) throws ParseException, JsonDeserializationException { - Event event = getService().getEvent(eventId); - RacingEventService racingEventService = getService(); - SecurityService securityService = racingEventService.getSecurityService(); - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { - getService().apply(new RemoveEvent(event.getId())); - if (withLeaderboardGroupsAndOverallLeaderboard == Boolean.TRUE) { - for (LeaderboardGroup group : event.getLeaderboardGroups()) { - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { - Leaderboard overallLeaderboard = group.getOverallLeaderboard(); - if (overallLeaderboard != null) { - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, - () -> { - racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); - }); - } - racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); + final Serializable eventUUID = UUIDHelper.tryUuidConversion(eventId); + final Event event = getService().getEvent(eventUUID); + if (event != null) { + final SecurityService securityService = getSecurityService(); + final RacingEventService racingEventService = getService(); + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { + getService().apply(new RemoveEvent(event.getId())); + if (withLeaderboardGroupsAndOverallLeaderboard == Boolean.TRUE) { + deleteRelatedLeaderBoardGroups(event, securityService, racingEventService); + } + }); + } else { + return getBadEventErrorResponse(eventId); + } + return Response.ok().build(); + } + + private void deleteRelatedLeaderBoardGroups(final Event event, final SecurityService securityService, + final RacingEventService racingEventService) { + for (LeaderboardGroup group : event.getLeaderboardGroups()) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { + Leaderboard overallLeaderboard = group.getOverallLeaderboard(); + if (overallLeaderboard != null) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, () -> { + racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); }); } - } - }); - return Response.ok().build(); + racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); + }); + } } @POST From fdac3a721d840890f11370da8143d86a681f8a4e Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Wed, 10 Jun 2020 10:35:18 +0200 Subject: [PATCH 019/235] bug4887: added parametrization to event deletion, including all leaderboards, all leaderboardgroups and all regattas associated with the event --- .../gateway/jaxrs/api/EventsResource.java | 62 ++++++++++++++----- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index c132b74d0f5..a61561591a2 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -72,6 +72,7 @@ import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; +import com.sap.sailing.domain.regattalike.HasRegattaLike; import com.sap.sailing.geocoding.ReverseGeocoder; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; @@ -93,6 +94,7 @@ import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard import com.sap.sailing.server.operationaltransformation.RemoveEvent; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard; import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardGroup; +import com.sap.sailing.server.operationaltransformation.RemoveRegatta; import com.sap.sailing.server.operationaltransformation.UpdateLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.UpdateSeries; import com.sap.sailing.server.security.SailingViewerRole; @@ -139,18 +141,52 @@ public class EventsResource extends AbstractSailingServerResource { @DELETE @Path("/{eventId}/") public Response delete(@PathParam("eventId") String eventId, - @QueryParam("withLeaderboardGroupsAndOverallLeaderboard") Boolean withLeaderboardGroupsAndOverallLeaderboard, + @QueryParam("withLeaderboardGroups") Boolean withLeaderboardGroups, + @QueryParam("withLeaderboardGroupsAndOverallLeaderBoards") Boolean withLeaderboardGroupsAndOverallLeaderBoards, + @QueryParam("withLeaderBoards") Boolean withLeaderBoards, @QueryParam("withRegattas") Boolean withRegattas) throws ParseException, JsonDeserializationException { final Serializable eventUUID = UUIDHelper.tryUuidConversion(eventId); final Event event = getService().getEvent(eventUUID); + final boolean deleteLeaderboardGroups = withLeaderboardGroups == Boolean.TRUE; + final boolean deleteLeaderboardGroupsAndOverallLeaderboards = withLeaderboardGroupsAndOverallLeaderBoards == Boolean.TRUE; + final boolean deleteLeaderboards = withLeaderBoards == Boolean.TRUE; + final boolean deleteRegattas = withRegattas == Boolean.TRUE; if (event != null) { final SecurityService securityService = getSecurityService(); final RacingEventService racingEventService = getService(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { getService().apply(new RemoveEvent(event.getId())); - if (withLeaderboardGroupsAndOverallLeaderboard == Boolean.TRUE) { - deleteRelatedLeaderBoardGroups(event, securityService, racingEventService); + if (deleteLeaderboardGroups || deleteLeaderboards || deleteLeaderboardGroupsAndOverallLeaderboards || deleteRegattas) { + for (LeaderboardGroup group : event.getLeaderboardGroups()) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { + if(deleteLeaderboards || deleteRegattas) { + for (Leaderboard leaderboard : group.getLeaderboards()) { + if(deleteRegattas && leaderboard instanceof RegattaLeaderboard) { + deleteReferencedRegatta(securityService, racingEventService, leaderboard); + } + if(deleteLeaderboards) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(leaderboard, () -> { + racingEventService.apply(new RemoveLeaderboard(leaderboard.getName())); + }); + } + } + }else if(deleteLeaderboardGroupsAndOverallLeaderboards) { + final Leaderboard overallLeaderboard = group.getOverallLeaderboard(); + if (overallLeaderboard != null) { + if(deleteRegattas && overallLeaderboard instanceof RegattaLeaderboard) { + deleteReferencedRegatta(securityService, racingEventService, overallLeaderboard); + } + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, () -> { + racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); + }); + } + } + if(deleteLeaderboardGroups || deleteLeaderboardGroupsAndOverallLeaderboards) { + racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); + } + }); + } } }); } else { @@ -159,19 +195,13 @@ public class EventsResource extends AbstractSailingServerResource { return Response.ok().build(); } - private void deleteRelatedLeaderBoardGroups(final Event event, final SecurityService securityService, - final RacingEventService racingEventService) { - for (LeaderboardGroup group : event.getLeaderboardGroups()) { - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { - Leaderboard overallLeaderboard = group.getOverallLeaderboard(); - if (overallLeaderboard != null) { - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, () -> { - racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); - }); - } - racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); - }); - } + private void deleteReferencedRegatta(final SecurityService securityService, final RacingEventService racingEventService, + Leaderboard leaderboard) { + final RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; + final Regatta regatta = regattaLeaderboard.getRegatta(); + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(regatta, () -> { + racingEventService.apply(new RemoveRegatta(regatta.getRegattaIdentifier())); + }); } @POST From 48fffee5423c534ed64bc6433973d33d27d1d7fd Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Wed, 10 Jun 2020 13:11:28 +0200 Subject: [PATCH 020/235] bug4887: removed withLeaderboardGroups parameter. Parameter withLeaderboardGroupsAndOverallLeaderBoards mirrors behavior of SailingServiceImpl --- .../gateway/jaxrs/api/EventsResource.java | 70 ++++++++++--------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index a61561591a2..595919f99ed 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -72,7 +72,6 @@ import com.sap.sailing.domain.leaderboard.ResultDiscardingRule; import com.sap.sailing.domain.leaderboard.ScoringScheme; import com.sap.sailing.domain.leaderboard.ThresholdBasedResultDiscardingRule; import com.sap.sailing.domain.leaderboard.impl.LeaderboardGroupImpl; -import com.sap.sailing.domain.regattalike.HasRegattaLike; import com.sap.sailing.geocoding.ReverseGeocoder; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.jaxrs.AbstractSailingServerResource; @@ -141,14 +140,11 @@ public class EventsResource extends AbstractSailingServerResource { @DELETE @Path("/{eventId}/") public Response delete(@PathParam("eventId") String eventId, - @QueryParam("withLeaderboardGroups") Boolean withLeaderboardGroups, - @QueryParam("withLeaderboardGroupsAndOverallLeaderBoards") Boolean withLeaderboardGroupsAndOverallLeaderBoards, - @QueryParam("withLeaderBoards") Boolean withLeaderBoards, - @QueryParam("withRegattas") Boolean withRegattas) + @QueryParam("withLeaderboardGroupsAndOverallLeaderBoards") Boolean withLeaderboardGroupsAndOverallLeaderBoards, + @QueryParam("withLeaderBoards") Boolean withLeaderBoards, @QueryParam("withRegattas") Boolean withRegattas) throws ParseException, JsonDeserializationException { final Serializable eventUUID = UUIDHelper.tryUuidConversion(eventId); final Event event = getService().getEvent(eventUUID); - final boolean deleteLeaderboardGroups = withLeaderboardGroups == Boolean.TRUE; final boolean deleteLeaderboardGroupsAndOverallLeaderboards = withLeaderboardGroupsAndOverallLeaderBoards == Boolean.TRUE; final boolean deleteLeaderboards = withLeaderBoards == Boolean.TRUE; final boolean deleteRegattas = withRegattas == Boolean.TRUE; @@ -157,35 +153,30 @@ public class EventsResource extends AbstractSailingServerResource { final RacingEventService racingEventService = getService(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { getService().apply(new RemoveEvent(event.getId())); - if (deleteLeaderboardGroups || deleteLeaderboards || deleteLeaderboardGroupsAndOverallLeaderboards || deleteRegattas) { + if (deleteLeaderboards || deleteLeaderboardGroupsAndOverallLeaderboards || deleteRegattas) { for (LeaderboardGroup group : event.getLeaderboardGroups()) { + if (deleteLeaderboards || deleteRegattas) { + for (Leaderboard leaderboard : group.getLeaderboards()) { + if (deleteRegattas && leaderboard instanceof RegattaLeaderboard) { + deleteReferencedRegatta(securityService, racingEventService, leaderboard); + } + if (deleteLeaderboards) { + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(leaderboard, + () -> { + racingEventService.apply(new RemoveLeaderboard(leaderboard.getName())); + }); + } + } + } + if (deleteLeaderboardGroupsAndOverallLeaderboards) { securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { - if(deleteLeaderboards || deleteRegattas) { - for (Leaderboard leaderboard : group.getLeaderboards()) { - if(deleteRegattas && leaderboard instanceof RegattaLeaderboard) { - deleteReferencedRegatta(securityService, racingEventService, leaderboard); - } - if(deleteLeaderboards) { - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(leaderboard, () -> { - racingEventService.apply(new RemoveLeaderboard(leaderboard.getName())); - }); - } + if (!deleteLeaderboards) { + deleteReferencedOverallLeaderboard(deleteRegattas, securityService, + racingEventService, group); } - }else if(deleteLeaderboardGroupsAndOverallLeaderboards) { - final Leaderboard overallLeaderboard = group.getOverallLeaderboard(); - if (overallLeaderboard != null) { - if(deleteRegattas && overallLeaderboard instanceof RegattaLeaderboard) { - deleteReferencedRegatta(securityService, racingEventService, overallLeaderboard); - } - securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, () -> { - racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); - }); - } - } - if(deleteLeaderboardGroups || deleteLeaderboardGroupsAndOverallLeaderboards) { racingEventService.apply(new RemoveLeaderboardGroup(group.getName())); - } - }); + }); + } } } }); @@ -195,8 +186,21 @@ public class EventsResource extends AbstractSailingServerResource { return Response.ok().build(); } - private void deleteReferencedRegatta(final SecurityService securityService, final RacingEventService racingEventService, - Leaderboard leaderboard) { + private void deleteReferencedOverallLeaderboard(final boolean deleteRegattas, final SecurityService securityService, + final RacingEventService racingEventService, LeaderboardGroup group) { + final Leaderboard overallLeaderboard = group.getOverallLeaderboard(); + if (overallLeaderboard != null) { + if (deleteRegattas && overallLeaderboard instanceof RegattaLeaderboard) { + deleteReferencedRegatta(securityService, racingEventService, overallLeaderboard); + } + securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(overallLeaderboard, () -> { + racingEventService.apply(new RemoveLeaderboard(overallLeaderboard.getName())); + }); + } + } + + private void deleteReferencedRegatta(final SecurityService securityService, + final RacingEventService racingEventService, Leaderboard leaderboard) { final RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; final Regatta regatta = regattaLeaderboard.getRegatta(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(regatta, () -> { From cf82f1054b88cd5dfabb4df0e8958b0742a711d9 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Fri, 12 Jun 2020 13:46:21 +0200 Subject: [PATCH 021/235] bug4887: refactoring; added Javadoc to delete method --- .../gateway/jaxrs/api/EventsResource.java | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 595919f99ed..9f2e2fa888f 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -137,26 +137,37 @@ public class EventsResource extends AbstractSailingServerResource { public EventsResource() { } + + /** + * Method to delete a specified {@link Event}. + * + * @param eventId - The UUID of the event to delete + * @param withLeaderboardGroups - Boolean whether to delete all associated {@link LeaderboardGroup}s or not. Doing so will also delete the overall {@link Leaderboard}s of each group. + * @param withLeaderBoards - Boolean whether to delete all associated {@link Leaderboard}s. + * @param withRegattas - Boolean whether to delete all associated {@link Regatta}s. Doing so will also delete all their {@link RegattaLeaderboard}s and {@link TrackedRace}s. + * @return A 200 response, if the delete was successful or a 404 response, if the eventId was not found. + * @throws ParseException + */ @DELETE @Path("/{eventId}/") public Response delete(@PathParam("eventId") String eventId, - @QueryParam("withLeaderboardGroupsAndOverallLeaderBoards") Boolean withLeaderboardGroupsAndOverallLeaderBoards, + @QueryParam("withLeaderboardGroups") Boolean withLeaderboardGroups, @QueryParam("withLeaderBoards") Boolean withLeaderBoards, @QueryParam("withRegattas") Boolean withRegattas) - throws ParseException, JsonDeserializationException { + throws ParseException { final Serializable eventUUID = UUIDHelper.tryUuidConversion(eventId); - final Event event = getService().getEvent(eventUUID); - final boolean deleteLeaderboardGroupsAndOverallLeaderboards = withLeaderboardGroupsAndOverallLeaderBoards == Boolean.TRUE; - final boolean deleteLeaderboards = withLeaderBoards == Boolean.TRUE; - final boolean deleteRegattas = withRegattas == Boolean.TRUE; + final RacingEventService racingEventService = getService(); + final Event event = racingEventService.getEvent(eventUUID); if (event != null) { + final boolean deleteLeaderboardGroups = withLeaderboardGroups == Boolean.TRUE; + final boolean deleteLeaderboards = withLeaderBoards == Boolean.TRUE; + final boolean deleteRegattas = withRegattas == Boolean.TRUE; final SecurityService securityService = getSecurityService(); - final RacingEventService racingEventService = getService(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { - getService().apply(new RemoveEvent(event.getId())); - if (deleteLeaderboards || deleteLeaderboardGroupsAndOverallLeaderboards || deleteRegattas) { - for (LeaderboardGroup group : event.getLeaderboardGroups()) { + racingEventService.apply(new RemoveEvent(event.getId())); + if (deleteLeaderboards || deleteLeaderboardGroups || deleteRegattas) { + for (final LeaderboardGroup group : event.getLeaderboardGroups()) { if (deleteLeaderboards || deleteRegattas) { - for (Leaderboard leaderboard : group.getLeaderboards()) { + for (final Leaderboard leaderboard : group.getLeaderboards()) { if (deleteRegattas && leaderboard instanceof RegattaLeaderboard) { deleteReferencedRegatta(securityService, racingEventService, leaderboard); } @@ -168,7 +179,7 @@ public class EventsResource extends AbstractSailingServerResource { } } } - if (deleteLeaderboardGroupsAndOverallLeaderboards) { + if (deleteLeaderboardGroups) { securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(group, () -> { if (!deleteLeaderboards) { deleteReferencedOverallLeaderboard(deleteRegattas, securityService, @@ -187,7 +198,7 @@ public class EventsResource extends AbstractSailingServerResource { } private void deleteReferencedOverallLeaderboard(final boolean deleteRegattas, final SecurityService securityService, - final RacingEventService racingEventService, LeaderboardGroup group) { + final RacingEventService racingEventService, final LeaderboardGroup group) { final Leaderboard overallLeaderboard = group.getOverallLeaderboard(); if (overallLeaderboard != null) { if (deleteRegattas && overallLeaderboard instanceof RegattaLeaderboard) { @@ -200,7 +211,7 @@ public class EventsResource extends AbstractSailingServerResource { } private void deleteReferencedRegatta(final SecurityService securityService, - final RacingEventService racingEventService, Leaderboard leaderboard) { + final RacingEventService racingEventService, final Leaderboard leaderboard) { final RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; final Regatta regatta = regattaLeaderboard.getRegatta(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(regatta, () -> { From abd1585411e6127bcaed03780135fc8e88f1046b Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Mon, 15 Jun 2020 09:07:03 +0200 Subject: [PATCH 022/235] bug4887: fixed typo in parameter withLeaderboards --- .../sailing/server/gateway/jaxrs/api/EventsResource.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index 9f2e2fa888f..d5e1bb839a4 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -143,7 +143,7 @@ public class EventsResource extends AbstractSailingServerResource { * * @param eventId - The UUID of the event to delete * @param withLeaderboardGroups - Boolean whether to delete all associated {@link LeaderboardGroup}s or not. Doing so will also delete the overall {@link Leaderboard}s of each group. - * @param withLeaderBoards - Boolean whether to delete all associated {@link Leaderboard}s. + * @param withLeaderboards - Boolean whether to delete all associated {@link Leaderboard}s. * @param withRegattas - Boolean whether to delete all associated {@link Regatta}s. Doing so will also delete all their {@link RegattaLeaderboard}s and {@link TrackedRace}s. * @return A 200 response, if the delete was successful or a 404 response, if the eventId was not found. * @throws ParseException @@ -152,14 +152,14 @@ public class EventsResource extends AbstractSailingServerResource { @Path("/{eventId}/") public Response delete(@PathParam("eventId") String eventId, @QueryParam("withLeaderboardGroups") Boolean withLeaderboardGroups, - @QueryParam("withLeaderBoards") Boolean withLeaderBoards, @QueryParam("withRegattas") Boolean withRegattas) + @QueryParam("withLeaderboards") Boolean withLeaderboards, @QueryParam("withRegattas") Boolean withRegattas) throws ParseException { final Serializable eventUUID = UUIDHelper.tryUuidConversion(eventId); final RacingEventService racingEventService = getService(); final Event event = racingEventService.getEvent(eventUUID); if (event != null) { final boolean deleteLeaderboardGroups = withLeaderboardGroups == Boolean.TRUE; - final boolean deleteLeaderboards = withLeaderBoards == Boolean.TRUE; + final boolean deleteLeaderboards = withLeaderboards == Boolean.TRUE; final boolean deleteRegattas = withRegattas == Boolean.TRUE; final SecurityService securityService = getSecurityService(); securityService.checkPermissionAndDeleteOwnershipForObjectRemoval(event, () -> { From 3b468844a89ad4e906c04f194162c9994a885269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 12 Jun 2020 14:09:21 +0200 Subject: [PATCH 023/235] bug5259: Created TimeRangeResultCache --- .../client/TimeRangeActionsExecutorTest.java | 218 ++++---------- .../test/client/TimeRangeResultCacheTest.java | 95 ++++++ .../async/TimeRangeActionsExecutor.java | 281 ++++++++---------- .../client/async/TimeRangeAsyncAction.java | 29 ++ .../client/async/TimeRangeAsyncCallback.java | 15 + .../client/async/TimeRangeResultCache.java | 191 ++++++++++++ 6 files changed, 508 insertions(+), 321 deletions(-) create mode 100644 java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java create mode 100644 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java create mode 100644 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java create mode 100644 java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java index 20507f3e7a0..bd87fea232b 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java @@ -1,10 +1,14 @@ package com.sap.sse.gwt.test.client; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; -import java.util.Iterator; +import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; @@ -15,188 +19,84 @@ import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.gwt.client.async.AsyncAction; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor; -import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor.TimeRangeAsyncAction; -import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor.TimeRangeResultJoiner; +import com.sap.sse.gwt.client.async.TimeRangeAsyncAction; +import com.sap.sse.gwt.client.async.TimeRangeAsyncCallback; public class TimeRangeActionsExecutorTest { private class MockAsyncActionsExecutor extends AsyncActionsExecutor { - @Override - public void execute(AsyncAction action, AsyncCallback callback) { - execute(action, MarkedAsyncCallback.CATEGORY_GLOBAL, callback); - } - @Override public void execute(AsyncAction action, String category, AsyncCallback callback) { action.execute(callback); } } - private class CounterAction implements TimeRangeAsyncAction { - protected TimeRange timeRange; - protected final boolean doSucceed; - protected final boolean doFail; - protected int counter = 0; - - public CounterAction(TimeRange timeRange, boolean doSucceed, boolean doFail) { - this.timeRange = timeRange; - this.doSucceed = doSucceed; - this.doFail = doFail; + private class MilliTimeRange extends TimeRangeImpl { + public MilliTimeRange(long from, long to) { + super(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); } - public int getCounter() { - return counter; + public MilliTimeRange(TimeRange timeRange) { + super(timeRange.from(), timeRange.to()); } @Override - public void execute(AsyncCallback callback) { - counter++; - if (doSucceed) callback.onSuccess(counter); - if (doFail) callback.onFailure(new RuntimeException()); - } - - @Override - public TimeRange getTimeRange() { - // TODO Auto-generated method stub - return timeRange; - } - - @Override - public void setTimeRange(TimeRange timeRange) { - this.timeRange = timeRange; + public String toString() { + return from().asMillis() + " - " + to().asMillis(); } } - private class NopAction implements TimeRangeAsyncAction { - private TimeRange timeRange; - - public NopAction(TimeRange timeRange) { - this.timeRange = timeRange; - } - @Override - public void execute(AsyncCallback callback) { - // NOP - } - @Override - public TimeRange getTimeRange() { - return timeRange; - } - @Override - public void setTimeRange(TimeRange timeRange) { - this.timeRange = timeRange; - } - } - - private static final AsyncCallback EMPTY_VOID_CALLBACK = new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - } - @Override - public void onSuccess(Void result) { - } - }; - private static final AsyncCallback EMPTY_INTEGER_CALLBACK = new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - } - @Override - public void onSuccess(Integer result) { - } - }; - @Test public void testSimpleRequest() { - TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor(), null); - final long now = System.currentTimeMillis(); - CounterAction action = new CounterAction(new TimeRangeImpl(new MillisecondsTimePoint(now), new MillisecondsTimePoint(now + 1000)), true, true); - exec.execute(action, EMPTY_INTEGER_CALLBACK); - assertEquals(1, action.getCounter()); - } - - @Test - public void testTrimFromResults() { - TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor(new MockAsyncActionsExecutor(), new TimeRangeResultJoiner() { + TimeRangeActionsExecutor, Integer, String> exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor()); + final AtomicBoolean actionHasRun = new AtomicBoolean(false); + final AtomicBoolean callbackHasRun = new AtomicBoolean(false); + exec.execute(new TimeRangeAsyncAction, String>() { @Override - public Integer join(List> resultsToJoin, TimeRange timeRange) { - return 0; + public void execute(Collection> timeRanges, + AsyncCallback> callback) { + actionHasRun.set(true); + Map results = new HashMap<>(); + for (Pair channel : timeRanges) { + results.put(channel.getA(), channel.getA().length()); + } + callback.onSuccess(results); + } + @Override + public Collection> getTimeRanges() { + Collection> channels = new ArrayList<>(); + channels.add(new Pair<>("a", new MilliTimeRange(0, 1))); + channels.add(new Pair<>("bb", new MilliTimeRange(1, 2))); + return channels; + } + }, new TimeRangeAsyncCallback, Integer, String>() { + @Override + public Map unzipResults(Map result) { + return result; + } + @Override + public void onSuccess(Map> results) { + callbackHasRun.set(true); + assertEquals(2, results.size()); + for (Map.Entry> result : results.entrySet()) { + assertEquals(result.getKey().length(), result.getValue().getB().intValue()); + } + } + @Override + public void onFailure(Throwable caught) { + assertTrue(false); + } + @Override + public Integer joinSubResults(TimeRange timeRange, List> toJoin) { + return toJoin.get(0).getB(); } }); - final TimeRange requestRange = new TimeRangeImpl(new MillisecondsTimePoint(10_000L), new MillisecondsTimePoint(20_000L)); - final TimeRange expectedRange = new TimeRangeImpl(new MillisecondsTimePoint(15_000L), new MillisecondsTimePoint(18_000L)); - final TimeRange leftRange = new TimeRangeImpl(new MillisecondsTimePoint(5_000L), new MillisecondsTimePoint(15_000L)); - final TimeRange rightRange = new TimeRangeImpl(new MillisecondsTimePoint(18_000L), new MillisecondsTimePoint(30_000L)); - final TimeRange includedRange = new TimeRangeImpl(new MillisecondsTimePoint(16_000L), new MillisecondsTimePoint(17_000L)); - final TimeRange outsideRange = new TimeRangeImpl(new MillisecondsTimePoint(50_000L), new MillisecondsTimePoint(100_000L)); - // Prefill with trim ranges - exec.execute(new CounterAction(leftRange, true, false), EMPTY_INTEGER_CALLBACK); - exec.execute(new CounterAction(rightRange, true, false), EMPTY_INTEGER_CALLBACK); - exec.execute(new CounterAction(includedRange, true, false), EMPTY_INTEGER_CALLBACK); - exec.execute(new CounterAction(outsideRange, true, false), EMPTY_INTEGER_CALLBACK); - - List timeRangeChanges = new ArrayList<>(); - CounterAction action = new CounterAction(requestRange, true, false) { - @Override - public void setTimeRange(TimeRange timeRange) { - super.setTimeRange(timeRange); - timeRangeChanges.add(timeRange); - } - }; - List expectedRangeChanges = new ArrayList<>(); - expectedRangeChanges.add(expectedRange); - expectedRangeChanges.add(requestRange); - exec.execute(action, EMPTY_INTEGER_CALLBACK); - assertListEquals(expectedRangeChanges, timeRangeChanges); + assertTrue(actionHasRun.get()); + assertTrue(callbackHasRun.get()); } @Test - public void testTrimFromResultsMultiplePasses() { - - } - - @Test - public void testTrimFromInTransit() { - TimeRangeActionsExecutor exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor(), new TimeRangeResultJoiner() { - @Override - public Integer join(List> resultsToJoin, TimeRange timeRange) { - return 0; - } - }); - final TimeRange requestRange = new TimeRangeImpl(new MillisecondsTimePoint(200_000L), new MillisecondsTimePoint(300_000L)); - final TimeRange expectedRange = new TimeRangeImpl(new MillisecondsTimePoint(210_000L), new MillisecondsTimePoint(250_000L)); - final TimeRange leftRange = new TimeRangeImpl(new MillisecondsTimePoint(100_000L), new MillisecondsTimePoint(210_000L)); - final TimeRange rightRange = new TimeRangeImpl(new MillisecondsTimePoint(250_000L), new MillisecondsTimePoint(400_000L)); - final TimeRange includedRange = new TimeRangeImpl(new MillisecondsTimePoint(220_000L), new MillisecondsTimePoint(245_000L)); - final TimeRange outsideRange = new TimeRangeImpl(new MillisecondsTimePoint(50_000L), new MillisecondsTimePoint(100_000L)); - // Put actions into transit - exec.execute(new NopAction<>(leftRange), EMPTY_INTEGER_CALLBACK); - exec.execute(new NopAction<>(rightRange), EMPTY_INTEGER_CALLBACK); - exec.execute(new NopAction<>(includedRange), EMPTY_INTEGER_CALLBACK); - exec.execute(new NopAction<>(outsideRange), EMPTY_INTEGER_CALLBACK); - - List timeRangeChanges = new ArrayList<>(); - CounterAction action = new CounterAction(requestRange, true, false) { - @Override - public void setTimeRange(TimeRange timeRange) { - super.setTimeRange(timeRange); - timeRangeChanges.add(timeRange); - } - }; - List expectedRangeChanges = new ArrayList<>(); - expectedRangeChanges.add(expectedRange); - expectedRangeChanges.add(requestRange); - exec.execute(action, EMPTY_INTEGER_CALLBACK); - assertListEquals(expectedRangeChanges, timeRangeChanges); - } - - private void assertListEquals(List expected, List actual) { - assertNotNull(expected); - assertNotNull(actual); - assertEquals(expected.size(), actual.size()); - Iterator expectedIter = expected.iterator(); - Iterator actualIter = actual.iterator(); - while (expectedIter.hasNext()) { - assertEquals(expectedIter.next(), actualIter.next()); - } + public void testOutOfOrderDelivery() { + throw new UnsupportedOperationException(); //TODO Implement } } diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java new file mode 100644 index 00000000000..5dbc604774b --- /dev/null +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java @@ -0,0 +1,95 @@ +package com.sap.sse.gwt.test.client; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.gwt.client.async.TimeRangeResultCache; + +public class TimeRangeResultCacheTest { + private class MilliTimeRange extends TimeRangeImpl { + public MilliTimeRange(long from, long to) { + super(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); + } + + public MilliTimeRange(TimeRange timeRange) { + super(timeRange.from(), timeRange.to()); + } + + @Override + public String toString() { + return from().asMillis() + " - " + to().asMillis(); + } + } + + @Test + public void testSimpleRequest() { + TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + TimeRange timeRange = new MilliTimeRange(100, 1000); + assertEquals(timeRange, trimmer.trimAndRegisterRequest(timeRange, false)); + List> expectedResult = new ArrayList<>(1); + expectedResult.add(new Pair<>(timeRange, null)); + assertArrayEquals(expectedResult.toArray(), trimmer.registerAndCollectResult(timeRange, null, null).toArray()); + } + + @Test + public void testTrimSinglePass() { + TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + final TimeRange requestRange = new MilliTimeRange(10_000L, 20_000L); + final TimeRange expectedRange = new MilliTimeRange(15_000L, 18_000L); + final TimeRange leftRange = new MilliTimeRange(10_000L, 15_000L); + final TimeRange rightRange = new MilliTimeRange(18_000L, 30_000L); + final TimeRange includedRange = new MilliTimeRange(16_000L, 17_000L); + final TimeRange outsideRange = new MilliTimeRange(50_000L, 100_000L); + trimmer.trimAndRegisterRequest(leftRange, true); + trimmer.trimAndRegisterRequest(rightRange, true); + trimmer.trimAndRegisterRequest(includedRange, true); + trimmer.trimAndRegisterRequest(outsideRange, true); + assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, false))); + } + + @Test + public void testTrimMultiplePasses() { + TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + final TimeRange requestRange = new MilliTimeRange(100_000L, 200_000L); + final TimeRange expectedRange = new MilliTimeRange(150_000L, 190_000L); + final TimeRange leftRange = new MilliTimeRange( 90_000L, 125_000L); + final TimeRange leftSecondPassRange = new MilliTimeRange(120_000L, 150_000L); + final TimeRange rightRange = new MilliTimeRange(190_000L, 300_000L); + final TimeRange outsideRange = new MilliTimeRange( 50_000L, 100_000L); + trimmer.trimAndRegisterRequest(leftSecondPassRange, true); + trimmer.trimAndRegisterRequest(leftRange, true); + trimmer.trimAndRegisterRequest(rightRange, true); + trimmer.trimAndRegisterRequest(outsideRange, true); + assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, false))); + } + + @Test + public void testGatherResults() { + TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + final Integer requestedInt = 1; + final Integer leftInt = -1; + final Integer outsideInt = 99; + final TimeRange requestedRange = new MilliTimeRange(100_000L, 200_000L); + final TimeRange expectedRange = new MilliTimeRange(150_000L, 200_000L); + final TimeRange leftRange = new MilliTimeRange( 50_000L, 150_000L); + final TimeRange outsideRange = new MilliTimeRange(200_000L, 300_000L); + final List> expectedResult = new ArrayList<>(); + expectedResult.add(new Pair<>(expectedRange, requestedInt)); + expectedResult.add(new Pair<>(leftRange, leftInt)); + trimmer.registerAndCollectResult(leftRange, leftInt, null); + trimmer.registerAndCollectResult(outsideRange, outsideInt, null); + TimeRange trimmedRange = trimmer.trimAndRegisterRequest(requestedRange, false); + assertEquals(expectedRange, trimmedRange); + List> result = trimmer.registerAndCollectResult(trimmedRange, requestedInt, null); + assertArrayEquals(expectedResult.toArray(), result.toArray()); + } +} diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index 2858144337c..f06e5106547 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -1,201 +1,158 @@ package com.sap.sse.gwt.client.async; -import java.util.ArrayDeque; -import java.util.Deque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Objects; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.sap.sse.common.MultiTimeRange; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.impl.MultiTimeRangeImpl; -public class TimeRangeActionsExecutor { - public interface TimeRangeAsyncAction extends AsyncAction { - TimeRange getTimeRange(); - void setTimeRange(TimeRange timeRange); - } +/** + * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. For the actual execution this + * class relies on an {@link AsyncActionsExecutor}. + * + * + * The idea is to provide an abstraction layer which will handle caching and intelligently cut down on the number and + * size of request being made to the server. + * + * @param + * Type returned by remote procedure. See {@link TimeRangeAsyncAction}. + * @param + * Type representing an individual part or channel of a complete {@code Result}. + * @param + * Typed used to index {@code SubResult}s. + * + * @author Tim Hessenmüller (D062243) + */ +public class TimeRangeActionsExecutor { + private final class ExecutorCallback implements AsyncCallback { + private final TimeRangeAsyncCallback callback; + private final Collection> requestedTimeRanges; + private final Map trimmedTimeRangeMap; - public interface TimeRangeResultJoiner { - /** - * - * @param resultsToJoin - * @param timeRange - * @return {@code null} to indicate missing data. - */ - T join(List> resultsToJoin, TimeRange timeRange); - } - - private class Request implements AsyncCallback { - private final TimeRangeAsyncAction action; - private final AsyncCallback callback; - private T result = null; - - public Request(TimeRangeAsyncAction asyncAction, AsyncCallback asyncCallback) { - this.action = asyncAction; - this.callback = asyncCallback; + private ExecutorCallback(Collection> requestedTimeRanges, + List> trimmedTimeRanges, TimeRangeAsyncCallback callback) { + this.callback = callback; + this.requestedTimeRanges = requestedTimeRanges; + trimmedTimeRangeMap = new HashMap<>(trimmedTimeRanges.size()); + trimmedTimeRanges.forEach(pair -> trimmedTimeRangeMap.put(pair.getA(), pair.getB())); } @Override - public void onSuccess(T result) { - TimeRange requestedTimeRange = TimeRangeActionsExecutor.this.requestedRangeMap.get(this); - // If this requests TimeRange was trimmed or the results are already present - if (requestedTimeRange == null || !requestedTimeRange.equals(action.getTimeRange())) { - //TODO reduce amount of elements that the joiner has to search through - result = (T) TimeRangeActionsExecutor.this.joinResults((TimeRangeActionsExecutor.Request) this); //TODO Type check - } - TimeRangeActionsExecutor.this.requestedRangeMap.remove(this); - if (requestedTimeRange != null) { - action.setTimeRange(requestedTimeRange); //TODO Resetting this to what it originally was is inefficient - } - if (result != null) { - this.result = result; - TimeRangeActionsExecutor.this.receivedRequestsRingBuffer.add((TimeRangeActionsExecutor.Request) this); //TODO Type check - TimeRangeActionsExecutor.this.receivedRange = null; - } - if (callback != null) { - callback.onSuccess(result); + public void onSuccess(Result result) { + Map unzippedResultMap = result != null ? callback.unzipResults(result) + : Collections.emptyMap(); + Map> completedResultMap = new HashMap<>(); + for (Pair request : requestedTimeRanges) { + SubResult subResult = unzippedResultMap.get(request.getA()); + TimeRangeResultCache cache = getSubResultCache(request.getA()); + List> partialResults = cache.registerAndCollectResult( + trimmedTimeRangeMap.get(request.getA()), subResult, new AsyncCallback() { + @Override + public void onSuccess(Void voidResult) { + ExecutorCallback.this.onSuccess(result); + } + + @Override + public void onFailure(Throwable caught) { + } + }); + if (partialResults == null) { + return; // A required request is still in transit + } + SubResult completedSubResult = callback.joinSubResults(request.getB(), partialResults); + completedResultMap.put(request.getA(), new Pair<>(request.getB(), completedSubResult)); } + callback.onSuccess(completedResultMap); } @Override public void onFailure(Throwable caught) { - TimeRangeActionsExecutor.this.requestedRangeMap.remove(this); - //TODO Check if other requests are now missing parts of their request - if (callback != null) { - callback.onFailure(caught); - } - } - - public TimeRangeAsyncAction getAction() { - return action; - } - - public T getResult() { - return result; + trimmedTimeRangeMap.entrySet().forEach(e -> getSubResultCache(e.getKey()).registerFailure(e.getValue())); + callback.onFailure(caught); } } - + /** + * {@link AsyncActionsExecutor} that will be used to actually execute remote procedures. + */ protected final AsyncActionsExecutor executor; + /** + * Action category passed along to the {@link #executor}. + * + * @see AsyncActionsExecutor#execute(AsyncAction, String, AsyncCallback) + */ protected String actionCategory = MarkedAsyncCallback.CATEGORY_GLOBAL; - protected final TimeRangeResultJoiner resultJoiner; - protected final Deque> waitingForOtherRequestsQueue = new ArrayDeque<>(); - /** - * Holds {@link TimeRange}s of requests that have been given to the {@link #executor}. - */ - protected final Map, TimeRange> requestedRangeMap = new HashMap<>(); - /** - * Ring buffer holding the last successful responses. - */ - protected final Deque> receivedRequestsRingBuffer = new ArrayDeque>() { - private static final long serialVersionUID = 1L; - private final int bufferSize = 16; - @Override - public void addFirst(Request e) { - while (size() >= bufferSize) { - super.pollFirst(); - } - super.addFirst(e); - } - @Override - public void addLast(Request e) { - while (size() >= bufferSize) { - super.pollFirst(); - } - super.addLast(e); - } - }; - /** - * Get with {@link #getReceivedTimeRange()} as this will be set to {@code null} once it becomes invalid. - * {@link MultiTimeRange} containing {@link #receivedRequestsRingBuffer} element's {@link TimeRange}s. - */ - protected MultiTimeRange receivedRange; + protected final Map> cacheMap = new HashMap<>(); - public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor, TimeRangeResultJoiner resultJoiner) { - this.executor = actionsExecutor; - this.resultJoiner = resultJoiner; + public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor) { + this.executor = Objects.requireNonNull(actionsExecutor); } - public void execute(TimeRangeAsyncAction action, AsyncCallback callback) { + public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor, String actionCategory) { + this.executor = Objects.requireNonNull(actionsExecutor); + this.actionCategory = actionCategory; + } + + /** + * Executes a {@link TimeRangeAsyncAction} and returns the results to a {@link TimeRangeAsyncCallback}. Calls + * {@link #execute(TimeRangeAsyncAction, AsyncCallback, boolean)} with {@code forceTimeRange} set to {@code false}. + * + * @param action + * {@link TimeRangeAsyncAction} to execute. + * @param callback + * {@link TimeRangeAsyncCallback} to return results to. + * @see #execute(TimeRangeAsyncAction, TimeRangeAsyncCallback, boolean) + */ + public void execute(TimeRangeAsyncAction action, + TimeRangeAsyncCallback callback) { execute(action, callback, /* forceTimeRange */ false); } - public void execute(TimeRangeAsyncAction action, AsyncCallback callback, - boolean forceTimeRange) { - Request request = new Request<>(action, callback); - TimeRange requestedTimeRange = action.getTimeRange(); - if (!forceTimeRange) { - TimeRange missingTimeRange = trimActionTimeRange(requestedTimeRange); - // If all data is already present simply return the results - if (missingTimeRange == null) { - request.onSuccess(null); - return; - } - action.setTimeRange(missingTimeRange); - } - - requestedRangeMap.put(request, requestedTimeRange); - //TODO waitingQueue - //executor.getNumberOfPendingActionsPerType(actionCategory); - - executor.execute(action, actionCategory, request); - } - /** - * @return trimmed {@link TimeRange} or {@code null} if the request is unnecessary since the results are already here. + * Executes a {@link TimeRangeAsyncAction} and returns the results to a {@link TimeRangeAsyncCallback}. + * + * @param action + * {@link TimeRangeAsyncAction} to execute. + * @param callback + * {@link TimeRangeAsyncCallback} to return results to. + * @param forceTimeRange + * if {@code false} the request will be optimized. + * @see #execute(TimeRangeAsyncAction, TimeRangeAsyncCallback) */ - private TimeRange trimActionTimeRange(TimeRange timeRange) { - // Signal to return result immediately if everything is already there - if (getReceivedTimeRange().includes(timeRange)) { - return null; + public void execute(TimeRangeAsyncAction action, + TimeRangeAsyncCallback callback, boolean forceTimeRange) { + List> trimmedTimeRanges = new ArrayList<>(action.getTimeRanges().size()); + for (Pair part : action.getTimeRanges()) { + TimeRangeResultCache cache = getSubResultCache(part.getA()); + TimeRange trimmedRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange); + if (trimmedRange != null) { + trimmedTimeRanges.add(part); + } } - // Trim against already fetched results and mark them - for (TimeRange rangeToTrimBy : getReceivedTimeRange()) { - timeRange = trimTimeRange(timeRange, rangeToTrimBy); //TODO Mark needed results so they don't get dropped + if (!trimmedTimeRanges.isEmpty()) { + executor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + action.execute(trimmedTimeRanges, callback); + } + }, actionCategory, new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback)); + } else { + new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback).onSuccess(null); } - // Trim against requests already in transit - for (TimeRange rangeToTrimBy : requestedRangeMap.values()) { - timeRange = trimTimeRange(timeRange, rangeToTrimBy); - } - return timeRange; } - private static TimeRange trimTimeRange(TimeRange toTrim, TimeRange trimWith) { - //TODO A TimeRange which had lain within toTrim on the first pass might now be usable on a second pass - if (toTrim.intersects(trimWith) && !toTrim.liesWithin(trimWith) && !toTrim.includes(trimWith)) { - return toTrim.subtract(trimWith).iterator().next(); + private TimeRangeResultCache getSubResultCache(Key key) { + TimeRangeResultCache cache = cacheMap.get(key); + if (cache == null) { + cache = new TimeRangeResultCache<>(); + cacheMap.put(key, cache); } - return toTrim; - } - - private Result joinResults(Request partialResult) { - final TimeRange requestedTimeRange = requestedRangeMap.get(partialResult); - if (requestedTimeRange == null) { //TODO Maybe only log since we could recover by using the trimmed TimeRange from Request.getAction().getTimeRange()? Also this case SHOULD never occur - throw new IllegalStateException("TimeRange for Request not found in requestedRangeMap"); - } - List> results = receivedRequestsRingBuffer.stream() - .filter(r -> r.getAction().getTimeRange().intersects(requestedTimeRange)) - .map(r -> new Pair<>(r.getAction().getTimeRange(), r.getResult())) - .collect(Collectors.toList()); - Result result = resultJoiner.join(results, requestedTimeRange); - if (result == null) { - //TODO Data missing! Fire off another request or wait for it to finish? - } - return result; - } - - protected MultiTimeRange getReceivedTimeRange() { - if (receivedRange == null) { - TimeRange[] timeRanges = receivedRequestsRingBuffer.stream() - .map(Request::getAction) - .map(TimeRangeAsyncAction::getTimeRange) - .toArray(TimeRange[]::new); - receivedRange = new MultiTimeRangeImpl(timeRanges); - } - return receivedRange; + return cache; } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java new file mode 100644 index 00000000000..48dfd019f9f --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java @@ -0,0 +1,29 @@ +package com.sap.sse.gwt.client.async; + +import java.util.Collection; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; + +/** + * An action that will execute a remote procedure. + * + * + * + * @param Type returned by remote procedure. + * @param Type used to index + * + * @author Tim Hessenmüller (D062243) + */ +public interface TimeRangeAsyncAction { + /** + * Starts execution of remote procedure. + * Might be called multiple times with new timeRanges. + * @param timeRanges + * @param callback + */ + void execute(Collection> timeRanges, AsyncCallback callback); + + Collection> getTimeRanges(); +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java new file mode 100644 index 00000000000..7b79a8c555c --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java @@ -0,0 +1,15 @@ +package com.sap.sse.gwt.client.async; + +import java.util.List; +import java.util.Map; + +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; + +public interface TimeRangeAsyncCallback { + Map unzipResults(Result result); + SubResult joinSubResults(TimeRange timeRange, List> toJoin); + + void onSuccess(Map> results); + void onFailure(Throwable caught); +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java new file mode 100644 index 00000000000..871b6d8104c --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -0,0 +1,191 @@ +package com.sap.sse.gwt.client.async; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Util.Pair; + +/** + * + * + * + * @param + * Type to be cached. + * + * @author Tim Hessenmüller (D062243) + */ +public class TimeRangeResultCache { + private static final int TRIM_MAX_ITERATIONS = 20; + private static final int CACHE_SIZE = 32; + + protected class Request { + private final Set> dependsOnSet = new HashSet<>(); + private boolean hasResult = false; + private Pair result = null; + private final List> waitingOnResultList = new ArrayList<>(0); // TODO Verify initial cap + + public Request() { + } + + public Request(Pair result) { + this.hasResult = true; + this.result = result; + } + + public Set> getDependencies() { + return dependsOnSet; + } + + public void addDependency(Request request) { + dependsOnSet.add(request); + } + + public void clearDependencies() { + dependsOnSet.clear(); + } + + public Pair getResult() { + return result; + } + + public void setResult(Pair result) { + this.hasResult = true; + this.result = result; + waitingOnResultList.forEach(c -> c.onSuccess(null)); + waitingOnResultList.clear(); + } + + public boolean hasResult() { + return hasResult; + } + + public void registerOnResultCallback(AsyncCallback callback) { + waitingOnResultList.add(callback); + } + } + + private final Map> requestCache = new LinkedHashMap>( + /* initialCapacity */ CACHE_SIZE, /* loadFactor */ .75f, /* accessOrder */ true) { + private static final long serialVersionUID = 1L; // TODO Generated id is 1? + + @Override + protected boolean removeEldestEntry(Entry> eldest) { + return size() >= CACHE_SIZE; // TODO + } + }; + + /** + * + * @param toTrim + * {@link TimeRange} to request. + * @param forceTimeRange + * if {@code false} the request will be optimized with cached results. + * @return {@link TimeRange} to request or {@code null} if no request is to be made. + */ + public TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { + Request request = new Request<>(); + if (!forceTimeRange) { // TODO Remove old results? + toTrim = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); + } + // Signal result already present + if (toTrim == null) { + return null; + } + requestCache.put(toTrim, request); + return toTrim; + } + + /** + * Registers that a request has returned with a result and collects all other needed cached results that are needed + * to construct a complete, time-continuous {@code Result}. + * + * @param timeRange + * {@link TimeRange} that this request was trimmed to. + * @param result + * {@code Result} of this request to cache. + * @param callbackIfResultsAreMissing + * {@link AsyncCallback}{@code } to call {@link AsyncCallback#onSuccess(Object)} on once a request + * this one depends on and which was still in transit returns. + * @return {@link List} of {@link TimeRange}, {@code Result} {@link Pair}s that contain all data needed to construct + * a time-continuous {@code Result} object. + */ + public List> registerAndCollectResult(TimeRange timeRange, Result result, + AsyncCallback callbackIfResultsAreMissing) { + Pair requestResult = new Pair<>(timeRange, result); + Request request = requestCache.get(timeRange); + if (request != null) { + request.setResult(requestResult); + } else { + request = new Request<>(requestResult); + requestCache.put(timeRange, request); + } + + List> results = new ArrayList<>(request.getDependencies().size() + 1); + results.add(requestResult); + for (Request dep : request.getDependencies()) { + if (!dep.hasResult()) { + dep.registerOnResultCallback(callbackIfResultsAreMissing); + return null; + } + results.add(dep.getResult()); + } + request.clearDependencies(); + return results; + } + + /** + * + * @param timeRange {@link TimeRange} of failed request. + */ + public void registerFailure(TimeRange timeRange) { + Request request = requestCache.remove(timeRange); + // TODO Request dependent on this one? + request.setResult(null); + } + + /** + * Trims a potential request with cached results. + * + * @param toTrim {@link TimeRange} + * @param request {@link Request} to attach dependencies to. + * @param rangesToTrimWith + * @return {@link TimeRange} which to request from the server because it is not in the cache. + */ + private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, + Iterable>> rangesToTrimWith) { + List>> rangesToTrimWithList = new ArrayList<>(); + rangesToTrimWith.forEach(rangesToTrimWithList::add); + for (int i = 0; i < TRIM_MAX_ITERATIONS; i++) { + boolean rangeWasTrimmedThisIteration = false; + Iterator>> iter = rangesToTrimWithList.iterator(); + while (iter.hasNext()) { + final Map.Entry> element = iter.next(); + final TimeRange trimWith = element.getKey(); + if (toTrim.intersects(trimWith)) { + if (!toTrim.liesWithin(trimWith) && !toTrim.includes(trimWith)) { + if (request != null) { + request.addDependency(element.getValue()); + } + toTrim = toTrim.subtract(trimWith).iterator().next(); // TODO Allow trimming to null + iter.remove(); + rangeWasTrimmedThisIteration = true; + } + } else { + iter.remove(); + } + } + if (!rangeWasTrimmedThisIteration) { + break; + } + } + return toTrim; + } +} From 19dc30b378ef987bbf3b994b909d9f82d5185791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Mon, 15 Jun 2020 14:59:25 +0200 Subject: [PATCH 024/235] bug5259: Fixed TimeRange trimming rules --- .../client/async/TimeRangeResultCache.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 871b6d8104c..07eb6e2fdcd 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -143,7 +143,8 @@ public class TimeRangeResultCache { /** * - * @param timeRange {@link TimeRange} of failed request. + * @param timeRange + * {@link TimeRange} of failed request. */ public void registerFailure(TimeRange timeRange) { Request request = requestCache.remove(timeRange); @@ -154,8 +155,10 @@ public class TimeRangeResultCache { /** * Trims a potential request with cached results. * - * @param toTrim {@link TimeRange} - * @param request {@link Request} to attach dependencies to. + * @param toTrim + * {@link TimeRange} + * @param request + * {@link Request} to attach dependencies to. * @param rangesToTrimWith * @return {@link TimeRange} which to request from the server because it is not in the cache. */ @@ -170,11 +173,22 @@ public class TimeRangeResultCache { final Map.Entry> element = iter.next(); final TimeRange trimWith = element.getKey(); if (toTrim.intersects(trimWith)) { - if (!toTrim.liesWithin(trimWith) && !toTrim.includes(trimWith)) { + // Only consider TimeRanges that touch or overlap toTrim + if (toTrim.liesWithin(trimWith)) { + // toTrim is fully covered by trimWith if (request != null) { request.addDependency(element.getValue()); } - toTrim = toTrim.subtract(trimWith).iterator().next(); // TODO Allow trimming to null + return null; + } + if (!toTrim.includes(trimWith) + || (toTrim.from().equals(trimWith.from()) || toTrim.to().equals(trimWith.to()))) { + // toTrim and trimWith overlap and trimWith does not lie within toTrim + // OR special case where trimWith lies within toTrim but touches one of the ends + if (request != null) { + request.addDependency(element.getValue()); + } + toTrim = toTrim.subtract(trimWith).iterator().next(); iter.remove(); rangeWasTrimmedThisIteration = true; } From 8efcf3d8ab782d810d9b14f76acce7532bf39cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Mon, 15 Jun 2020 16:25:02 +0200 Subject: [PATCH 025/235] bug5259: Added documentation to TimeRangeResultCache --- .../client/async/TimeRangeResultCache.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 07eb6e2fdcd..ce8c71c5289 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -14,12 +14,12 @@ import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; /** - * - * + * Optimizes requests by trimming their wanted {@link TimeRange} against a cache of (fetched and still outstanding) + * results. * * @param * Type to be cached. - * + * @see TimeRangeActionsExecutor * @author Tim Hessenmüller (D062243) */ public class TimeRangeResultCache { @@ -83,12 +83,14 @@ public class TimeRangeResultCache { }; /** - * + * Trims the requested {@link TimeRange} against results that already cached and registers the trimmed + * {@link TimeRange} with the cache. + * * @param toTrim * {@link TimeRange} to request. * @param forceTimeRange * if {@code false} the request will be optimized with cached results. - * @return {@link TimeRange} to request or {@code null} if no request is to be made. + * @return {@link TimeRange} to request or {@code null} if no request is to be made since the results are cached. */ public TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { Request request = new Request<>(); @@ -106,7 +108,7 @@ public class TimeRangeResultCache { /** * Registers that a request has returned with a result and collects all other needed cached results that are needed * to construct a complete, time-continuous {@code Result}. - * + * * @param timeRange * {@link TimeRange} that this request was trimmed to. * @param result @@ -142,25 +144,31 @@ public class TimeRangeResultCache { } /** - * + * Removes the failed request from cache. + * * @param timeRange * {@link TimeRange} of failed request. */ public void registerFailure(TimeRange timeRange) { Request request = requestCache.remove(timeRange); - // TODO Request dependent on this one? - request.setResult(null); + // TODO Requests dependent on this one? + if (request != null) { + request.setResult(null); + } } /** - * Trims a potential request with cached results. + * Trims a potential request with cached results. For simplicity {@code toTrim} will currently not be split up into + * multiple TimeRanges if a part in the middle is cached. * * @param toTrim - * {@link TimeRange} + * {@link TimeRange} to trim. * @param request * {@link Request} to attach dependencies to. * @param rangesToTrimWith - * @return {@link TimeRange} which to request from the server because it is not in the cache. + * {@link TimeRange}s that {@code toTrim} will be trimmed against. + * @return {@link TimeRange} which to request from the server because it is not covered by {@code rangesToTrimWith} + * or {@code null} if no request to the server is to be made. */ private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, Iterable>> rangesToTrimWith) { From d4894c8b04e01c764fa39e327d219ef7ac742cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 19 Jun 2020 14:38:02 +0200 Subject: [PATCH 026/235] bug5259: Implemented TimeRangeActionsExecutorTest.testOutOfOrderDelivery --- .../client/TimeRangeActionsExecutorTest.java | 103 +++++++++++++++++- .../async/TimeRangeActionsExecutor.java | 13 ++- 2 files changed, 110 insertions(+), 6 deletions(-) diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java index bd87fea232b..24751025618 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java @@ -1,6 +1,7 @@ package com.sap.sse.gwt.test.client; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; @@ -9,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; @@ -48,10 +50,11 @@ public class TimeRangeActionsExecutorTest { @Test public void testSimpleRequest() { - TimeRangeActionsExecutor, Integer, String> exec = new TimeRangeActionsExecutor<>(new MockAsyncActionsExecutor()); + TimeRangeActionsExecutor, Integer, String> exec = new TimeRangeActionsExecutor<>( + new MockAsyncActionsExecutor()); final AtomicBoolean actionHasRun = new AtomicBoolean(false); final AtomicBoolean callbackHasRun = new AtomicBoolean(false); - exec.execute(new TimeRangeAsyncAction, String>() { + exec.execute(new TimeRangeAsyncAction, String>() { @Override public void execute(Collection> timeRanges, AsyncCallback> callback) { @@ -62,6 +65,7 @@ public class TimeRangeActionsExecutorTest { } callback.onSuccess(results); } + @Override public Collection> getTimeRanges() { Collection> channels = new ArrayList<>(); @@ -69,11 +73,12 @@ public class TimeRangeActionsExecutorTest { channels.add(new Pair<>("bb", new MilliTimeRange(1, 2))); return channels; } - }, new TimeRangeAsyncCallback, Integer, String>() { + }, new TimeRangeAsyncCallback, Integer, String>() { @Override public Map unzipResults(Map result) { return result; } + @Override public void onSuccess(Map> results) { callbackHasRun.set(true); @@ -82,10 +87,12 @@ public class TimeRangeActionsExecutorTest { assertEquals(result.getKey().length(), result.getValue().getB().intValue()); } } + @Override public void onFailure(Throwable caught) { assertTrue(false); } + @Override public Integer joinSubResults(TimeRange timeRange, List> toJoin) { return toJoin.get(0).getB(); @@ -97,6 +104,94 @@ public class TimeRangeActionsExecutorTest { @Test public void testOutOfOrderDelivery() { - throw new UnsupportedOperationException(); //TODO Implement + TimeRangeActionsExecutor, Integer, String> exec = new TimeRangeActionsExecutor<>( + new MockAsyncActionsExecutor()); + final AtomicBoolean callbackHasRun = new AtomicBoolean(false); + AtomicReference>> lateCallback = new AtomicReference<>(); + // Fire off first request which will simulate a long round trip time + exec.execute(new TimeRangeAsyncAction, String>() { + @Override + public void execute(Collection> timeRanges, + AsyncCallback> callback) { + // Don't call callback now + lateCallback.set(callback); + } + + @Override + public Collection> getTimeRanges() { + List> timeRanges = new ArrayList<>(); + timeRanges.add(new Pair<>("x", TimeRangeImpl.create(100_000L, 120_000L))); + return timeRanges; + } + }, new TimeRangeAsyncCallback, Integer, String>() { + + @Override + public Map unzipResults(Map result) { + return result; + } + + @Override + public void onSuccess(Map> results) { + assertEquals(Integer.valueOf(1), results.get("x").getB()); + } + + @Override + public void onFailure(Throwable caught) { + assertTrue(false); // Fail test + } + + @Override + public Integer joinSubResults(TimeRange timeRange, List> toJoin) { + return toJoin.stream().mapToInt(Pair::getB).sum(); + } + }); + // Fire off second request which requires the results from the first request but that one is still in transit + final TimeRange timeRange = TimeRangeImpl.create(110_000L, 130_000L); + exec.execute(new TimeRangeAsyncAction, String>() { + @Override + public void execute(Collection> timeRanges, + AsyncCallback> callback) { + Map result = new HashMap<>(); + result.put("x", 2); + callback.onSuccess(result); + } + + @Override + public Collection> getTimeRanges() { + List> timeRanges = new ArrayList<>(); + timeRanges.add(new Pair<>("x", timeRange)); + return timeRanges; + } + }, new TimeRangeAsyncCallback, Integer, String>() { + + @Override + public Map unzipResults(Map result) { + return result; + } + + @Override + public void onSuccess(Map> results) { + Pair result = results.get("x"); + assertNotNull(result); + assertEquals(timeRange, result.getA()); + assertEquals(Integer.valueOf(3), result.getB()); + callbackHasRun.set(true); + } + + @Override + public void onFailure(Throwable caught) { + assertTrue(false); // Fail test + } + + @Override + public Integer joinSubResults(TimeRange timeRange, List> toJoin) { + return toJoin.stream().mapToInt(Pair::getB).sum(); + } + }); + // After the second request has finished the first one now gets answered + Map resultsFromFirstRequest = new HashMap<>(); + resultsFromFirstRequest.put("x", 1); + lateCallback.get().onSuccess(resultsFromFirstRequest); + assertTrue(callbackHasRun.get()); } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index f06e5106547..9f4d03df1b0 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -30,6 +30,9 @@ import com.sap.sse.common.Util.Pair; * @author Tim Hessenmüller (D062243) */ public class TimeRangeActionsExecutor { + /** + * Callback called by {@link TimeRangeActionsExecutor#executor} upon receiving an answer from the server. + */ private final class ExecutorCallback implements AsyncCallback { private final TimeRangeAsyncCallback callback; private final Collection> requestedTimeRanges; @@ -88,7 +91,7 @@ public class TimeRangeActionsExecutor { * @see AsyncActionsExecutor#execute(AsyncAction, String, AsyncCallback) */ protected String actionCategory = MarkedAsyncCallback.CATEGORY_GLOBAL; - protected final Map> cacheMap = new HashMap<>(); + protected final Map> cacheMap = new HashMap<>(); //TODO Invalidation public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor) { this.executor = Objects.requireNonNull(actionsExecutor); @@ -127,12 +130,18 @@ public class TimeRangeActionsExecutor { */ public void execute(TimeRangeAsyncAction action, TimeRangeAsyncCallback callback, boolean forceTimeRange) { + if (action == null) { + throw new IllegalArgumentException("action must not be null!"); + } + if (callback == null) { + throw new IllegalArgumentException("callback must not be null!"); + } List> trimmedTimeRanges = new ArrayList<>(action.getTimeRanges().size()); for (Pair part : action.getTimeRanges()) { TimeRangeResultCache cache = getSubResultCache(part.getA()); TimeRange trimmedRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange); if (trimmedRange != null) { - trimmedTimeRanges.add(part); + trimmedTimeRanges.add(new Pair<>(part.getA(), trimmedRange)); } } if (!trimmedTimeRanges.isEmpty()) { From 903f3441b9b97845614378bbd067874eb949b9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 19 Jun 2020 15:35:13 +0200 Subject: [PATCH 027/235] bug5259: Added documentation to TimeRangeAsyncCallback --- .../client/async/TimeRangeAsyncCallback.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java index 7b79a8c555c..01e1eb8e3e7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java @@ -6,10 +6,65 @@ import java.util.Map; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; +/** + * Interface that the caller must implement to receive a response from a remote procedure call initiated by a + * {@link TimeRangeAsyncAction}. + *

+ * + * When used with a {@link TimeRangeActionsExecutor} the data flow looks like this: + *

    + *
  1. {@link TimeRangeAsyncAction} gets executed and returns server response. + *
  2. The response gets split up into {@link SubResult}s by {@link Key} by {@link #unzipResults(Object)}. + *
  3. By {@link Key} the {@link SubResult}s get cached and get put into {@link #joinSubResults(TimeRange, List)} along + * with all other required cached {@link SubResult}s.
  4. + *
  5. The completed {@link SubResult}s returned by {@link #joinSubResults(TimeRange, List)} get collected and put into + * {@link #onSuccess(Map)}.
  6. + *
+ * + * @param + * @param + * test + * @param + * @see TimeRangeAsyncAction + * @see TimeRangeActionsExecutor + * @author Tim Hessenmüller (D062243) + */ public interface TimeRangeAsyncCallback { + /** + * Splits up a server response into {@link SubResult}s by {@link Key}. + * + * @param result + * {@link Result} returned by server. + * @return {@link Map} of {@link Key} {@link SubResult} pairs. + */ Map unzipResults(Result result); + + /** + * Joins possibly multiple {@link SubResult}s with different {@link TimeRange}s together to form the originally + * requested {@link SubResult} over {@code timeRange}. + * + * @param timeRange + * originally requested {@link TimeRange}. + * @param toJoin + * {@link List} of cached {@link SubResult}s with their respective {@link TimeRange}s. + * @return completed {@link SubResult}. + */ SubResult joinSubResults(TimeRange timeRange, List> toJoin); + /** + * Called after successful request and joining of {@link SubResult}s.
+ * It might be useful to apply the reverse operation of {@link #unzipResults(Object)} to {@code results}. + * + * @param results + * result of the remote procedure call. + */ void onSuccess(Map> results); + + /** + * Called when an exception occurs. + * + * @param caught + * {@link Throwable} + */ void onFailure(Throwable caught); } \ No newline at end of file From e031a2eec888e35514853556d6af41cae2ab2872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 19 Jun 2020 15:38:30 +0200 Subject: [PATCH 028/235] bug5259: Added documentation to TimeRangeAsyncAction --- .../client/async/TimeRangeAsyncAction.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java index 48dfd019f9f..0a9fff2f2ed 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java @@ -7,23 +7,32 @@ import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; /** - * An action that will execute a remote procedure. - * - * + * An action that will asynchronously execute a remote procedure. Executed by {@link TimeRangeActionsExecutor}. * - * @param Type returned by remote procedure. - * @param Type used to index + * @param + * Type returned by remote procedure. + * @param + * Type used to index + * @see TimeRangeActionsExecutor * * @author Tim Hessenmüller (D062243) */ public interface TimeRangeAsyncAction { /** - * Starts execution of remote procedure. - * Might be called multiple times with new timeRanges. + * Starts execution of remote procedure. Might be called multiple times with new timeRanges. + * * @param timeRanges + * {@link TimeRange}s to request from the server. They might differ from the wanted {@link TimeRange}s + * returned by {@link #getTimeRanges()} because of cached results. * @param callback + * {@link AsyncCallback} to give response to. */ void execute(Collection> timeRanges, AsyncCallback callback); + /** + * Gets the wanted {@link TimeRange}s per {@link Key}. + * + * @return wanted {@link TimeRange} for each wanted {@link Key}. + */ Collection> getTimeRanges(); } \ No newline at end of file From ae7bf44c9b8b758a4048961c2f7055ad664f0321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 19 Jun 2020 15:40:00 +0200 Subject: [PATCH 029/235] bug5259: Updated documentation for TimeRangeActionsExecutor --- .../sse/gwt/client/async/TimeRangeActionsExecutor.java | 6 +++--- .../sap/sse/gwt/client/async/TimeRangeResultCache.java | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index 9f4d03df1b0..13f2cf58fe3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -14,7 +14,7 @@ import com.sap.sse.common.Util.Pair; /** * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. For the actual execution this - * class relies on an {@link AsyncActionsExecutor}. + * class relies on an {@link AsyncActionsExecutor}.

* * * The idea is to provide an abstraction layer which will handle caching and intelligently cut down on the number and @@ -23,9 +23,9 @@ import com.sap.sse.common.Util.Pair; * @param * Type returned by remote procedure. See {@link TimeRangeAsyncAction}. * @param - * Type representing an individual part or channel of a complete {@code Result}. + * Type representing an individual part or channel of a complete {@link Result}. * @param - * Typed used to index {@code SubResult}s. + * Typed used to index {@link SubResult}s. * * @author Tim Hessenmüller (D062243) */ diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index ce8c71c5289..bf672c8a52a 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -107,17 +107,17 @@ public class TimeRangeResultCache { /** * Registers that a request has returned with a result and collects all other needed cached results that are needed - * to construct a complete, time-continuous {@code Result}. + * to construct a complete, time-continuous {@link Result}. * * @param timeRange * {@link TimeRange} that this request was trimmed to. * @param result - * {@code Result} of this request to cache. + * {@link Result} of this request to cache. * @param callbackIfResultsAreMissing * {@link AsyncCallback}{@code } to call {@link AsyncCallback#onSuccess(Object)} on once a request * this one depends on and which was still in transit returns. - * @return {@link List} of {@link TimeRange}, {@code Result} {@link Pair}s that contain all data needed to construct - * a time-continuous {@code Result} object. + * @return {@link List} of {@link TimeRange}, {@link Result} {@link Pair}s that contain all data needed to construct + * a time-continuous {@link Result} object. */ public List> registerAndCollectResult(TimeRange timeRange, Result result, AsyncCallback callbackIfResultsAreMissing) { From 91c62b920104b580f424831008bfd1844f82f830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Tue, 23 Jun 2020 13:21:32 +0200 Subject: [PATCH 030/235] bug5259: More documentation for TimeRangeActionsExecutor --- .../gwt/client/async/TimeRangeActionsExecutor.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index 13f2cf58fe3..7ef55a3d303 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -14,11 +14,16 @@ import com.sap.sse.common.Util.Pair; /** * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. For the actual execution this - * class relies on an {@link AsyncActionsExecutor}.

- * + * class relies on an {@link AsyncActionsExecutor}.
* * The idea is to provide an abstraction layer which will handle caching and intelligently cut down on the number and - * size of request being made to the server. + * size of request being made to the server.

+ * + * This executor operates on {@link TimeRangeAsyncAction}s which provide the wanted {@link TimeRange} by {@link Key} + * with {@link TimeRangeAsyncAction#getTimeRanges()}. E.g. The {@link Key} could be a competitor or a boat class.
+ * Each of the {@link TimeRange}s is then trimmed against its own cache of returned and outstanding requests in + * {@link TimeRangeResultCache}.
+ * After getting the server response the trimmed result is completed by the cache and {@link TimeRangeAsyncCallback}. * * @param * Type returned by remote procedure. See {@link TimeRangeAsyncAction}. @@ -26,6 +31,8 @@ import com.sap.sse.common.Util.Pair; * Type representing an individual part or channel of a complete {@link Result}. * @param * Typed used to index {@link SubResult}s. + * @see TimeRangeAsyncAction + * @see TimeRangeAsyncCallback * * @author Tim Hessenmüller (D062243) */ From 8d3177142e0698cd4d7bd360dd5dbfce8c8a2e47 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Tue, 23 Jun 2020 13:43:11 +0200 Subject: [PATCH 031/235] bug5259: factored MilliTimeRange that existed as equal copy in two tests into a separate class --- .../sse/gwt/test/client/MilliTimeRange.java | 22 +++++++++++++++++++ .../client/TimeRangeActionsExecutorTest.java | 16 -------------- .../test/client/TimeRangeResultCacheTest.java | 17 -------------- 3 files changed, 22 insertions(+), 33 deletions(-) create mode 100755 java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java new file mode 100755 index 00000000000..e4fcdcd8d70 --- /dev/null +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java @@ -0,0 +1,22 @@ +package com.sap.sse.gwt.test.client; + +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.TimeRangeImpl; + +class MilliTimeRange extends TimeRangeImpl { + private static final long serialVersionUID = -3274328893091285564L; + + public MilliTimeRange(long from, long to) { + super(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); + } + + public MilliTimeRange(TimeRange timeRange) { + super(timeRange.from(), timeRange.to()); + } + + @Override + public String toString() { + return from().asMillis() + " - " + to().asMillis(); + } +} \ No newline at end of file diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java index 24751025618..3854cfa5a22 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java @@ -17,7 +17,6 @@ import org.junit.Test; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.gwt.client.async.AsyncAction; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; @@ -33,21 +32,6 @@ public class TimeRangeActionsExecutorTest { } } - private class MilliTimeRange extends TimeRangeImpl { - public MilliTimeRange(long from, long to) { - super(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); - } - - public MilliTimeRange(TimeRange timeRange) { - super(timeRange.from(), timeRange.to()); - } - - @Override - public String toString() { - return from().asMillis() + " - " + to().asMillis(); - } - } - @Test public void testSimpleRequest() { TimeRangeActionsExecutor, Integer, String> exec = new TimeRangeActionsExecutor<>( diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java index 5dbc604774b..0cb0cd56280 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java @@ -10,26 +10,9 @@ import org.junit.Test; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.gwt.client.async.TimeRangeResultCache; public class TimeRangeResultCacheTest { - private class MilliTimeRange extends TimeRangeImpl { - public MilliTimeRange(long from, long to) { - super(new MillisecondsTimePoint(from), new MillisecondsTimePoint(to)); - } - - public MilliTimeRange(TimeRange timeRange) { - super(timeRange.from(), timeRange.to()); - } - - @Override - public String toString() { - return from().asMillis() + " - " + to().asMillis(); - } - } - @Test public void testSimpleRequest() { TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); From 36a23f8a373c870bc1c00b8ebf89d849b3731276 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Tue, 23 Jun 2020 19:42:22 +0200 Subject: [PATCH 032/235] bug4887: updated REST API doc for eventDelete --- .../webservices/api/v1/eventDeleteDoc.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html index 64ece6d3232..97b86de6636 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/eventDeleteDoc.html @@ -12,7 +12,7 @@

URL: /api/v1/events/{event-id}/

Description: -

Deletes and Event, optionally also allows to delete the LeaderboardGroup this event is part of and the linked OverallLeaderboard

+

Deletes an Event, optionally also allows to delete related LeaderboardGroups and/or all related Leaderboards and/or all related Regattas


@@ -25,12 +25,14 @@ - + @@ -39,7 +41,7 @@ -
Mandatory parameters:none; when no parameters are provided the call is a no-opnone;
Optional parameters: -
withLeaderboardGroupsAndOverallLeaderboard If true, the LeaderBoardGroup and the OverallLeaderboard will be deleted as well
+
withLeaderboardGroups - if true, deletes all associated LeaderboardGroups. Doing so will also delete the overall Leaderboards of each group.
+
withLeaderboards - if true, deletes all associated Leaderboards. The deletion of a RegattaLeaderboard entails the deletion of all Leaderboards holding the same Regatta.
+
withRegattas - if true, deletes all associated Regattas. Doing so will also delete all their RegattaLeaderboards and TrackedRaces.
Example:curl -X DELETE -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/events/0065b870-9ddb-42be-a7d4-6be2081cb63e?withLeaderboardGroupsAndOverallLeaderboard=true"
+
curl -X DELETE -H "Authorization: Bearer w9YSIfr/Zf6a127SZDddkhdAtQuuv0Jm1PSXlo1Yvd0=" "http://127.0.0.1:8888/sailingserver/api/v1/events/0065b870-9ddb-42be-a7d4-6be2081cb63e?withLeaderboardGroups=true"
From 40b0fcc543a969e4e9de38e1e062fa94231cea52 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 24 Jun 2020 10:15:09 +0200 Subject: [PATCH 033/235] bug5259: working on comments; making variables final where possible --- .../async/TimeRangeActionsExecutor.java | 46 ++++++++++------ .../client/async/TimeRangeAsyncAction.java | 5 +- .../client/async/TimeRangeResultCache.java | 52 +++++++++---------- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index 7ef55a3d303..166646f231a 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -13,24 +13,35 @@ import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; /** - * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. For the actual execution this - * class relies on an {@link AsyncActionsExecutor}.
+ * An Executor for efficient, asynchronous execution of {@link TimeRangeAsyncAction}s. This executor operates on + * {@link TimeRangeAsyncAction}s which specify the wanted {@link TimeRange} by {@link Key} with + * {@link TimeRangeAsyncAction#getTimeRanges()} and are expected to return a {@code Result}. The {@link Key} could, + * e.g., be a competitor or a boat class. + *

+ * + * This executor manages and optimizes concurrent requests for the same {@code Key} that have overlapping time ranges. + * This way, redundant requests for the same data can be avoided. This executor collects the results of the overlapping + * concurrent requests and uses them to satisfy each individual client request as if they had all been executed in their + * entirety. + *

+ * + * Each of the {@link TimeRange}s is then trimmed against the {@link TimeRaneResultCache cache} of returned and + * outstanding requests for the respective {@code Key}. After having received the server responses for all time ranges + * required to satisfy a {@link TimeRangeAsyncAction} the trimmed results are combined and the + * {@link TimeRangeAsyncCallback callback} is invoked. + *

+ * + * For the actual execution this class relies on an {@link AsyncActionsExecutor}. + *

* - * The idea is to provide an abstraction layer which will handle caching and intelligently cut down on the number and - * size of request being made to the server.

- * - * This executor operates on {@link TimeRangeAsyncAction}s which provide the wanted {@link TimeRange} by {@link Key} - * with {@link TimeRangeAsyncAction#getTimeRanges()}. E.g. The {@link Key} could be a competitor or a boat class.
- * Each of the {@link TimeRange}s is then trimmed against its own cache of returned and outstanding requests in - * {@link TimeRangeResultCache}.
- * After getting the server response the trimmed result is completed by the cache and {@link TimeRangeAsyncCallback}. * * @param - * Type returned by remote procedure. See {@link TimeRangeAsyncAction}. + * Type returned by the combined call. See {@link TimeRangeAsyncAction}. * @param - * Type representing an individual part or channel of a complete {@link Result}. + * Type representing an individual part or channel of a complete {@link Result}, as returned by + * an individual remote procedure call. * @param - * Typed used to index {@link SubResult}s. + * Type used to index {@link SubResult}s. * @see TimeRangeAsyncAction * @see TimeRangeAsyncCallback * @@ -59,10 +70,11 @@ public class TimeRangeActionsExecutor { : Collections.emptyMap(); Map> completedResultMap = new HashMap<>(); for (Pair request : requestedTimeRanges) { - SubResult subResult = unzippedResultMap.get(request.getA()); - TimeRangeResultCache cache = getSubResultCache(request.getA()); + final Key key = request.getA(); + SubResult subResult = unzippedResultMap.get(key); + TimeRangeResultCache cache = getSubResultCache(key); List> partialResults = cache.registerAndCollectResult( - trimmedTimeRangeMap.get(request.getA()), subResult, new AsyncCallback() { + trimmedTimeRangeMap.get(key), subResult, new AsyncCallback() { @Override public void onSuccess(Void voidResult) { ExecutorCallback.this.onSuccess(result); @@ -76,7 +88,7 @@ public class TimeRangeActionsExecutor { return; // A required request is still in transit } SubResult completedSubResult = callback.joinSubResults(request.getB(), partialResults); - completedResultMap.put(request.getA(), new Pair<>(request.getB(), completedSubResult)); + completedResultMap.put(key, new Pair<>(request.getB(), completedSubResult)); } callback.onSuccess(completedResultMap); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java index 0a9fff2f2ed..c67ba0ba34b 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java @@ -7,7 +7,10 @@ import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; /** - * An action that will asynchronously execute a remote procedure. Executed by {@link TimeRangeActionsExecutor}. + * An action that will asynchronously execute a remote procedure that is parameterized by time ranges and by a + * {@code Key} (e.g., a competitor, or a boat or similar) and that returns a {@code Result}. Executed by + * {@link TimeRangeActionsExecutor}. The benefit of using this class over making a straight call to the remote procedure + * is that the {@link TimeRangeActionsExecutor} understands, manages and optimized the overlaps in concurrent requests. * * @param * Type returned by remote procedure. diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index bf672c8a52a..310b283fed8 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -15,10 +15,11 @@ import com.sap.sse.common.Util.Pair; /** * Optimizes requests by trimming their wanted {@link TimeRange} against a cache of (fetched and still outstanding) - * results. + * results. In the context of a {@link TimeRangeActionsExecutor} such a cache is responsible for managing the requests + * for a single {@code Key}, such as a competitor or a boat class. * * @param - * Type to be cached. + * type of the results of the remote calls which are to be cached * @see TimeRangeActionsExecutor * @author Tim Hessenmüller (D062243) */ @@ -26,25 +27,25 @@ public class TimeRangeResultCache { private static final int TRIM_MAX_ITERATIONS = 20; private static final int CACHE_SIZE = 32; - protected class Request { - private final Set> dependsOnSet = new HashSet<>(); + protected class Request { + private final Set dependsOnSet = new HashSet<>(); private boolean hasResult = false; - private Pair result = null; + private Pair result = null; private final List> waitingOnResultList = new ArrayList<>(0); // TODO Verify initial cap public Request() { } - public Request(Pair result) { + public Request(Pair result) { this.hasResult = true; this.result = result; } - public Set> getDependencies() { + public Set getDependencies() { return dependsOnSet; } - public void addDependency(Request request) { + public void addDependency(Request request) { dependsOnSet.add(request); } @@ -52,11 +53,11 @@ public class TimeRangeResultCache { dependsOnSet.clear(); } - public Pair getResult() { + public Pair getResult() { return result; } - public void setResult(Pair result) { + public void setResult(Pair result) { this.hasResult = true; this.result = result; waitingOnResultList.forEach(c -> c.onSuccess(null)); @@ -72,12 +73,12 @@ public class TimeRangeResultCache { } } - private final Map> requestCache = new LinkedHashMap>( + private final Map requestCache = new LinkedHashMap( /* initialCapacity */ CACHE_SIZE, /* loadFactor */ .75f, /* accessOrder */ true) { private static final long serialVersionUID = 1L; // TODO Generated id is 1? @Override - protected boolean removeEldestEntry(Entry> eldest) { + protected boolean removeEldestEntry(Entry eldest) { return size() >= CACHE_SIZE; // TODO } }; @@ -93,7 +94,7 @@ public class TimeRangeResultCache { * @return {@link TimeRange} to request or {@code null} if no request is to be made since the results are cached. */ public TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { - Request request = new Request<>(); + Request request = new Request(); if (!forceTimeRange) { // TODO Remove old results? toTrim = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); } @@ -107,7 +108,7 @@ public class TimeRangeResultCache { /** * Registers that a request has returned with a result and collects all other needed cached results that are needed - * to construct a complete, time-continuous {@link Result}. + * to construct a complete, time-contiguous {@link Result}. * * @param timeRange * {@link TimeRange} that this request was trimmed to. @@ -121,18 +122,17 @@ public class TimeRangeResultCache { */ public List> registerAndCollectResult(TimeRange timeRange, Result result, AsyncCallback callbackIfResultsAreMissing) { - Pair requestResult = new Pair<>(timeRange, result); - Request request = requestCache.get(timeRange); + final Pair requestResult = new Pair<>(timeRange, result); + Request request = requestCache.get(timeRange); if (request != null) { request.setResult(requestResult); } else { - request = new Request<>(requestResult); + request = new Request(requestResult); requestCache.put(timeRange, request); } - - List> results = new ArrayList<>(request.getDependencies().size() + 1); + final List> results = new ArrayList<>(request.getDependencies().size() + 1); results.add(requestResult); - for (Request dep : request.getDependencies()) { + for (final Request dep : request.getDependencies()) { if (!dep.hasResult()) { dep.registerOnResultCallback(callbackIfResultsAreMissing); return null; @@ -150,7 +150,7 @@ public class TimeRangeResultCache { * {@link TimeRange} of failed request. */ public void registerFailure(TimeRange timeRange) { - Request request = requestCache.remove(timeRange); + Request request = requestCache.remove(timeRange); // TODO Requests dependent on this one? if (request != null) { request.setResult(null); @@ -170,15 +170,15 @@ public class TimeRangeResultCache { * @return {@link TimeRange} which to request from the server because it is not covered by {@code rangesToTrimWith} * or {@code null} if no request to the server is to be made. */ - private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, - Iterable>> rangesToTrimWith) { - List>> rangesToTrimWithList = new ArrayList<>(); + private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, + Iterable> rangesToTrimWith) { + List> rangesToTrimWithList = new ArrayList<>(); rangesToTrimWith.forEach(rangesToTrimWithList::add); for (int i = 0; i < TRIM_MAX_ITERATIONS; i++) { boolean rangeWasTrimmedThisIteration = false; - Iterator>> iter = rangesToTrimWithList.iterator(); + Iterator> iter = rangesToTrimWithList.iterator(); while (iter.hasNext()) { - final Map.Entry> element = iter.next(); + final Map.Entry element = iter.next(); final TimeRange trimWith = element.getKey(); if (toTrim.intersects(trimWith)) { // Only consider TimeRanges that touch or overlap toTrim From ecd45716c2ce21bebe06991cf3b98b9fe6ec4a14 Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 24 Jun 2020 10:41:16 +0200 Subject: [PATCH 034/235] bug5259: moved tests to same package as classes to test to avoid having to make methods public --- .../client => client/async}/AsyncActionsExecutorTest.java | 4 +--- .../gwt/{test/client => client/async}/MilliTimeRange.java | 2 +- .../async}/TimeRangeActionsExecutorTest.java | 7 +------ .../client => client/async}/TimeRangeResultCacheTest.java | 3 +-- .../com/sap/sse/gwt/client/async/TimeRangeResultCache.java | 4 ++-- 5 files changed, 6 insertions(+), 14 deletions(-) rename java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/{test/client => client/async}/AsyncActionsExecutorTest.java (97%) rename java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/{test/client => client/async}/MilliTimeRange.java (94%) rename java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/{test/client => client/async}/TimeRangeActionsExecutorTest.java (95%) rename java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/{test/client => client/async}/TimeRangeResultCacheTest.java (97%) diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/AsyncActionsExecutorTest.java similarity index 97% rename from java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java rename to java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/AsyncActionsExecutorTest.java index 121b468cfe9..1e3d1301ebb 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/AsyncActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/AsyncActionsExecutorTest.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.test.client; +package com.sap.sse.gwt.client.async; import org.junit.Test; @@ -7,8 +7,6 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.gwt.client.async.AsyncAction; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; /** * Some simple tests for the {@link AsyncActionsExecutor}. diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/MilliTimeRange.java similarity index 94% rename from java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java rename to java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/MilliTimeRange.java index e4fcdcd8d70..0592533a91f 100755 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/MilliTimeRange.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/MilliTimeRange.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.test.client; +package com.sap.sse.gwt.client.async; import com.sap.sse.common.TimeRange; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java similarity index 95% rename from java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java rename to java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java index 3854cfa5a22..3416ef1eda4 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.test.client; +package com.sap.sse.gwt.client.async; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -18,11 +18,6 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.TimeRangeImpl; -import com.sap.sse.gwt.client.async.AsyncAction; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.async.TimeRangeActionsExecutor; -import com.sap.sse.gwt.client.async.TimeRangeAsyncAction; -import com.sap.sse.gwt.client.async.TimeRangeAsyncCallback; public class TimeRangeActionsExecutorTest { private class MockAsyncActionsExecutor extends AsyncActionsExecutor { diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java similarity index 97% rename from java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java rename to java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java index 0cb0cd56280..bf5528148c9 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/test/client/TimeRangeResultCacheTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java @@ -1,4 +1,4 @@ -package com.sap.sse.gwt.test.client; +package com.sap.sse.gwt.client.async; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -10,7 +10,6 @@ import org.junit.Test; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; -import com.sap.sse.gwt.client.async.TimeRangeResultCache; public class TimeRangeResultCacheTest { @Test diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 310b283fed8..4d19bc6ef5e 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -84,7 +84,7 @@ public class TimeRangeResultCache { }; /** - * Trims the requested {@link TimeRange} against results that already cached and registers the trimmed + * Trims the requested {@link TimeRange} against results that are already cached and registers the trimmed * {@link TimeRange} with the cache. * * @param toTrim @@ -93,7 +93,7 @@ public class TimeRangeResultCache { * if {@code false} the request will be optimized with cached results. * @return {@link TimeRange} to request or {@code null} if no request is to be made since the results are cached. */ - public TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { + TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { Request request = new Request(); if (!forceTimeRange) { // TODO Remove old results? toTrim = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); From f19de0c4855dfdfdd29605df54b8ca44c1c745fa Mon Sep 17 00:00:00 2001 From: Axel Uhl Date: Wed, 24 Jun 2020 14:58:02 +0200 Subject: [PATCH 035/235] bug5259: added various comments, made more things final; see my comments on bug5259 in Bugzilla --- .../src/com/sap/sse/common/TimeRange.java | 4 +- .../async/TimeRangeResultCacheTest.java | 16 +- .../sap/sse/gwt/client/async/AsyncAction.java | 3 +- .../client/async/AsyncActionsExecutor.java | 2 - .../async/TimeRangeActionsExecutor.java | 70 +++++---- .../client/async/TimeRangeAsyncAction.java | 16 +- .../client/async/TimeRangeAsyncCallback.java | 8 +- .../client/async/TimeRangeResultCache.java | 137 +++++++++++++----- 8 files changed, 167 insertions(+), 89 deletions(-) diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java index f03f5fca2aa..fd2ecb6dba1 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java @@ -27,9 +27,7 @@ public interface TimeRange extends Comparable, Serializable { /** * Also returns true if (either or both) the from or to timepoints are equal. - * E.g. 10-100 lies within 10-00, and 10-50 lies within 10-100. - * @param other - * @return + * E.g. 10-100 lies within 5-100, and 10-50 lies within 10-100. */ boolean liesWithin(TimeRange other); diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java index bf5528148c9..b33c83cd207 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java @@ -16,7 +16,7 @@ public class TimeRangeResultCacheTest { public void testSimpleRequest() { TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); TimeRange timeRange = new MilliTimeRange(100, 1000); - assertEquals(timeRange, trimmer.trimAndRegisterRequest(timeRange, false)); + assertEquals(timeRange, trimmer.trimAndRegisterRequest(timeRange, /* force */ false)); List> expectedResult = new ArrayList<>(1); expectedResult.add(new Pair<>(timeRange, null)); assertArrayEquals(expectedResult.toArray(), trimmer.registerAndCollectResult(timeRange, null, null).toArray()); @@ -35,7 +35,7 @@ public class TimeRangeResultCacheTest { trimmer.trimAndRegisterRequest(rightRange, true); trimmer.trimAndRegisterRequest(includedRange, true); trimmer.trimAndRegisterRequest(outsideRange, true); - assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, false))); + assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, /* force */ false))); } @Test @@ -47,11 +47,11 @@ public class TimeRangeResultCacheTest { final TimeRange leftSecondPassRange = new MilliTimeRange(120_000L, 150_000L); final TimeRange rightRange = new MilliTimeRange(190_000L, 300_000L); final TimeRange outsideRange = new MilliTimeRange( 50_000L, 100_000L); - trimmer.trimAndRegisterRequest(leftSecondPassRange, true); - trimmer.trimAndRegisterRequest(leftRange, true); - trimmer.trimAndRegisterRequest(rightRange, true); - trimmer.trimAndRegisterRequest(outsideRange, true); - assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, false))); + trimmer.trimAndRegisterRequest(leftSecondPassRange, /* force */ true); + trimmer.trimAndRegisterRequest(leftRange, /* force */ true); + trimmer.trimAndRegisterRequest(rightRange, /* force */ true); + trimmer.trimAndRegisterRequest(outsideRange, /* force */ true); + assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, /* force */ false))); } @Test @@ -69,7 +69,7 @@ public class TimeRangeResultCacheTest { expectedResult.add(new Pair<>(leftRange, leftInt)); trimmer.registerAndCollectResult(leftRange, leftInt, null); trimmer.registerAndCollectResult(outsideRange, outsideInt, null); - TimeRange trimmedRange = trimmer.trimAndRegisterRequest(requestedRange, false); + TimeRange trimmedRange = trimmer.trimAndRegisterRequest(requestedRange, /* force */ false); assertEquals(expectedRange, trimmedRange); List> result = trimmer.registerAndCollectResult(trimmedRange, requestedInt, null); assertArrayEquals(expectedResult.toArray(), result.toArray()); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncAction.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncAction.java index 4006b0c33df..a8b0af7c25a 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncAction.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncAction.java @@ -9,6 +9,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; * The type of the returned value of the call * @author c5163874 */ +@FunctionalInterface public interface AsyncAction { - public void execute(AsyncCallback callback); + void execute(AsyncCallback callback); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java index c94a238864b..1b0d462a7c7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/AsyncActionsExecutor.java @@ -41,14 +41,12 @@ public class AsyncActionsExecutor { } public void execute() { -// GWT.log("Action name " + action.getClass().getName()); this.action.execute(new MarkedAsyncCallback(this, getCategory())); } @Override public void onSuccess(T result) { try { -// GWT.log("Execution success for action of type " + getType() + ", category "+getCategory()); this.callback.onSuccess(result); } finally { AsyncActionsExecutor.this.callCompleted(this); diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index 166646f231a..a39d82dbdb7 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -36,10 +36,15 @@ import com.sap.sse.common.Util.Pair; * * * @param - * Type returned by the combined call. See {@link TimeRangeAsyncAction}. + * Type returned by the combined call. See {@link TimeRangeAsyncAction}. Holds a compound result for several + * keys, such as for several competitors or several boats. A {@link TimeRangeAsyncCallback} object is + * expected to be able to {@link TimeRangeAsyncCallback#unzipResults(Object) split} such a {@code Result} + * object into the {@code Key}s and {@code SubResult}s, as well as to + * {@link TimeRangeAsyncCallback#joinSubResults(TimeRange, List) join} these sub-results of the individual + * results for trimmed requests into a sub-result for the full time range requested for one {@code Key}. * @param - * Type representing an individual part or channel of a complete {@link Result}, as returned by - * an individual remote procedure call. + * Type representing an individual part or channel of a compound {@link Result}, specific to one {@code Key}, + * as obtained by {@link TimeRangeAsyncCallback#unzipResults(Object) splitting} a compound {@code Result}. * @param * Type used to index {@link SubResult}s. * @see TimeRangeAsyncAction @@ -49,7 +54,8 @@ import com.sap.sse.common.Util.Pair; */ public class TimeRangeActionsExecutor { /** - * Callback called by {@link TimeRangeActionsExecutor#executor} upon receiving an answer from the server. + * Callback called by {@link TimeRangeActionsExecutor#executor} upon receiving an answer from the server + * for a potentially trimmed request */ private final class ExecutorCallback implements AsyncCallback { private final TimeRangeAsyncCallback callback; @@ -66,18 +72,19 @@ public class TimeRangeActionsExecutor { @Override public void onSuccess(Result result) { - Map unzippedResultMap = result != null ? callback.unzipResults(result) - : Collections.emptyMap(); - Map> completedResultMap = new HashMap<>(); + // from the compound result extract the potentially trimmed sub-results per key + final Map unzippedResultMap = result != null ? callback.unzipResults(result) : Collections.emptyMap(); + final Map> completedResultMap = new HashMap<>(); for (Pair request : requestedTimeRanges) { final Key key = request.getA(); - SubResult subResult = unzippedResultMap.get(key); - TimeRangeResultCache cache = getSubResultCache(key); - List> partialResults = cache.registerAndCollectResult( - trimmedTimeRangeMap.get(key), subResult, new AsyncCallback() { + final SubResult trimmedSubResultForKey = unzippedResultMap.get(key); + final TimeRangeResultCache cache = getSubResultCache(key); + // FIXME registerAndCollectResult clears the Request's dependencies; returning bluntly below and hoping for a retry will no longer find those dependencies + final List> partialResults = cache.registerAndCollectResult( + trimmedTimeRangeMap.get(key), trimmedSubResultForKey, new AsyncCallback() { @Override public void onSuccess(Void voidResult) { - ExecutorCallback.this.onSuccess(result); + ExecutorCallback.this.onSuccess(result); // try again after another dependency has delivered its result } @Override @@ -85,12 +92,12 @@ public class TimeRangeActionsExecutor { } }); if (partialResults == null) { - return; // A required request is still in transit - } + return; // A required request is still in transit; the callback defined above will be notified when + } // another dependency delivers its result and will then try the result combination process again TODO extra work! SubResult completedSubResult = callback.joinSubResults(request.getB(), partialResults); completedResultMap.put(key, new Pair<>(request.getB(), completedSubResult)); } - callback.onSuccess(completedResultMap); + callback.onSuccess(completedResultMap); // TODO use the callback to combine the SubResults into a combined Result object } @Override @@ -103,17 +110,22 @@ public class TimeRangeActionsExecutor { /** * {@link AsyncActionsExecutor} that will be used to actually execute remote procedures. */ - protected final AsyncActionsExecutor executor; + private final AsyncActionsExecutor executor; + /** * Action category passed along to the {@link #executor}. * * @see AsyncActionsExecutor#execute(AsyncAction, String, AsyncCallback) */ - protected String actionCategory = MarkedAsyncCallback.CATEGORY_GLOBAL; - protected final Map> cacheMap = new HashMap<>(); //TODO Invalidation + private final String actionCategory; + + private final Map> cacheMap = new HashMap<>(); //TODO Invalidation + /** + * Creates a new executor using a default action category {@link MarkedAsyncCallback#CATEGORY_GLOBAL}. + */ public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor) { - this.executor = Objects.requireNonNull(actionsExecutor); + this(actionsExecutor, MarkedAsyncCallback.CATEGORY_GLOBAL); } public TimeRangeActionsExecutor(AsyncActionsExecutor actionsExecutor, String actionCategory) { @@ -123,7 +135,8 @@ public class TimeRangeActionsExecutor { /** * Executes a {@link TimeRangeAsyncAction} and returns the results to a {@link TimeRangeAsyncCallback}. Calls - * {@link #execute(TimeRangeAsyncAction, AsyncCallback, boolean)} with {@code forceTimeRange} set to {@code false}. + * {@link #execute(TimeRangeAsyncAction, AsyncCallback, boolean)} with {@code forceTimeRange} set to {@code false}, + * thus allowing for cached requests to help trim the new request so as to avoid overlapping redundant requests. * * @param action * {@link TimeRangeAsyncAction} to execute. @@ -144,7 +157,8 @@ public class TimeRangeActionsExecutor { * @param callback * {@link TimeRangeAsyncCallback} to return results to. * @param forceTimeRange - * if {@code false} the request will be optimized. + * if {@code false} the request will be optimized by trimming its time range; otherwise it will be + * executed unchanged * @see #execute(TimeRangeAsyncAction, TimeRangeAsyncCallback) */ public void execute(TimeRangeAsyncAction action, @@ -155,21 +169,17 @@ public class TimeRangeActionsExecutor { if (callback == null) { throw new IllegalArgumentException("callback must not be null!"); } - List> trimmedTimeRanges = new ArrayList<>(action.getTimeRanges().size()); + final List> trimmedTimeRanges = new ArrayList<>(action.getTimeRanges().size()); for (Pair part : action.getTimeRanges()) { - TimeRangeResultCache cache = getSubResultCache(part.getA()); - TimeRange trimmedRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange); + final TimeRangeResultCache cache = getSubResultCache(part.getA()); + final TimeRange trimmedRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange); if (trimmedRange != null) { trimmedTimeRanges.add(new Pair<>(part.getA(), trimmedRange)); } } if (!trimmedTimeRanges.isEmpty()) { - executor.execute(new AsyncAction() { - @Override - public void execute(AsyncCallback callback) { - action.execute(trimmedTimeRanges, callback); - } - }, actionCategory, new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback)); + executor.execute(cb->action.execute(trimmedTimeRanges, cb), + actionCategory, new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback)); } else { new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback).onSuccess(null); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java index c67ba0ba34b..0fe3fc7fa2d 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncAction.java @@ -7,10 +7,18 @@ import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; /** - * An action that will asynchronously execute a remote procedure that is parameterized by time ranges and by a - * {@code Key} (e.g., a competitor, or a boat or similar) and that returns a {@code Result}. Executed by - * {@link TimeRangeActionsExecutor}. The benefit of using this class over making a straight call to the remote procedure - * is that the {@link TimeRangeActionsExecutor} understands, manages and optimized the overlaps in concurrent requests. + * An action that will asynchronously execute a remote procedure that is parameterized by time ranges keyed by a set of + * {@code Key}s (e.g., a competitor, or a boat or similar) and that returns a {@code Result}, most likely again keyed by + * those {@code Key}s somehow. The action specifies by its return value for the {@link #getTimeRanges()} method for + * which time ranges it would like to know the results. When + * {@link TimeRangeActionsExecutor#execute(TimeRangeAsyncAction, TimeRangeAsyncCallback) executed} by a + * {@link TimeRangeActionsExecutor}, that executor can trim the request time ranges based on cached requests, avoiding + * redundant requests for overlapping time ranges. After the executor has trimmed the requests the executor will call + * this action's {@link #execute(Collection, AsyncCallback)} method with the trimmed time ranges. + *

+ * + * The benefit of using this approach over making a straight call to the remote procedure is that the + * {@link TimeRangeActionsExecutor} understands, manages and optimizes the overlaps in concurrent requests. * * @param * Type returned by remote procedure. diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java index 01e1eb8e3e7..efe4f909a61 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java @@ -13,7 +13,8 @@ import com.sap.sse.common.Util.Pair; * * When used with a {@link TimeRangeActionsExecutor} the data flow looks like this: *

    - *
  1. {@link TimeRangeAsyncAction} gets executed and returns server response. + *
  2. {@link TimeRangeAsyncAction} gets executed with a compound request for potentially several keys and returns a + * compound server response of type {@code Result} *
  3. The response gets split up into {@link SubResult}s by {@link Key} by {@link #unzipResults(Object)}. *
  4. By {@link Key} the {@link SubResult}s get cached and get put into {@link #joinSubResults(TimeRange, List)} along * with all other required cached {@link SubResult}s.
  5. @@ -22,9 +23,12 @@ import com.sap.sse.common.Util.Pair; *
* * @param + * The type of the compound result returned by the compound request for multiple keys * @param - * test + * The type of a result for a single key and time range * @param + * The type of key, each of which can have a separate time range in the compound request, and for each one a + * separate {@code SubResult} is expected to be provided in the compound {@code Result} object. * @see TimeRangeAsyncAction * @see TimeRangeActionsExecutor * @author Tim Hessenmüller (D062243) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 4d19bc6ef5e..89205f11ad3 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -16,7 +17,10 @@ import com.sap.sse.common.Util.Pair; /** * Optimizes requests by trimming their wanted {@link TimeRange} against a cache of (fetched and still outstanding) * results. In the context of a {@link TimeRangeActionsExecutor} such a cache is responsible for managing the requests - * for a single {@code Key}, such as a competitor or a boat class. + * for a single {@code Key}, such as a competitor or a boat class.

+ * + * Before making a request for a time range, the executor has to call {@link #trimAndRegisterRequest(TimeRange, boolean)}. + * When {@code false} is used for the {@code forceTimeRange} parameter, this cache will * * @param * type of the results of the remote calls which are to be cached @@ -27,6 +31,17 @@ public class TimeRangeResultCache { private static final int TRIM_MAX_ITERATIONS = 20; private static final int CACHE_SIZE = 32; + /** + * Stores properties of a request that may have been the result of trimming an original request's time ranges. + * If trimming took place (see {@link TimeRangeResultCache#trimAndRegisterRequest(TimeRange, boolean)}), the + * trimmed request will have {@link #getDependencies() dependencies} on other {@link Request}s whose results + * it needs to combine with its own result in order to satisfy the original untrimmed request from which it + * was derived by trimming.

+ * + * This request keeps track of whether it {@link #hasResult() has received a result}. It furthermore manages + * a set of {@link AsyncCallback} callbacks that will be triggered exactly once when a result has been + * {@link #setResult(Pair) received} for this request. + */ protected class Request { private final Set dependsOnSet = new HashSet<>(); private boolean hasResult = false; @@ -41,10 +56,20 @@ public class TimeRangeResultCache { this.result = result; } + /** + * Obtains all other requests (excluding {@code this} request) of which all or parts of their results + * are required to combine them with the results of this trimmed request into a result that reflects the + * response to the original request from which this request was derived by trimming. + */ public Set getDependencies() { return dependsOnSet; } + /** + * Records that this request depends on the results of {@code request} because this request was trimmed based + * on the assumption that {@code request}'s results are or will become available and cover a part of what this + * request was originally expected to deliver. + */ public void addDependency(Request request) { dependsOnSet.add(request); } @@ -57,6 +82,12 @@ public class TimeRangeResultCache { return result; } + /** + * Sets the result for this request's total time range; {@link #hasResult()} will return {@code true} + * after this method returns, and {@link #getResult()} will return {@code result} from then on. + * All callbacks that were registered using {@link #registerOnResultCallback(AsyncCallback)} up to this time + * will be {@link AsyncCallback#onSuccess(Object) notified}. + */ public void setResult(Pair result) { this.hasResult = true; this.result = result; @@ -68,78 +99,101 @@ public class TimeRangeResultCache { return hasResult; } + /** + * The callback is added to the set of callbacks notified when this request {@link #setResult(Pair) receives} + * its result. Registering a callback after the result has already been received has no effect. The callback + * set will be cleared once the result has been received. + */ public void registerOnResultCallback(AsyncCallback callback) { waitingOnResultList.add(callback); } } + /** + * The size-limited {@link Request} cache. The keys are the time ranges that may have resulted from trimming and + * represent those time ranges for which requests are effectively made. The corresponding value {@link Request} + * object captures {@link Request#getDependencies() requests on which the request depends} after having been + * trimmed. + */ private final Map requestCache = new LinkedHashMap( /* initialCapacity */ CACHE_SIZE, /* loadFactor */ .75f, /* accessOrder */ true) { - private static final long serialVersionUID = 1L; // TODO Generated id is 1? + private static final long serialVersionUID = -5421470532158005911L; @Override protected boolean removeEldestEntry(Entry eldest) { - return size() >= CACHE_SIZE; // TODO + return size() >= CACHE_SIZE; // FIXME don't remove an entry on whose request other requests in the cache depend } }; /** - * Trims the requested {@link TimeRange} against results that are already cached and registers the trimmed - * {@link TimeRange} with the cache. + * Trims the requested {@link TimeRange} against results that are already cached or requested and registers the + * trimmed {@link TimeRange} with the cache as a request that will be made and that therefore shall already be used + * to trim subsequent requests. * * @param toTrim * {@link TimeRange} to request. * @param forceTimeRange * if {@code false} the request will be optimized with cached results. - * @return {@link TimeRange} to request or {@code null} if no request is to be made since the results are cached. + * @return {@link TimeRange} to effectively request (potentially trimmed from {@code toTrim}) or {@code null} if no + * request is to be made since the results are cached. */ TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { - Request request = new Request(); + final Request request = new Request(); + final TimeRange effectivePotentiallyTrimmedRequest; if (!forceTimeRange) { // TODO Remove old results? - toTrim = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); + effectivePotentiallyTrimmedRequest = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); + } else { + effectivePotentiallyTrimmedRequest = toTrim; } - // Signal result already present - if (toTrim == null) { - return null; + // null signals that the result is already present + if (effectivePotentiallyTrimmedRequest != null) { + // cache the request which is expected to not yet have a result + requestCache.put(effectivePotentiallyTrimmedRequest, request); } - requestCache.put(toTrim, request); - return toTrim; + return effectivePotentiallyTrimmedRequest; } /** * Registers that a request has returned with a result and collects all other needed cached results that are needed * to construct a complete, time-contiguous {@link Result}. * - * @param timeRange - * {@link TimeRange} that this request was trimmed to. + * @param effectiveRequestTimeRange + * {@link TimeRange} that the request that now returned a result was trimmed to. * @param result - * {@link Result} of this request to cache. + * {@link Result} of the trimmed request; this result will be cached, keyed by its trimmed {@code timeRange} * @param callbackIfResultsAreMissing * {@link AsyncCallback}{@code } to call {@link AsyncCallback#onSuccess(Object)} on once a request - * this one depends on and which was still in transit returns. + * this one depends on and which was still in transit returns. Note that this then does not imply that all requests + * needed to fulfill the original request have delivered their result yet; the same may happen again for another + * dependency when trying again later. * @return {@link List} of {@link TimeRange}, {@link Result} {@link Pair}s that contain all data needed to construct - * a time-continuous {@link Result} object. + * a time-continuous {@link Result} object, or {@code null} if not all results on which the request for + * {@code effectiveRequestTimeRange} depended have been received yet; in this case the {@code callbackIfResultsAreMissing} + * will later receive a callback if one (but maybe not all) of those results has been received. */ - public List> registerAndCollectResult(TimeRange timeRange, Result result, + public List> registerAndCollectResult(TimeRange effectiveRequestTimeRange, Result result, AsyncCallback callbackIfResultsAreMissing) { - final Pair requestResult = new Pair<>(timeRange, result); - Request request = requestCache.get(timeRange); + final Pair requestResult = new Pair<>(effectiveRequestTimeRange, result); + Request request = requestCache.get(effectiveRequestTimeRange); if (request != null) { - request.setResult(requestResult); + request.setResult(requestResult); // notifies all callbacks that were waiting for this result } else { + // FIXME the request that has returned now must have been evicted from the size-limited cache; but what about any dependencies? request = new Request(requestResult); - requestCache.put(timeRange, request); + requestCache.put(effectiveRequestTimeRange, request); } final List> results = new ArrayList<>(request.getDependencies().size() + 1); results.add(requestResult); - for (final Request dep : request.getDependencies()) { + for (final Request dep : request.getDependencies()) { // FIXME see above; no dependencies for new request that replaces an evicted one if (!dep.hasResult()) { dep.registerOnResultCallback(callbackIfResultsAreMissing); - return null; + return null; // TODO it's sufficient to register on *one* dependency, although this may lead to a few premature re-tries + } else { + results.add(dep.getResult()); } - results.add(dep.getResult()); } - request.clearDependencies(); + // FIXME if ExecutorCallback resolves the same compound result multiple times, clearing dependencies here would no longer let us find the dependencies required here + request.clearDependencies(); // FIXME moot if the request was just created above because the original one was evicted from the cache earlier return results; } @@ -147,36 +201,41 @@ public class TimeRangeResultCache { * Removes the failed request from cache. * * @param timeRange - * {@link TimeRange} of failed request. + * effective {@link TimeRange} of failed request (result of potential trimming) */ public void registerFailure(TimeRange timeRange) { Request request = requestCache.remove(timeRange); // TODO Requests dependent on this one? + // FIXME shouldn't this propagate as failure also to the callbacks of dependent requests? Add failure tests for trimmed requests! if (request != null) { request.setResult(null); } } /** - * Trims a potential request with cached results. For simplicity {@code toTrim} will currently not be split up into - * multiple TimeRanges if a part in the middle is cached. + * Trims a request based on cached results, returning a potentially trimmed time range that reflects which part(s) + * is/are missing from the cache. For simplicity {@code toTrim} will currently not be split up into multiple + * TimeRanges if a part in the middle is cached.

+ * + * * * @param toTrim * {@link TimeRange} to trim. * @param request - * {@link Request} to attach dependencies to. + * {@link Request} to attach dependencies to that reflect of which other requests the results are + * required in order to fulfill the request for the complete {@code toTrim} time range * @param rangesToTrimWith * {@link TimeRange}s that {@code toTrim} will be trimmed against. - * @return {@link TimeRange} which to request from the server because it is not covered by {@code rangesToTrimWith} - * or {@code null} if no request to the server is to be made. + * @return {@link TimeRange} which to request because it is not covered by {@code rangesToTrimWith} or {@code null} + * if no request to the server is to be made. */ private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, Iterable> rangesToTrimWith) { - List> rangesToTrimWithList = new ArrayList<>(); - rangesToTrimWith.forEach(rangesToTrimWithList::add); + List> rangesToTrimWithAsList = new LinkedList<>(); + rangesToTrimWith.forEach(rangesToTrimWithAsList::add); for (int i = 0; i < TRIM_MAX_ITERATIONS; i++) { boolean rangeWasTrimmedThisIteration = false; - Iterator> iter = rangesToTrimWithList.iterator(); + final Iterator> iter = rangesToTrimWithAsList.iterator(); while (iter.hasNext()) { final Map.Entry element = iter.next(); final TimeRange trimWith = element.getKey(); @@ -187,9 +246,8 @@ public class TimeRangeResultCache { if (request != null) { request.addDependency(element.getValue()); } - return null; - } - if (!toTrim.includes(trimWith) + return null; // meaning that no additional effective requests are required; the cache has or will have it all + } else if (!toTrim.includes(trimWith) || (toTrim.from().equals(trimWith.from()) || toTrim.to().equals(trimWith.to()))) { // toTrim and trimWith overlap and trimWith does not lie within toTrim // OR special case where trimWith lies within toTrim but touches one of the ends @@ -201,6 +259,7 @@ public class TimeRangeResultCache { rangeWasTrimmedThisIteration = true; } } else { + // no intersection between request and cache entry; remove the cache entry from the list of requests to consider iter.remove(); } } From 76ca4af71d11bbc7ba52232a113e4ae6b76c87a4 Mon Sep 17 00:00:00 2001 From: Udo Wessels Date: Mon, 6 Jul 2020 21:56:57 +0200 Subject: [PATCH 036/235] Add new methods to wait for a web element until expected value is set. Before it was not guaranteed that the web element was rendered and initialized completely and therefore the value accessor return a wrong, unexpected value. If expected value is not reached a timeout will occur. --- .../selenium/pages/gwt/CheckBoxPO.java | 20 +++++++++++++++ .../sailing/selenium/pages/gwt/ListBoxPO.java | 20 +++++++++++++++ .../selenium/pages/gwt/TextBoxBasePO.java | 20 +++++++++++++++ .../pages/raceboard/MapSettingsPO.java | 25 ++++++++++--------- .../selenium/test/raceboard/SettingsTest.java | 16 ++++++------ 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java index b2fd6892183..08248a105fb 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java @@ -1,5 +1,7 @@ package com.sap.sailing.selenium.pages.gwt; +import java.util.function.BooleanSupplier; + import org.openqa.selenium.By.ByTagName; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; @@ -39,6 +41,24 @@ public class CheckBoxPO extends AbstractInputPO { super(driver, element); } + /** + * Wait until checkbox status equals to expected one. + * + * @param expected expected status + */ + public void waitForElementUntil(boolean expected) { + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return isSelected() == expected; + } catch (Exception e) { + return false; + } + } + }); + } + /** * Get the text of the check boxes label. * diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java index c51e0180f85..1587cd9b94f 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java @@ -1,9 +1,12 @@ package com.sap.sailing.selenium.pages.gwt; +import java.util.function.BooleanSupplier; + import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.Select; +import com.google.common.base.Objects; import com.sap.sailing.selenium.pages.PageObject; /** @@ -35,6 +38,23 @@ public class ListBoxPO extends AbstractInputPO { selectBox = new Select(element); } + /** + * Wait until selected value is equals to expected value. + * @param expected expected value. + */ + public void waitForElementUntil(String expected) { + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return Objects.equal(getSelectedOptionValue(), expected); + } catch (Exception e) { + return false; + } + } + }); + } + public String getSelectedOptionLabel() { return this.selectBox.getFirstSelectedOption().getText(); } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java index 5a27fdd479d..a5210b2ae66 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java @@ -1,8 +1,11 @@ package com.sap.sailing.selenium.pages.gwt; +import java.util.function.BooleanSupplier; + import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import com.google.common.base.Objects; import com.sap.sailing.selenium.pages.PageObject; /** @@ -17,6 +20,23 @@ public abstract class TextBoxBasePO extends AbstractInputPO { super(driver, element); } + /** + * Wait until text equals to expected one. + * @param expected expected text. + */ + public void waitForElementUntil(String expected) { + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return Objects.equal(getText(), expected); + } catch (Exception e) { + return false; + } + } + }); + } + /** * Appends the given text to the underlying {@link WebElement}, while keeping possibly existing text. * diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/MapSettingsPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/MapSettingsPO.java index 1389f4fb8fc..adee9da5a68 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/MapSettingsPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/raceboard/MapSettingsPO.java @@ -5,37 +5,38 @@ import java.util.List; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; -import com.sap.sailing.selenium.core.BySeleniumId; -import com.sap.sailing.selenium.core.FindBy; import com.sap.sailing.selenium.pages.common.DataEntryDialogPO; import com.sap.sailing.selenium.pages.gwt.CheckBoxPO; public class MapSettingsPO extends DataEntryDialogPO { - @FindBy(how = BySeleniumId.class, using = "windUpCheckBox") - private WebElement windUpCheckBox; - - @FindBy(how = BySeleniumId.class, using = "showOnlySelectedCompetitorsCheckBox") - private WebElement showOnlySelectedCompetitorsCheckBox; - public MapSettingsPO(WebDriver driver, WebElement element) { super(driver, element); } + + public void waitForWindUpUntil(boolean expected) { + WebElement element = findElementBySeleniumId("windUpCheckBox"); + new CheckBoxPO(driver, element).waitForElementUntil(expected); + } public void setWindUp(boolean selected) { - new CheckBoxPO(driver, windUpCheckBox).setSelected(selected); + WebElement element = findElementBySeleniumId("windUpCheckBox"); + new CheckBoxPO(driver, element).setSelected(selected); } public boolean isWindUp() { - return new CheckBoxPO(driver, windUpCheckBox).isSelected(); + WebElement element = findElementBySeleniumId("windUpCheckBox"); + return new CheckBoxPO(driver, element).isSelected(); } public void setShowOnlySelectedCompetitors(boolean selected) { - new CheckBoxPO(driver, showOnlySelectedCompetitorsCheckBox).setSelected(selected); + WebElement element = findElementBySeleniumId("showOnlySelectedCompetitorsCheckBox"); + new CheckBoxPO(driver, element).setSelected(selected); } public boolean isShowOnlySelectedCompetitors() { - return new CheckBoxPO(driver, showOnlySelectedCompetitorsCheckBox).isSelected(); + WebElement element = findElementBySeleniumId("showOnlySelectedCompetitorsCheckBox"); + return new CheckBoxPO(driver, element).isSelected(); } public void setShowWindStreamletOverlay(boolean selected) { diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java index 64d2c054e84..f54897bcee4 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/test/raceboard/SettingsTest.java @@ -111,7 +111,7 @@ public class SettingsTest extends AbstractSeleniumTest { leaderboardSettingsDialog.pressCancel(); MapSettingsPO mapSettings = raceboard.openMapSettings(); // Verify initial mode settings - Assert.assertFalse(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(false); Assert.assertFalse(mapSettings.isShowOnlySelectedCompetitors()); mapSettings.setTransparentHoverlines(true); mapSettings.setWindUp(false); @@ -148,7 +148,7 @@ public class SettingsTest extends AbstractSeleniumTest { leaderboardSettingsDialog.pressOk(false, false); mapSettings = raceboard.openMapSettings(); // verify default mode settings override custom user settings - Assert.assertTrue(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(true); // verify default mode settings override system defaults Assert.assertTrue(mapSettings.isShowOnlySelectedCompetitors()); // Verify custom user settings are independent for modes @@ -168,7 +168,7 @@ public class SettingsTest extends AbstractSeleniumTest { leaderboardSettingsDialog.pressCancel(); mapSettings = raceboard.openMapSettings(); // Verify that mode settings are overridden by document settings - Assert.assertFalse(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(false); // Verify custom user settings are independent for modes Assert.assertFalse(mapSettings.isTransparentHoverlines()); // verify that custom document settings override mode settings of other modes @@ -191,7 +191,7 @@ public class SettingsTest extends AbstractSeleniumTest { Assert.assertArrayEquals(detailsToSelect, selectedDetails); leaderboardSettingsDialog.pressCancel(); mapSettings = raceboard.openMapSettings(); - Assert.assertTrue(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(true); } /** @@ -259,7 +259,7 @@ public class SettingsTest extends AbstractSeleniumTest { MapSettingsPO mapSettings = raceboard.openMapSettings(); // Verify initial mode settings - Assert.assertFalse(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(false); Assert.assertFalse(mapSettings.isShowOnlySelectedCompetitors()); mapSettings.setWindUp(true); mapSettings.setShowOnlySelectedCompetitors(true); @@ -270,7 +270,7 @@ public class SettingsTest extends AbstractSeleniumTest { mapSettings = raceboard.openMapSettings(); // verify default settings work - Assert.assertTrue(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(true); Assert.assertTrue(mapSettings.isShowOnlySelectedCompetitors()); } @@ -292,7 +292,7 @@ public class SettingsTest extends AbstractSeleniumTest { MapSettingsPO mapSettings = raceboard.openMapSettings(); // The following options are false in the system default but activated by the WINNING_LANES mode - Assert.assertTrue(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(true); Assert.assertTrue(mapSettings.isShowOnlySelectedCompetitors()); mapSettings.setWindUp(false); mapSettings.setShowOnlySelectedCompetitors(false); @@ -303,7 +303,7 @@ public class SettingsTest extends AbstractSeleniumTest { mapSettings = raceboard.openMapSettings(); // verify mode settigns have been overwritten - Assert.assertFalse(mapSettings.isWindUp()); + mapSettings.waitForWindUpUntil(false); Assert.assertFalse(mapSettings.isShowOnlySelectedCompetitors()); } From b56cf7ab0bd847245532741b3ac5c58b1dbe43ed Mon Sep 17 00:00:00 2001 From: Alexandr Timoshenko Date: Thu, 9 Jul 2020 16:55:27 +0200 Subject: [PATCH 037/235] correctly stop services foreground services --- .../sending/MessageSendingService.java | 1 - .../app/services/TrackingService.java | 15 ----- .../app/data/InMemoryDataStore.java | 6 +- .../app/services/RaceStateService.java | 64 ++++++++++--------- .../SendingServiceAwareActivity.java | 14 ++-- 5 files changed, 44 insertions(+), 56 deletions(-) diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java index 3b6b88c907d..5b3ad9ca225 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/shared/services/sending/MessageSendingService.java @@ -243,7 +243,6 @@ public class MessageSendingService extends Service implements MessageSendingList CharSequence name = getString(R.string.service_info); NotificationHelper.createNotificationChannel(this, NotificationHelper.getNotificationChannelId(), name); - startForeground(NotificationHelper.getNotificationId(), NotificationHelper.getNotification(this, NotificationHelper.getNotificationChannelId())); ExLog.i(this, TAG, "Sending Service on Create."); } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java index a66bf9e200b..6b0c03a6e3a 100755 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java @@ -161,7 +161,6 @@ public class TrackingService extends Service implements LocationListener { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, UPDATE_INTERVAL_IN_MILLIS_DEFAULT, minLocationUpdateDistanceInMeters, this); ExLog.i(this, TAG, "Started Tracking"); - showNotification(); prefs.setTrackerIsTracking(true); prefs.setTrackerIsTrackingCheckinDigest(checkinDigest); @@ -395,20 +394,6 @@ public class TrackingService extends Service implements LocationListener { // provider (GPS) (re)enabled by the user while tracking } - private void showNotification() { - // Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel - CharSequence name = getText(R.string.service_info); - NotificationHelper.createNotificationChannel(this, NotificationHelper.getNotificationChannelId(), name); - - Intent intent = new Intent(this, TrackingActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); - - Notification notification = NotificationHelper.getNotification(this, NotificationHelper.getNotificationChannelId(), getText(R.string.app_name), - getString(R.string.tracking_notification_text, event.name), pendingIntent); - startForeground(NotificationHelper.getNotificationId(), notification); - } - public void registerGPSQualityListener(GPSQualityListener listener) { gpsQualityListener = listener; } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java index f3c9fb300b8..47c7ac6bae9 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/data/InMemoryDataStore.java @@ -55,8 +55,10 @@ public enum InMemoryDataStore implements DataStore { if (mContext == null) { mContext = context.getApplicationContext(); } - Intent intent = new Intent(mContext, RaceStateService.class); - mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + if (!mBound) { + Intent intent = new Intent(mContext, RaceStateService.class); + mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } } @Override diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateService.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateService.java index 8778ec1c764..833d4dc7cff 100755 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateService.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/services/RaceStateService.java @@ -83,8 +83,6 @@ public class RaceStateService extends Service { super.onCreate(); - Notification notification = setupNotification(null); - startForeground(NotificationHelper.getNotificationId(), notification); ExLog.i(this, TAG, "Started."); } @@ -160,6 +158,7 @@ public class RaceStateService extends Service { managedIntents.clear(); ExLog.i(this, TAG, "All races unregistered."); + stopForeground(true); } public void unregisterRace(ManagedRace race) { @@ -178,7 +177,7 @@ public class RaceStateService extends Service { } managedIntents.remove(race.getId()); } else { - ExLog.w(this, TAG, "Couldn't find any managed intents for race "+race.getId()); + ExLog.w(this, TAG, "Couldn't find any managed intents for race " + race.getId()); } ExLog.i(this, TAG, "Race " + race.getId() + " unregistered"); updateNotification(); @@ -203,33 +202,33 @@ public class RaceStateService extends Service { if (action != null) { switch (action) { - case AppConstants.INTENT_ACTION_CLEAR_RACES: - handleClearRaces(); - break; - - case AppConstants.INTENT_ACTION_CLEANUP_RACES: - handleCleanupRaces(); - break; - - default: - String id = intent.getStringExtra(AppConstants.INTENT_EXTRA_RACE_ID); - ManagedRace race = dataManager.getDataStore().getRace(id); - if (race == null) { - ExLog.w(this, TAG, "No race for id " + id); - return; - } - - switch (action) { - case AppConstants.INTENT_ACTION_ALARM_ACTION: - long timePoint = intent.getLongExtra(AppConstants.INTENT_EXTRA_TIMEPOINT_MILLIS, 0); - String eventName = intent.getStringExtra(AppConstants.INTENT_EXTRA_EVENTNAME); - RaceStateEvent event = new RaceStateEventImpl(new MillisecondsTimePoint(timePoint), - RaceStateEvents.valueOf(eventName)); - ExLog.i(this, TAG, String.format("Processing %s", event.toString())); - race.getState().processStateEvent(event); - clearAlarmByName(race, event.getEventName()); + case AppConstants.INTENT_ACTION_CLEAR_RACES: + handleClearRaces(); break; - } + + case AppConstants.INTENT_ACTION_CLEANUP_RACES: + handleCleanupRaces(); + break; + + default: + String id = intent.getStringExtra(AppConstants.INTENT_EXTRA_RACE_ID); + ManagedRace race = dataManager.getDataStore().getRace(id); + if (race == null) { + ExLog.w(this, TAG, "No race for id " + id); + return; + } + + switch (action) { + case AppConstants.INTENT_ACTION_ALARM_ACTION: + long timePoint = intent.getLongExtra(AppConstants.INTENT_EXTRA_TIMEPOINT_MILLIS, 0); + String eventName = intent.getStringExtra(AppConstants.INTENT_EXTRA_EVENTNAME); + RaceStateEvent event = new RaceStateEventImpl(new MillisecondsTimePoint(timePoint), + RaceStateEvents.valueOf(eventName)); + ExLog.i(this, TAG, String.format("Processing %s", event.toString())); + race.getState().processStateEvent(event); + clearAlarmByName(race, event.getEventName()); + break; + } } } } @@ -288,7 +287,10 @@ public class RaceStateService extends Service { int numRaces = managedIntents.keySet().size(); String content = getString(R.string.service_text_num_races, numRaces); Notification notification = setupNotification(content); - startForeground(NotificationHelper.getNotificationId(), notification); + if (numRaces > 0) { + ExLog.i(this, TAG, "Starting foreground"); + startForeground(NotificationHelper.getNotificationId(), notification); + } } public void registerRace(ManagedRace race) { @@ -338,7 +340,7 @@ public class RaceStateService extends Service { } @TargetApi(Build.VERSION_CODES.KITKAT) - /* package */ void setAlarm(ManagedRace race, RaceStateEvent event) { + /* package */ void setAlarm(ManagedRace race, RaceStateEvent event) { PendingIntent intent = createAlarmPendingIntent(race, event); managedIntents.get(race.getId()).add(Pair.create(intent, event.getEventName())); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SendingServiceAwareActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SendingServiceAwareActivity.java index b2f1038a791..6754d6e166d 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SendingServiceAwareActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/SendingServiceAwareActivity.java @@ -59,6 +59,7 @@ public abstract class SendingServiceAwareActivity extends ResilientActivity SendingServiceAwareActivity activity = mActivity.get(); if (activity != null) { activity.boundSendingService = false; + activity.sendingServiceConnection = null; } } @@ -89,14 +90,10 @@ public abstract class SendingServiceAwareActivity extends ResilientActivity private static String TAG = SendingServiceAwareActivity.class.getName(); - public SendingServiceAwareActivity() { - sendingServiceConnection = new MessageSendingServiceConnection(this); - } - @Override public void onStart() { super.onStart(); - + sendingServiceConnection = new MessageSendingServiceConnection(this); Intent intent = new Intent(this, MessageSendingService.class); ExLog.i(this, TAG, "bindSendingService"); bindService(intent, sendingServiceConnection, Context.BIND_AUTO_CREATE); @@ -107,8 +104,11 @@ public abstract class SendingServiceAwareActivity extends ResilientActivity super.onStop(); ExLog.i(this, TAG, "unbindSendingService"); - unbindService(sendingServiceConnection); - boundSendingService = false; + if (sendingServiceConnection!=null) { + unbindService(sendingServiceConnection); + boundSendingService = false; + sendingServiceConnection = null; + } } protected void updateSendingServiceInformation() { From 0909c8f53d9ec38a3dc41e2c993f728e133a3ba7 Mon Sep 17 00:00:00 2001 From: Udo Wessels Date: Fri, 10 Jul 2020 15:42:38 +0200 Subject: [PATCH 038/235] Added visibility check to CheckBoxPO and to waitUntil... methods of ListBoxPO and TextBoxBasePO --- .../sailing/selenium/pages/gwt/CheckBoxPO.java | 18 +++++++++++++++++- .../sailing/selenium/pages/gwt/ListBoxPO.java | 2 +- .../selenium/pages/gwt/TextBoxBasePO.java | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java index 08248a105fb..e5fb1c335d6 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/CheckBoxPO.java @@ -51,7 +51,7 @@ public class CheckBoxPO extends AbstractInputPO { @Override public boolean getAsBoolean() { try { - return isSelected() == expected; + return getWebElement().isDisplayed() && isSelected() == expected; } catch (Exception e) { return false; } @@ -76,9 +76,25 @@ public class CheckBoxPO extends AbstractInputPO { * @see WebElement#isSelected() */ public boolean isSelected() { + checkVisibility(); return this.input.isSelected(); } + public void checkVisibility() { + if (!getWebElement().isDisplayed()) { + waitUntil(new BooleanSupplier() { + @Override + public boolean getAsBoolean() { + try { + return getWebElement().isDisplayed(); + } catch (Exception e) { + return false; + } + } + }); + } + } + /** * Changes the {@link WebElement}s selection by clicking it, if desired and current state don't match. * diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java index 1587cd9b94f..dab859050ec 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/ListBoxPO.java @@ -47,7 +47,7 @@ public class ListBoxPO extends AbstractInputPO { @Override public boolean getAsBoolean() { try { - return Objects.equal(getSelectedOptionValue(), expected); + return getWebElement().isDisplayed() && Objects.equal(getSelectedOptionValue(), expected); } catch (Exception e) { return false; } diff --git a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java index a5210b2ae66..607983c9afe 100644 --- a/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java +++ b/java/com.sap.sailing.selenium.test/src/com/sap/sailing/selenium/pages/gwt/TextBoxBasePO.java @@ -29,7 +29,7 @@ public abstract class TextBoxBasePO extends AbstractInputPO { @Override public boolean getAsBoolean() { try { - return Objects.equal(getText(), expected); + return getWebElement().isDisplayed() && Objects.equal(getText(), expected); } catch (Exception e) { return false; } From 6cc36a98701d2986a4582318fa8f16b88a4b216e Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Mon, 13 Jul 2020 14:09:27 +0200 Subject: [PATCH 039/235] improve refreshing of visibility, remove changing width of search box bug5232 --- .../home/desktop/partials/header/Header.gss | 8 ++-- .../home/desktop/partials/header/Header.java | 45 ++++++++++++------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss index 131b771f7c8..3df20f2e355 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss @@ -33,7 +33,7 @@ color: #f6f9fc; white-space: nowrap; } -@media (min-width: 64.2em) { +@media (min-width: 61em) { .siteheader_logo_title { display: inline; } @@ -101,7 +101,7 @@ } .search_textfield { display: none; - width: 8em; + width: 13.333333333333334em; padding: 0.357142857142857em 2.857142857142857em 0.357142857142857em 0.357142857142857em; border: 1px solid #666; font-size: 0.933333333333333em; @@ -116,9 +116,7 @@ display: inline-block; } } -.search_textfield:focus { - width: 13.333333333333334em; -} + .search_button { width: 2.666666666666667em !important; height: 1em !important; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java index 869f955c05b..ec61f70390f 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java @@ -29,6 +29,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; @@ -105,8 +106,6 @@ public class Header extends Composite implements HeaderConstants { this.dropdownHandler = dropdownHandler; this.headerNavigationIcon = headerNavigationIcon; this.menuToDropDownItemMap = menuToDropDownItemMap; - // initialize visibility for drop down - refreshVisibility(); } @Override @@ -134,7 +133,17 @@ public class Header extends Composite implements HeaderConstants { this.dropdownHandler.setVisible(false); } } - + + public void refreshVisibility(int delayMillis) { + new Timer() { + + @Override + public void run() { + refreshVisibility(); + } + }.schedule(delayMillis); + } + } private static HeaderUiBinder uiBinder = GWT.create(HeaderUiBinder.class); @@ -145,6 +154,18 @@ public class Header extends Composite implements HeaderConstants { this.navigator = navigator; HeaderResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); + + Map menuToDropDownItemMap = new HashMap<>(); + menuToDropDownItemMap.put(startPageLink, startPageLinkMenu); + menuToDropDownItemMap.put(eventsPageLink, eventsPageLinkMenu); + menuToDropDownItemMap.put(solutionsPageLink, solutionsPageLinkMenu); + menuToDropDownItemMap.put(adminConsolePageLink, adminConsolePageLinkMenu); + menuToDropDownItemMap.put(dataMiningPageLink,dataMiningPageLinkMenu); + headerNavigationDropDownMenuContainer.getStyle().setDisplay(Display.NONE); + final DropdownHandler dropdownHandler = new DropdownHandler(headerNavigationIcon, headerNavigationDropDownMenuContainer); + menuItemVisibilityHandler = new MenuItemVisibilityHandler(menuToDropDownItemMap, dropdownHandler, headerNavigationIcon); + Window.addResizeHandler(menuItemVisibilityHandler); + links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink, adminConsolePageLink, dataMiningPageLink }); homeNavigation = navigator.getHomeNavigation(); eventsNavigation = navigator.getEventsNavigation(); @@ -188,6 +209,10 @@ public class Header extends Composite implements HeaderConstants { menuItemVisibilityHandler.refreshVisibility(); }); searchText.getElement().setAttribute("placeholder", StringMessages.INSTANCE.headerSearchPlaceholder()); + searchText.addFocusHandler((focusEvent) -> menuItemVisibilityHandler.refreshVisibility(370)); + searchText.addBlurHandler(blurEvent -> { + menuItemVisibilityHandler.refreshVisibility(370); + }); searchText.addKeyPressHandler(new KeyPressHandler() { @Override public void onKeyPress(KeyPressEvent event) { @@ -218,18 +243,8 @@ public class Header extends Composite implements HeaderConstants { eventsPageLinkMenu.addClickHandler(this::goToEvents); solutionsPageLinkMenu.addClickHandler(this::goToSolutions); //remaining entries please see at registration for AuthenticationContextEvent - - Map menuToDropDownItemMap = new HashMap<>(); - menuToDropDownItemMap.put(startPageLink, startPageLinkMenu); - menuToDropDownItemMap.put(eventsPageLink, eventsPageLinkMenu); - menuToDropDownItemMap.put(solutionsPageLink, solutionsPageLinkMenu); - menuToDropDownItemMap.put(adminConsolePageLink, adminConsolePageLinkMenu); - menuToDropDownItemMap.put(dataMiningPageLink,dataMiningPageLinkMenu); - - - headerNavigationDropDownMenuContainer.getStyle().setDisplay(Display.NONE); - menuItemVisibilityHandler = new MenuItemVisibilityHandler(menuToDropDownItemMap, new DropdownHandler(headerNavigationIcon, headerNavigationDropDownMenuContainer), headerNavigationIcon); - Window.addResizeHandler(menuItemVisibilityHandler); + // refresh after UI has been put into place + menuItemVisibilityHandler.refreshVisibility(); } @UiHandler("startPageLink") From 37d6de82fc4c4a4fbdce4ac297a7a6eb8cdac933 Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Mon, 13 Jul 2020 14:10:06 +0200 Subject: [PATCH 040/235] aws eclipse .project file --- java/com.amazon.aws.aws-java-api/.project | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 java/com.amazon.aws.aws-java-api/.project diff --git a/java/com.amazon.aws.aws-java-api/.project b/java/com.amazon.aws.aws-java-api/.project new file mode 100644 index 00000000000..b2ef8f42555 --- /dev/null +++ b/java/com.amazon.aws.aws-java-api/.project @@ -0,0 +1,34 @@ + + + com.amazon.aws.aws-java-api + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + From ec6a1f7af364d43e9b4986a0659ce9ae41e81339 Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Mon, 13 Jul 2020 16:07:32 +0200 Subject: [PATCH 041/235] cleanup bug 5232 --- .../sap/sailing/gwt/home/desktop/partials/header/Header.gss | 3 --- 1 file changed, 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss index 3df20f2e355..8671edb830a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.gss @@ -107,9 +107,6 @@ font-size: 0.933333333333333em; color: #f6f9fc; background: #333; - -webkit-transition: width 0.33s ease-out; - -moz-transition: width 0.33s ease-out; - transition: width 0.33s ease-out; } @media (min-width: 61em) { .search_textfield { From 12c4f58b70261dcdaa0eed1b6b898faf96dc87d2 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Thu, 16 Jul 2020 00:57:12 +0200 Subject: [PATCH 042/235] code styling --- .../sailing/gwt/home/desktop/partials/event/EventTeaser.java | 3 +-- .../desktop/places/event/regatta/EventRegattaActivity.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/event/EventTeaser.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/event/EventTeaser.java index f3d8dd87877..09fe11f13b3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/event/EventTeaser.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/event/EventTeaser.java @@ -57,8 +57,7 @@ public class EventTeaser extends Composite { placeNavigation.goToPlace(); } } - });; - + }); updateUI(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java index 2509c1bdd63..11c73538e6f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java @@ -57,7 +57,6 @@ public class EventRegattaActivity extends AbstractEventActivity Date: Thu, 16 Jul 2020 00:58:23 +0200 Subject: [PATCH 043/235] bug5312: Introducing open graph meta tags for desktop event header --- .../partials/eventheader/EventHeader.java | 17 +++++- .../partials/seriesheader/SeriesHeader.java | 16 +++++ .../partials/sharing/SharingButtons.java | 59 ++++++++++++++++++- .../sharing/SharingMetadataProvider.java | 7 ++- .../partials/eventheader/EventHeader.java | 2 +- .../partials/seriesheader/SeriesHeader.java | 2 +- .../gwt/home/shared/utils/LogoUtil.java | 8 ++- 7 files changed, 103 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java index 12f94026c00..7ec0671fc6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java @@ -90,11 +90,26 @@ public class EventHeader extends Composite { String dateString = EventDatesFormatterUtil.formatDateRangeWithYear(event.getStartDate(), event.getEndDate()); return StringMessages.INSTANCE.eventSharingLongText(event.getDisplayName(), event.getLocationOrVenue(), dateString, url); } + + @Override + public String getImageUrl() { + return LogoUtil.resolveLogoURL(event); + } + + @Override + public String getTitle() { + return event.getDisplayName(); + } + + @Override + public String getDescription() { + return event.getDescription() != null ? event.getDescription() : getShortText(); + } }); } private void initFields() { - LogoUtil.setEventLogo(eventLogo, event); + LogoUtil.setLogo(eventLogo, event); if (presenter.showRegattaMetadata()) { presenter.getCurrentEventNavigation().configureAnchorElement(eventLogoAnchorUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java index e9035cd9f4c..85b9eee6066 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java @@ -22,6 +22,7 @@ import com.sap.sailing.gwt.home.desktop.places.fakeseries.SeriesView.Presenter; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources; import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil; +import com.sap.sailing.gwt.home.shared.utils.LogoUtil; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.LinkUtil; @@ -65,6 +66,21 @@ public class SeriesHeader extends Composite { public String getLongText(String url) { return StringMessages.INSTANCE.seriesSharingLongText(series.getDisplayName(), url); } + + @Override + public String getImageUrl() { + return LogoUtil.resolveLogoURL(series); + } + + @Override + public String getTitle() { + return series.getDisplayName(); + } + + @Override + public String getDescription() { + return getShortText(); + } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java index f5a98882a8c..c4a165f7d0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java @@ -2,8 +2,14 @@ package com.sap.sailing.gwt.home.desktop.partials.sharing; import static com.google.gwt.dom.client.Style.Display.NONE; +import java.util.ArrayList; + import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.MetaElement; +import com.google.gwt.dom.client.NodeList; import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -15,6 +21,12 @@ import com.sap.sse.gwt.shared.ClientConfiguration; public class SharingButtons extends Composite { + private static final String RDFA_ATTRIBUTE_IDENTIFIER = "property"; + private static final String DEFAULT_TYPE_PROPERTY_VALUE = "website"; + private static final String OG_IMAGE = "og:image"; + private static final String OG_TYPE = "og:type"; + private static final String OG_TITLE = "og:title"; + private static final String OG_DESCRIPTION = "og:description"; private static SharingButtonsUiBinder uiBinder = GWT.create(SharingButtonsUiBinder.class); interface SharingButtonsUiBinder extends UiBinder { @@ -37,10 +49,9 @@ public class SharingButtons extends Composite { if (!ClientConfiguration.getInstance().isBrandingActive()) { return; } - String shortText = provider.getShortText(); String longText = provider.getLongText(Window.Location.getHref()); - + appendOpenGraphMetaTags(provider); UrlBuilder mailtoLink = new UrlBuilder().setProtocol("mailto").setParameter("subject", shortText).setParameter("body", longText); // URLBuilder encodes spaces in parameters using "+" instead of "%20". This causes problems in Mail programs that do not decode "+" as space. mail.setHref(mailtoLink.buildString().replace("+", "%20")); @@ -50,4 +61,48 @@ public class SharingButtons extends Composite { .setPath("sharer/sharer.php").setParameter("u", Window.Location.getHref()); facebook.setHref(facebookLink.buildString()); } + + private void appendOpenGraphMetaTags(SharingMetadataProvider provider) { + final Document document = Document.get(); + final ArrayList metaElements = new ArrayList<>(); + final MetaElement descriptionMetaTag = findOrCreateMetaElement(OG_DESCRIPTION, document); + descriptionMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_DESCRIPTION); + descriptionMetaTag.setContent(provider.getDescription()); + metaElements.add(descriptionMetaTag); + final MetaElement titleMetaTag = findOrCreateMetaElement(OG_TITLE, document); + titleMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_TITLE); + titleMetaTag.setContent(provider.getTitle()); + metaElements.add(titleMetaTag); + final MetaElement typeMetaTag = findOrCreateMetaElement(OG_TYPE, document); + typeMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_TYPE); + typeMetaTag.setContent(DEFAULT_TYPE_PROPERTY_VALUE); + metaElements.add(typeMetaTag); + final MetaElement imageMetaTag = findOrCreateMetaElement(OG_IMAGE, document); + imageMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_IMAGE); + imageMetaTag.setContent(provider.getImageUrl()); + metaElements.add(imageMetaTag); + appendMetaElements(metaElements); + } + + private MetaElement findOrCreateMetaElement(final String tagId, final Document document) { + final Element elementById = document.getElementById(tagId); + MetaElement metaElement; + if(elementById != null && elementById instanceof MetaElement) { + metaElement = (MetaElement) elementById; + }else { + metaElement = document.createMetaElement(); + } + metaElement.setId(tagId); + metaElement.removeFromParent(); + return metaElement; + } + + private void appendMetaElements(final ArrayList metaElements) { + final Document document = Document.get(); + final NodeList nodes = document.getElementsByTagName("head"); + final Element head = (Element) nodes.getItem(0); + for(final MetaElement metaTag : metaElements) { + head.appendChild(metaTag); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java index d1d3d03fb84..3fc32acc602 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java @@ -2,8 +2,13 @@ package com.sap.sailing.gwt.home.desktop.partials.sharing; public interface SharingMetadataProvider { + String getTitle(); + + String getDescription(); + String getShortText(); String getLongText(String url); - + + String getImageUrl(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java index d08bf84ce83..f6383642490 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java @@ -47,7 +47,7 @@ public class EventHeader extends Composite { nameSeparatorUi.removeFromParent(); } LabelTypeUtil.renderLabelType(eventStateUi, event.getState().getStateMarker()); - LogoUtil.setEventLogo(eventLogoUi, event); + LogoUtil.setLogo(eventLogoUi, event); if (logoNavigation != null) { logoNavigation.configureAnchorElement(eventLogoUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java index 0a437bbd460..cdbf3f5d09a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java @@ -37,7 +37,7 @@ public class SeriesHeader extends Composite { } private void setUiFieldValues(EventSeriesViewDTO event, PlaceNavigation logoNavigation) { - LogoUtil.setEventLogo(eventLogoUi, event); + LogoUtil.setLogo(eventLogoUi, event); if (logoNavigation != null) { logoNavigation.configureAnchorElement(eventLogoUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java index 8af83592426..ffa2c46b428 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java @@ -20,11 +20,15 @@ public abstract class LogoUtil { * @param eventLogoUi {@link Element} where the logo is set * @param event {@link EventViewDTO} providing data */ - public static void setEventLogo(Element eventLogoUi, HasLogo event) { - String logoUrl = event.getLogoImage() != null ? event.getLogoImage().getSourceRef() : DEFAULT_EVENT_LOGO.asString(); + public static void setLogo(Element eventLogoUi, HasLogo event) { + String logoUrl = resolveLogoURL(event); setLogoAndTitle(eventLogoUi, logoUrl, event.getDisplayName()); } + public static String resolveLogoURL(HasLogo event) { + return event.getLogoImage() != null ? event.getLogoImage().getSourceRef() : DEFAULT_EVENT_LOGO.asString(); + } + private static void setLogoAndTitle(Element logoUi, String logoUrl, String logoTitle) { logoUi.getStyle().setBackgroundImage("url(" + logoUrl + ")"); logoUi.setTitle(logoTitle); From d5e153ebcd6caac42e71cf8898f851c369654e28 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Thu, 16 Jul 2020 10:23:02 +0200 Subject: [PATCH 044/235] bug5312: including required meta tag og:url --- .../gwt/home/desktop/partials/sharing/SharingButtons.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java index c4a165f7d0f..b2bfc00467d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java @@ -27,6 +27,7 @@ public class SharingButtons extends Composite { private static final String OG_TYPE = "og:type"; private static final String OG_TITLE = "og:title"; private static final String OG_DESCRIPTION = "og:description"; + private static final String OG_URL = "og:url"; private static SharingButtonsUiBinder uiBinder = GWT.create(SharingButtonsUiBinder.class); interface SharingButtonsUiBinder extends UiBinder { @@ -81,6 +82,10 @@ public class SharingButtons extends Composite { imageMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_IMAGE); imageMetaTag.setContent(provider.getImageUrl()); metaElements.add(imageMetaTag); + final MetaElement urlMetaTag = findOrCreateMetaElement(OG_URL, document); + urlMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_URL); + urlMetaTag.setContent(Window.Location.getHref()); + metaElements.add(urlMetaTag); appendMetaElements(metaElements); } From c3b5f992d28fbe963987cfba19361117478a337b Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Thu, 16 Jul 2020 11:22:17 +0200 Subject: [PATCH 045/235] bug5312: using thumbnail image instead of logo --- .../gwt/home/desktop/partials/eventheader/EventHeader.java | 2 +- .../gwt/home/desktop/partials/seriesheader/SeriesHeader.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java index 7ec0671fc6c..65736385d73 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java @@ -93,7 +93,7 @@ public class EventHeader extends Composite { @Override public String getImageUrl() { - return LogoUtil.resolveLogoURL(event); + return event.getThumbnailImageURL(); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java index 85b9eee6066..663705b3e9b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java @@ -12,6 +12,7 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.event.EventAndLeaderboardReferenceWithStateDTO; +import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO; import com.sap.sailing.gwt.home.communication.event.EventState; import com.sap.sailing.gwt.home.communication.fakeseries.EventSeriesViewDTO; import com.sap.sailing.gwt.home.desktop.partials.sharing.SharingButtons; @@ -22,7 +23,6 @@ import com.sap.sailing.gwt.home.desktop.places.fakeseries.SeriesView.Presenter; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources; import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil; -import com.sap.sailing.gwt.home.shared.utils.LogoUtil; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.LinkUtil; @@ -69,7 +69,8 @@ public class SeriesHeader extends Composite { @Override public String getImageUrl() { - return LogoUtil.resolveLogoURL(series); + EventMetadataDTO mostRecentEventInSeries = series.getEventsDescending().get(0); + return mostRecentEventInSeries.getThumbnailImageURL(); } @Override From a060608f11c89d55357fff09b6a2a4f914b8d3a7 Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Fri, 17 Jul 2020 09:52:55 +0200 Subject: [PATCH 046/235] check with delay bug 5232 --- .../sap/sailing/gwt/home/desktop/partials/header/Header.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java index ec61f70390f..4deb8265b72 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java @@ -160,7 +160,7 @@ public class Header extends Composite implements HeaderConstants { menuToDropDownItemMap.put(eventsPageLink, eventsPageLinkMenu); menuToDropDownItemMap.put(solutionsPageLink, solutionsPageLinkMenu); menuToDropDownItemMap.put(adminConsolePageLink, adminConsolePageLinkMenu); - menuToDropDownItemMap.put(dataMiningPageLink,dataMiningPageLinkMenu); + menuToDropDownItemMap.put(dataMiningPageLink, dataMiningPageLinkMenu); headerNavigationDropDownMenuContainer.getStyle().setDisplay(Display.NONE); final DropdownHandler dropdownHandler = new DropdownHandler(headerNavigationIcon, headerNavigationDropDownMenuContainer); menuItemVisibilityHandler = new MenuItemVisibilityHandler(menuToDropDownItemMap, dropdownHandler, headerNavigationIcon); @@ -244,7 +244,7 @@ public class Header extends Composite implements HeaderConstants { solutionsPageLinkMenu.addClickHandler(this::goToSolutions); //remaining entries please see at registration for AuthenticationContextEvent // refresh after UI has been put into place - menuItemVisibilityHandler.refreshVisibility(); + menuItemVisibilityHandler.refreshVisibility(100); } @UiHandler("startPageLink") From 698affa53a760b16fe856a73361088533f7a6b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 12:21:37 +0200 Subject: [PATCH 047/235] bug5259: Implemented Petri Net in TimeRangeResultCache.java --- .../client/async/TimeRangeResultCache.java | 349 +++++++++++------- 1 file changed, 220 insertions(+), 129 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 89205f11ad3..8fb27c3cc63 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -1,13 +1,12 @@ package com.sap.sse.gwt.client.async; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import com.google.gwt.user.client.rpc.AsyncCallback; @@ -17,10 +16,12 @@ import com.sap.sse.common.Util.Pair; /** * Optimizes requests by trimming their wanted {@link TimeRange} against a cache of (fetched and still outstanding) * results. In the context of a {@link TimeRangeActionsExecutor} such a cache is responsible for managing the requests - * for a single {@code Key}, such as a competitor or a boat class.

+ * for a single {@code Key}, such as a competitor or a boat class. + *

* - * Before making a request for a time range, the executor has to call {@link #trimAndRegisterRequest(TimeRange, boolean)}. - * When {@code false} is used for the {@code forceTimeRange} parameter, this cache will + * Before making a request for a time range, the executor has to call + * {@link #trimAndRegisterRequest(TimeRange, boolean)}. When {@code false} is used for the {@code forceTimeRange} + * parameter, this cache will * * @param * type of the results of the remote calls which are to be cached @@ -32,98 +33,202 @@ public class TimeRangeResultCache { private static final int CACHE_SIZE = 32; /** - * Stores properties of a request that may have been the result of trimming an original request's time ranges. - * If trimming took place (see {@link TimeRangeResultCache#trimAndRegisterRequest(TimeRange, boolean)}), the - * trimmed request will have {@link #getDependencies() dependencies} on other {@link Request}s whose results - * it needs to combine with its own result in order to satisfy the original untrimmed request from which it - * was derived by trimming.

- * - * This request keeps track of whether it {@link #hasResult() has received a result}. It furthermore manages - * a set of {@link AsyncCallback} callbacks that will be triggered exactly once when a result has been + * Stores properties of a request that may have been the result of trimming an original request's time ranges. If + * trimming took place (see {@link TimeRangeResultCache#trimAndRegisterRequest(TimeRange, boolean)}), the trimmed + * request will have {@link #getChildrenSet() dependencies} on other {@link Request}s whose results it needs to + * combine with its own result in order to satisfy the original untrimmed request from which it was derived by + * trimming. + *

+ * + * This request keeps track of whether it {@link #hasResult() has received a result}. It furthermore manages a set + * of {@link AsyncCallback} callbacks that will be triggered exactly once when a result has been * {@link #setResult(Pair) received} for this request. + *

+ * + * Dependent requests are represented as a petri net. {@link Request}s are vertices and {@link #childrenSet} and + * {@link #parentSet} make up doubly-linked edges. {@link Result}s are tokens which get handled by a transition + * function implemented in {@link #notifyActionSuccessIfHasAllResults()}. */ protected class Request { - private final Set dependsOnSet = new HashSet<>(); + private final TimeRangeAsyncAction action; + private final AsyncCallback>> callback; + private boolean callbackWasCalled = false; + private final TimeRange actionTimeRange; + private TimeRange trimmedTimeRange; + private final Set parentSet = new HashSet<>(); + private final Set childrenSet = new HashSet<>(); + private int childrenWithoutResultCounter = 0; private boolean hasResult = false; - private Pair result = null; - private final List> waitingOnResultList = new ArrayList<>(0); // TODO Verify initial cap + private Result result = null; - public Request() { - } - - public Request(Pair result) { - this.hasResult = true; - this.result = result; + public Request(TimeRange timeRange, AsyncCallback>> callback, + TimeRangeAsyncAction action) { + this.actionTimeRange = timeRange; + this.trimmedTimeRange = timeRange; + this.callback = callback; + this.action = action; } /** - * Obtains all other requests (excluding {@code this} request) of which all or parts of their results - * are required to combine them with the results of this trimmed request into a result that reflects the - * response to the original request from which this request was derived by trimming. + * Obtains all other requests (excluding {@code this} request) of which all or parts of their results are + * required to combine them with the results of this trimmed request into a result that reflects the response to + * the original request from which this request was derived by trimming. */ - public Set getDependencies() { - return dependsOnSet; + public Set getChildrenSet() { + return childrenSet; } /** - * Records that this request depends on the results of {@code request} because this request was trimmed based - * on the assumption that {@code request}'s results are or will become available and cover a part of what this - * request was originally expected to deliver. + * Might immediately call {@link #notifyActionSuccessIfHasAllResults()}. */ - public void addDependency(Request request) { - dependsOnSet.add(request); + public void addChildren(List requests) { + if (callbackWasCalled) { + throw new IllegalStateException("Children may not be added after results have been submitted!"); + } + childrenWithoutResultCounter++; // This will later be undone and serves the purpose of delaying the call to notifyActionSuccessIfHasAllResults until all children have been added + for (Request request : requests) { + assert !this.equals(request); + final boolean notPresentBefore = childrenSet.add(request); + if (notPresentBefore) { + childrenWithoutResultCounter++; + } + request.addParent(this); // Will immediately decrement childrenWithoutResultCounter if result is present + } + childrenWithoutResultCounter--; + notifyActionSuccessIfHasAllResults(); } - public void clearDependencies() { - dependsOnSet.clear(); + private void releaseChildren() { + for (Request child : childrenSet) { + child.removeParent(this); + if (child.canBeEvicted()) { + TimeRangeResultCache.this.evictRequestFromCache(child); + } + } + childrenSet.clear(); } - public Pair getResult() { + public Set getParentSet() { + return parentSet; + } + + private void addParent(Request parent) { + assert !this.equals(parent); + final boolean notPresentBefore = parentSet.add(parent); + if (notPresentBefore && hasResult) { + parent.onChildSuccess(); + } + } + + private void removeParent(Request parent) { + parentSet.remove(parent); + } + + public void onSuccess(Result result) { + setResult(result); + notifyActionSuccessIfHasAllResults(); + parentSet.forEach(parent -> parent.onChildSuccess()); + } + + public void onFailure(Throwable caught) { + notifyActionFailure(caught); + Set parents = new HashSet<>(parentSet); //TODO ConcurrentModificationError + parents.forEach(parent -> parent.onChildFailure(caught)); + } + + private void onChildSuccess() { + childrenWithoutResultCounter--; + notifyActionSuccessIfHasAllResults(); + } + + private void onChildFailure(Throwable caught) { + // TODO + notifyActionFailure(caught); + } + + private void notifyActionSuccessIfHasAllResults() { + if (!callbackWasCalled && hasResult && childrenWithoutResultCounter <= 0) { + // TODO + List> results = new ArrayList<>(childrenSet.size() + 1); + results.add(new Pair<>(getTrimmedTimeRange(), getResult())); + for (Request child : childrenSet) { + results.add(new Pair<>(child.getTrimmedTimeRange(), child.getResult())); + } + callback.onSuccess(results); + callbackWasCalled = true; + releaseChildren(); + if (canBeEvicted()) { + TimeRangeResultCache.this.evictRequestFromCache(this); + } + } + } + + private void notifyActionFailure(Throwable caught) { + //TODO + if (!callbackWasCalled) { + callback.onFailure(caught); + callbackWasCalled = true; + releaseChildren(); + if (canBeEvicted()) { + TimeRangeResultCache.this.evictRequestFromCache(this); + } + } + } + + protected boolean canBeEvicted() { + return callbackWasCalled && parentSet.isEmpty(); + } + + public Result getResult() { return result; } /** - * Sets the result for this request's total time range; {@link #hasResult()} will return {@code true} - * after this method returns, and {@link #getResult()} will return {@code result} from then on. - * All callbacks that were registered using {@link #registerOnResultCallback(AsyncCallback)} up to this time - * will be {@link AsyncCallback#onSuccess(Object) notified}. + * Sets the result for this request's total time range; {@link #hasResult()} will return {@code true} after this + * method returns, and {@link #getResult()} will return {@code result} from then on. All callbacks that were + * registered using {@link #registerOnResultCallback(AsyncCallback)} up to this time will be + * {@link AsyncCallback#onSuccess(Object) notified}. */ - public void setResult(Pair result) { + private void setResult(Result result) { this.hasResult = true; this.result = result; - waitingOnResultList.forEach(c -> c.onSuccess(null)); - waitingOnResultList.clear(); } public boolean hasResult() { return hasResult; } - /** - * The callback is added to the set of callbacks notified when this request {@link #setResult(Pair) receives} - * its result. Registering a callback after the result has already been received has no effect. The callback - * set will be cleared once the result has been received. - */ - public void registerOnResultCallback(AsyncCallback callback) { - waitingOnResultList.add(callback); + public TimeRange getActionTimeRange() { + return actionTimeRange; + } + + public TimeRange getTrimmedTimeRange() { + return trimmedTimeRange; + } + + public void setTrimmedTimeRange(TimeRange timeRange) { + this.trimmedTimeRange = timeRange; } } /** * The size-limited {@link Request} cache. The keys are the time ranges that may have resulted from trimming and * represent those time ranges for which requests are effectively made. The corresponding value {@link Request} - * object captures {@link Request#getDependencies() requests on which the request depends} after having been - * trimmed. + * object captures {@link Request#getChildrenSet() requests on which the request depends} after having been trimmed. */ - private final Map requestCache = new LinkedHashMap( - /* initialCapacity */ CACHE_SIZE, /* loadFactor */ .75f, /* accessOrder */ true) { - private static final long serialVersionUID = -5421470532158005911L; + private final Map, Request> requestCache = new HashMap<>(CACHE_SIZE); - @Override - protected boolean removeEldestEntry(Entry eldest) { - return size() >= CACHE_SIZE; // FIXME don't remove an entry on whose request other requests in the cache depend - } - }; + private void evictRequestFromCache(TimeRangeAsyncAction action) { + requestCache.remove(action); + } + + private void evictRequestFromCache(Request request) { + evictRequestFromCache(request.action); + } + + int getCacheSize() { + return requestCache.size(); + } /** * Trims the requested {@link TimeRange} against results that are already cached or requested and registers the @@ -137,18 +242,15 @@ public class TimeRangeResultCache { * @return {@link TimeRange} to effectively request (potentially trimmed from {@code toTrim}) or {@code null} if no * request is to be made since the results are cached. */ - TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange) { - final Request request = new Request(); - final TimeRange effectivePotentiallyTrimmedRequest; - if (!forceTimeRange) { // TODO Remove old results? - effectivePotentiallyTrimmedRequest = trimTimeRangeAndAttachDeps(toTrim, request, requestCache.entrySet()); - } else { - effectivePotentiallyTrimmedRequest = toTrim; - } + public TimeRange trimAndRegisterRequest(TimeRange toTrim, boolean forceTimeRange, TimeRangeAsyncAction action, + AsyncCallback>> callback) { + final Request request = new Request(toTrim, callback, action); + final TimeRange effectivePotentiallyTrimmedRequest = !forceTimeRange ? trimTimeRangeAndAttachDeps(request) + : toTrim; // null signals that the result is already present if (effectivePotentiallyTrimmedRequest != null) { // cache the request which is expected to not yet have a result - requestCache.put(effectivePotentiallyTrimmedRequest, request); + requestCache.put(action, request); } return effectivePotentiallyTrimmedRequest; } @@ -160,62 +262,45 @@ public class TimeRangeResultCache { * @param effectiveRequestTimeRange * {@link TimeRange} that the request that now returned a result was trimmed to. * @param result - * {@link Result} of the trimmed request; this result will be cached, keyed by its trimmed {@code timeRange} + * {@link Result} of the trimmed request; this result will be cached, keyed by its trimmed + * {@code timeRange} * @param callbackIfResultsAreMissing * {@link AsyncCallback}{@code } to call {@link AsyncCallback#onSuccess(Object)} on once a request - * this one depends on and which was still in transit returns. Note that this then does not imply that all requests - * needed to fulfill the original request have delivered their result yet; the same may happen again for another - * dependency when trying again later. + * this one depends on and which was still in transit returns. Note that this then does not imply that + * all requests needed to fulfill the original request have delivered their result yet; the same may + * happen again for another dependency when trying again later. * @return {@link List} of {@link TimeRange}, {@link Result} {@link Pair}s that contain all data needed to construct * a time-continuous {@link Result} object, or {@code null} if not all results on which the request for - * {@code effectiveRequestTimeRange} depended have been received yet; in this case the {@code callbackIfResultsAreMissing} - * will later receive a callback if one (but maybe not all) of those results has been received. + * {@code effectiveRequestTimeRange} depended have been received yet; in this case the + * {@code callbackIfResultsAreMissing} will later receive a callback if one (but maybe not all) of those + * results has been received. */ - public List> registerAndCollectResult(TimeRange effectiveRequestTimeRange, Result result, - AsyncCallback callbackIfResultsAreMissing) { - final Pair requestResult = new Pair<>(effectiveRequestTimeRange, result); - Request request = requestCache.get(effectiveRequestTimeRange); - if (request != null) { - request.setResult(requestResult); // notifies all callbacks that were waiting for this result - } else { - // FIXME the request that has returned now must have been evicted from the size-limited cache; but what about any dependencies? - request = new Request(requestResult); - requestCache.put(effectiveRequestTimeRange, request); + public void registerResult(TimeRangeAsyncAction action, Result result) { + Request request = requestCache.get(action); + if (request == null) { + throw new IllegalArgumentException("Attempted to register result for non-existent request: " + action.toString()); } - final List> results = new ArrayList<>(request.getDependencies().size() + 1); - results.add(requestResult); - for (final Request dep : request.getDependencies()) { // FIXME see above; no dependencies for new request that replaces an evicted one - if (!dep.hasResult()) { - dep.registerOnResultCallback(callbackIfResultsAreMissing); - return null; // TODO it's sufficient to register on *one* dependency, although this may lead to a few premature re-tries - } else { - results.add(dep.getResult()); - } - } - // FIXME if ExecutorCallback resolves the same compound result multiple times, clearing dependencies here would no longer let us find the dependencies required here - request.clearDependencies(); // FIXME moot if the request was just created above because the original one was evicted from the cache earlier - return results; + request.onSuccess(result); // notifies all callbacks that were waiting for this result } /** * Removes the failed request from cache. * * @param timeRange - * effective {@link TimeRange} of failed request (result of potential trimming) + * effective {@link TimeRange} of failed request (result of potential trimming) */ - public void registerFailure(TimeRange timeRange) { - Request request = requestCache.remove(timeRange); - // TODO Requests dependent on this one? - // FIXME shouldn't this propagate as failure also to the callbacks of dependent requests? Add failure tests for trimmed requests! + public void registerFailure(TimeRangeAsyncAction action, Throwable cause) { + Request request = requestCache.get(action); if (request != null) { - request.setResult(null); + request.onFailure(cause); } } /** * Trims a request based on cached results, returning a potentially trimmed time range that reflects which part(s) * is/are missing from the cache. For simplicity {@code toTrim} will currently not be split up into multiple - * TimeRanges if a part in the middle is cached.

+ * TimeRanges if a part in the middle is cached. + *

* * * @@ -229,44 +314,50 @@ public class TimeRangeResultCache { * @return {@link TimeRange} which to request because it is not covered by {@code rangesToTrimWith} or {@code null} * if no request to the server is to be made. */ - private TimeRange trimTimeRangeAndAttachDeps(TimeRange toTrim, Request request, - Iterable> rangesToTrimWith) { - List> rangesToTrimWithAsList = new LinkedList<>(); - rangesToTrimWith.forEach(rangesToTrimWithAsList::add); - for (int i = 0; i < TRIM_MAX_ITERATIONS; i++) { + private TimeRange trimTimeRangeAndAttachDeps(Request request) { + TimeRange toTrim = request.getActionTimeRange(); + List rangesToTrimWithAsList = new LinkedList<>(); //TODO Profile against ArrayList (swap and remove) + requestCache.values().forEach(rangesToTrimWithAsList::add); + List childrenList = new ArrayList<>(); + iterationsLoop: for (int i = 0; i < TRIM_MAX_ITERATIONS; i++) { boolean rangeWasTrimmedThisIteration = false; - final Iterator> iter = rangesToTrimWithAsList.iterator(); + final Iterator iter = rangesToTrimWithAsList.iterator(); while (iter.hasNext()) { - final Map.Entry element = iter.next(); - final TimeRange trimWith = element.getKey(); - if (toTrim.intersects(trimWith)) { - // Only consider TimeRanges that touch or overlap toTrim - if (toTrim.liesWithin(trimWith)) { - // toTrim is fully covered by trimWith - if (request != null) { - request.addDependency(element.getValue()); - } - return null; // meaning that no additional effective requests are required; the cache has or will have it all - } else if (!toTrim.includes(trimWith) - || (toTrim.from().equals(trimWith.from()) || toTrim.to().equals(trimWith.to()))) { - // toTrim and trimWith overlap and trimWith does not lie within toTrim - // OR special case where trimWith lies within toTrim but touches one of the ends - if (request != null) { - request.addDependency(element.getValue()); - } + final Request element = iter.next(); + final TimeRange trimWith = element.getTrimmedTimeRange(); + if (trimWith != null) { + final boolean rangeAfter = toTrim.from().after(trimWith.to()) || toTrim.from().equals(trimWith.to()); + final boolean fromAfter = toTrim.from().after(trimWith.from()); + final boolean fromIncluded = fromAfter || toTrim.from().equals(trimWith.from()); + final boolean rangeBefore = toTrim.to().before(trimWith.from()) || toTrim.to().equals(trimWith.from()); + final boolean toBefore = toTrim.to().before(trimWith.to()); + final boolean toIncluded = toBefore || toTrim.to().equals(trimWith.to()); + if (rangeAfter || rangeBefore) { + // toTrim and trimWith do not touch at all; Do not consider in following iterations + iter.remove(); + } else if (fromIncluded && toIncluded) { + // toTrim is completely included in trimWith + childrenList.add(element); + toTrim = null; + break iterationsLoop; // toTrim has been completely trimmed away + } else if (fromIncluded || toIncluded) { + // toTrim overlaps trimWith but only on one side i.e. trimWith is not included in toTrim + childrenList.add(element); toTrim = toTrim.subtract(trimWith).iterator().next(); iter.remove(); rangeWasTrimmedThisIteration = true; } } else { - // no intersection between request and cache entry; remove the cache entry from the list of requests to consider + // trimWith is null; Do not consider in following iterations iter.remove(); } } if (!rangeWasTrimmedThisIteration) { - break; + break iterationsLoop; } } + request.setTrimmedTimeRange(toTrim); + request.addChildren(childrenList); return toTrim; } } From 4b598c820d4d952554a9516d5d19ab7728016b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 12:22:22 +0200 Subject: [PATCH 048/235] bug5259: Updated TimeRangeResultCacheTest to compile again --- .../async/TimeRangeResultCacheTest.java | 169 +++++++++++++++--- 1 file changed, 146 insertions(+), 23 deletions(-) diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java index b33c83cd207..cb3f89dc1d7 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeResultCacheTest.java @@ -2,61 +2,171 @@ package com.sap.sse.gwt.client.async; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util.Pair; public class TimeRangeResultCacheTest { + private static AsyncCallback>> getCallback(boolean assertSuccess) { + return new AsyncCallback>>() { + private boolean triggered = false; + @Override + public void onFailure(Throwable caught) { + assertTriggeredOnce(); + if (assertSuccess) { + assertTrue("Expected onSuccess but got onFailure", false); // Fail test + } + } + @Override + public void onSuccess(List> result) { + assertTriggeredOnce(); + if (!assertSuccess) { + assertTrue("Expected onFailure but got onSuccess", false); // Fail test + } + } + private void assertTriggeredOnce() { + assertFalse("Callback was triggered more than once", triggered); + triggered = true; + } + }; + } + private static TimeRangeAsyncAction getDummyAction() { + return new TimeRangeAsyncAction() { + @Override + public void execute(Collection> timeRanges, AsyncCallback callback) { + // Nop + } + @Override + public Collection> getTimeRanges() { + return Collections.emptyList(); + } + }; + } + @Test public void testSimpleRequest() { - TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + TimeRangeResultCache cache = new TimeRangeResultCache<>(); TimeRange timeRange = new MilliTimeRange(100, 1000); - assertEquals(timeRange, trimmer.trimAndRegisterRequest(timeRange, /* force */ false)); - List> expectedResult = new ArrayList<>(1); - expectedResult.add(new Pair<>(timeRange, null)); - assertArrayEquals(expectedResult.toArray(), trimmer.registerAndCollectResult(timeRange, null, null).toArray()); + TimeRangeAsyncAction action = getDummyAction(); + final AtomicBoolean callbackHasRun = new AtomicBoolean(false); + assertEquals(0, cache.getCacheSize()); + assertEquals(timeRange, cache.trimAndRegisterRequest(timeRange, /* force */ false, action, new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + assertTrue(false); + } + @Override + public void onSuccess(List> result) { + List> expectedResult = new ArrayList<>(1); + expectedResult.add(new Pair<>(timeRange, null)); + assertArrayEquals(expectedResult.toArray(), result.toArray()); + assertFalse(callbackHasRun.getAndSet(true)); // Fails if run more than once + } + })); + assertEquals(1, cache.getCacheSize()); + cache.registerResult(action, null); + assertEquals(0, cache.getCacheSize()); + assertTrue(callbackHasRun.get()); + } + + @Test + public void testDependentRequests() { + TimeRangeResultCache cache = new TimeRangeResultCache<>(); + final TimeRangeAsyncAction firstAction = getDummyAction(); + final TimeRangeAsyncAction secondAction = getDummyAction(); + final TimeRangeAsyncAction thirdAction = getDummyAction(); + final TimeRangeAsyncAction fourthAction = getDummyAction(); + final TimeRange firstRange = new MilliTimeRange(10_000L, 20_000L); + final TimeRange firstExpectedRange = new MilliTimeRange(10_000L, 20_000L); + final TimeRange secondRange = new MilliTimeRange(11_000L, 21_000L); + final TimeRange secondExpectedRange = new MilliTimeRange(20_000L, 21_000L); + final TimeRange thirdRange = new MilliTimeRange(12_000L, 22_000L); + final TimeRange thirdExpectedRange = new MilliTimeRange(21_000L, 22_000L); + final TimeRange fourthRange = new MilliTimeRange(21_000L, 23_000L); + final TimeRange fourthExpectedRange = new MilliTimeRange(22_000L, 23_000L); + assertEquals(0, cache.getCacheSize()); + assertEquals(firstExpectedRange, cache.trimAndRegisterRequest(firstRange, false, firstAction, getCallback(true))); + assertEquals(1, cache.getCacheSize()); + assertEquals(secondExpectedRange, cache.trimAndRegisterRequest(secondRange, false, secondAction, getCallback(false))); + assertEquals(2, cache.getCacheSize()); + assertEquals(thirdExpectedRange, cache.trimAndRegisterRequest(thirdRange, false, thirdAction, getCallback(false))); + assertEquals(3, cache.getCacheSize()); + assertEquals(fourthExpectedRange, cache.trimAndRegisterRequest(fourthRange, false, fourthAction, getCallback(true))); + assertEquals(4, cache.getCacheSize()); + cache.registerResult(firstAction, null); + assertEquals(4, cache.getCacheSize()); + cache.registerFailure(secondAction, new Throwable("Second request failed")); + assertEquals(2, cache.getCacheSize()); + cache.registerResult(fourthAction, null); + assertEquals(2, cache.getCacheSize()); + cache.registerResult(thirdAction, null); + assertEquals(0, cache.getCacheSize()); + } + + @Test(expected = IllegalArgumentException.class) + public void testRegisterResultForNonExistentRequest() { + TimeRangeResultCache cache = new TimeRangeResultCache<>(); + cache.registerResult(getDummyAction(), null); } @Test public void testTrimSinglePass() { - TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + TimeRangeResultCache cache = new TimeRangeResultCache<>(); final TimeRange requestRange = new MilliTimeRange(10_000L, 20_000L); final TimeRange expectedRange = new MilliTimeRange(15_000L, 18_000L); final TimeRange leftRange = new MilliTimeRange(10_000L, 15_000L); final TimeRange rightRange = new MilliTimeRange(18_000L, 30_000L); final TimeRange includedRange = new MilliTimeRange(16_000L, 17_000L); final TimeRange outsideRange = new MilliTimeRange(50_000L, 100_000L); - trimmer.trimAndRegisterRequest(leftRange, true); - trimmer.trimAndRegisterRequest(rightRange, true); - trimmer.trimAndRegisterRequest(includedRange, true); - trimmer.trimAndRegisterRequest(outsideRange, true); - assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, /* force */ false))); + cache.trimAndRegisterRequest(leftRange, true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(rightRange, true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(includedRange, true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(outsideRange, true, getDummyAction(), getCallback(true)); + assertEquals(expectedRange, new MilliTimeRange(cache.trimAndRegisterRequest(requestRange, /* force */ false, getDummyAction(), getCallback(true)))); } @Test public void testTrimMultiplePasses() { - TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + TimeRangeResultCache cache = new TimeRangeResultCache<>(); final TimeRange requestRange = new MilliTimeRange(100_000L, 200_000L); final TimeRange expectedRange = new MilliTimeRange(150_000L, 190_000L); final TimeRange leftRange = new MilliTimeRange( 90_000L, 125_000L); final TimeRange leftSecondPassRange = new MilliTimeRange(120_000L, 150_000L); final TimeRange rightRange = new MilliTimeRange(190_000L, 300_000L); final TimeRange outsideRange = new MilliTimeRange( 50_000L, 100_000L); - trimmer.trimAndRegisterRequest(leftSecondPassRange, /* force */ true); - trimmer.trimAndRegisterRequest(leftRange, /* force */ true); - trimmer.trimAndRegisterRequest(rightRange, /* force */ true); - trimmer.trimAndRegisterRequest(outsideRange, /* force */ true); - assertEquals(expectedRange, new MilliTimeRange(trimmer.trimAndRegisterRequest(requestRange, /* force */ false))); + cache.trimAndRegisterRequest(leftSecondPassRange, /* force */ true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(leftRange, /* force */ true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(rightRange, /* force */ true, getDummyAction(), getCallback(true)); + cache.trimAndRegisterRequest(outsideRange, /* force */ true, getDummyAction(), getCallback(true)); + assertEquals(expectedRange, new MilliTimeRange(cache.trimAndRegisterRequest(requestRange, /* force */ false, getDummyAction(), getCallback(true)))); + } + + @Test + public void testTrimRequestElimination() { + TimeRangeResultCache cache = new TimeRangeResultCache<>(); + final TimeRange requestRange = new MilliTimeRange(150_000L, 250_000L); + final TimeRange surroundingRange = new MilliTimeRange(100_000L, 300_000L); + assertEquals(surroundingRange, cache.trimAndRegisterRequest(surroundingRange, true, getDummyAction(), getCallback(true))); + assertEquals(null, cache.trimAndRegisterRequest(requestRange, false, getDummyAction(), getCallback(true))); } @Test public void testGatherResults() { - TimeRangeResultCache trimmer = new TimeRangeResultCache<>(); + TimeRangeResultCache cache = new TimeRangeResultCache<>(); + final TimeRangeAsyncAction requestAction = getDummyAction(); + final TimeRangeAsyncAction leftAction = getDummyAction(); + final TimeRangeAsyncAction outsideAction = getDummyAction(); final Integer requestedInt = 1; final Integer leftInt = -1; final Integer outsideInt = 99; @@ -67,11 +177,24 @@ public class TimeRangeResultCacheTest { final List> expectedResult = new ArrayList<>(); expectedResult.add(new Pair<>(expectedRange, requestedInt)); expectedResult.add(new Pair<>(leftRange, leftInt)); - trimmer.registerAndCollectResult(leftRange, leftInt, null); - trimmer.registerAndCollectResult(outsideRange, outsideInt, null); - TimeRange trimmedRange = trimmer.trimAndRegisterRequest(requestedRange, /* force */ false); + final AtomicBoolean callbackHasRun = new AtomicBoolean(false); + assertEquals(leftRange, cache.trimAndRegisterRequest(leftRange, true, leftAction, getCallback(true))); + assertEquals(outsideRange, cache.trimAndRegisterRequest(outsideRange, true, outsideAction, getCallback(true))); + TimeRange trimmedRange = cache.trimAndRegisterRequest(requestedRange, /* force */ false, requestAction, new AsyncCallback>>() { + @Override + public void onSuccess(List> result) { + assertArrayEquals(expectedResult.toArray(), result.toArray()); + assertFalse(callbackHasRun.getAndSet(true)); + } + @Override + public void onFailure(Throwable caught) { + assertTrue(false); + } + }); assertEquals(expectedRange, trimmedRange); - List> result = trimmer.registerAndCollectResult(trimmedRange, requestedInt, null); - assertArrayEquals(expectedResult.toArray(), result.toArray()); + cache.registerResult(leftAction, leftInt); + cache.registerResult(outsideAction, outsideInt); + cache.registerResult(requestAction, requestedInt); + assertTrue(callbackHasRun.get()); } } From a9c724dc25409c3cc6f1fc0252c6998b8afd69c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 12:24:26 +0200 Subject: [PATCH 049/235] bug5259: Updated TimeRangeActionsExecutor to match TimeRangeResultCache --- .../async/TimeRangeActionsExecutor.java | 97 ++++++++++--------- .../client/async/TimeRangeAsyncCallback.java | 29 ++++-- 2 files changed, 73 insertions(+), 53 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index a39d82dbdb7..c3515402c0e 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -38,13 +38,13 @@ import com.sap.sse.common.Util.Pair; * @param * Type returned by the combined call. See {@link TimeRangeAsyncAction}. Holds a compound result for several * keys, such as for several competitors or several boats. A {@link TimeRangeAsyncCallback} object is - * expected to be able to {@link TimeRangeAsyncCallback#unzipResults(Object) split} such a {@code Result} + * expected to be able to {@link TimeRangeAsyncCallback#unzipResult(Object) split} such a {@code Result} * object into the {@code Key}s and {@code SubResult}s, as well as to * {@link TimeRangeAsyncCallback#joinSubResults(TimeRange, List) join} these sub-results of the individual * results for trimmed requests into a sub-result for the full time range requested for one {@code Key}. * @param * Type representing an individual part or channel of a compound {@link Result}, specific to one {@code Key}, - * as obtained by {@link TimeRangeAsyncCallback#unzipResults(Object) splitting} a compound {@code Result}. + * as obtained by {@link TimeRangeAsyncCallback#unzipResult(Object) splitting} a compound {@code Result}. * @param * Type used to index {@link SubResult}s. * @see TimeRangeAsyncAction @@ -58,67 +58,67 @@ public class TimeRangeActionsExecutor { * for a potentially trimmed request */ private final class ExecutorCallback implements AsyncCallback { + private final TimeRangeAsyncAction action; private final TimeRangeAsyncCallback callback; - private final Collection> requestedTimeRanges; - private final Map trimmedTimeRangeMap; + private final Map requestedTimeRangeMap; + private final Map>> subResultsMap; - private ExecutorCallback(Collection> requestedTimeRanges, - List> trimmedTimeRanges, TimeRangeAsyncCallback callback) { + private ExecutorCallback(TimeRangeAsyncAction action, + Collection> requestedTimeRanges, + TimeRangeAsyncCallback callback) { + this.action = action; this.callback = callback; - this.requestedTimeRanges = requestedTimeRanges; - trimmedTimeRangeMap = new HashMap<>(trimmedTimeRanges.size()); - trimmedTimeRanges.forEach(pair -> trimmedTimeRangeMap.put(pair.getA(), pair.getB())); + this.requestedTimeRangeMap = new HashMap<>(requestedTimeRanges.size()); + requestedTimeRanges.forEach(pair -> requestedTimeRangeMap.put(pair.getA(), pair.getB())); + this.subResultsMap = new HashMap<>(requestedTimeRanges.size()); } @Override public void onSuccess(Result result) { // from the compound result extract the potentially trimmed sub-results per key - final Map unzippedResultMap = result != null ? callback.unzipResults(result) : Collections.emptyMap(); - final Map> completedResultMap = new HashMap<>(); - for (Pair request : requestedTimeRanges) { - final Key key = request.getA(); + final Map unzippedResultMap = result != null ? callback.unzipResult(result) : Collections.emptyMap(); + for (Key key : requestedTimeRangeMap.keySet()) { final SubResult trimmedSubResultForKey = unzippedResultMap.get(key); final TimeRangeResultCache cache = getSubResultCache(key); - // FIXME registerAndCollectResult clears the Request's dependencies; returning bluntly below and hoping for a retry will no longer find those dependencies - final List> partialResults = cache.registerAndCollectResult( - trimmedTimeRangeMap.get(key), trimmedSubResultForKey, new AsyncCallback() { - @Override - public void onSuccess(Void voidResult) { - ExecutorCallback.this.onSuccess(result); // try again after another dependency has delivered its result - } - - @Override - public void onFailure(Throwable caught) { - } - }); - if (partialResults == null) { - return; // A required request is still in transit; the callback defined above will be notified when - } // another dependency delivers its result and will then try the result combination process again TODO extra work! - SubResult completedSubResult = callback.joinSubResults(request.getB(), partialResults); - completedResultMap.put(key, new Pair<>(request.getB(), completedSubResult)); + cache.registerResult(action, trimmedSubResultForKey); } - callback.onSuccess(completedResultMap); // TODO use the callback to combine the SubResults into a combined Result object } @Override public void onFailure(Throwable caught) { - trimmedTimeRangeMap.entrySet().forEach(e -> getSubResultCache(e.getKey()).registerFailure(e.getValue())); + requestedTimeRangeMap.keySet().forEach(key -> getSubResultCache(key).registerFailure(action, caught)); callback.onFailure(caught); } + + public void onSubResultSuccess(Key key, List> results) { + subResultsMap.put(key, results); + if (subResultsMap.size() == requestedTimeRangeMap.size()) { + final Map unzippedResult = new HashMap<>(subResultsMap.size()); + for (Map.Entry>> entry : subResultsMap.entrySet()) { + TimeRange requestedTimeRange = requestedTimeRangeMap.get(entry.getKey()); + unzippedResult.put(entry.getKey(), callback.joinSubResults(requestedTimeRange, entry.getValue())); + } + callback.onSuccess(callback.zipSubResults(unzippedResult)); + } + } + + public void onSubResultFailure(Throwable caught) { + //TODO + } } /** * {@link AsyncActionsExecutor} that will be used to actually execute remote procedures. */ private final AsyncActionsExecutor executor; - + /** * Action category passed along to the {@link #executor}. * * @see AsyncActionsExecutor#execute(AsyncAction, String, AsyncCallback) */ private final String actionCategory; - + private final Map> cacheMap = new HashMap<>(); //TODO Invalidation /** @@ -169,20 +169,27 @@ public class TimeRangeActionsExecutor { if (callback == null) { throw new IllegalArgumentException("callback must not be null!"); } - final List> trimmedTimeRanges = new ArrayList<>(action.getTimeRanges().size()); - for (Pair part : action.getTimeRanges()) { + final Collection> requestedTimeRanges = action.getTimeRanges(); + final ExecutorCallback execCallback = new ExecutorCallback(action, requestedTimeRanges, callback); + final List> trimmedTimeRanges = new ArrayList<>(requestedTimeRanges.size()); + for (Pair part : requestedTimeRanges) { final TimeRangeResultCache cache = getSubResultCache(part.getA()); - final TimeRange trimmedRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange); - if (trimmedRange != null) { - trimmedTimeRanges.add(new Pair<>(part.getA(), trimmedRange)); + TimeRange potentiallyTrimmedTimeRange = cache.trimAndRegisterRequest(part.getB(), forceTimeRange, action, + new AsyncCallback>>() { + @Override + public void onFailure(Throwable caught) { + execCallback.onSubResultFailure(caught); + } + @Override + public void onSuccess(List> result) { + execCallback.onSubResultSuccess(part.getA(), result); + } + }); + if (potentiallyTrimmedTimeRange != null) { + trimmedTimeRanges.add(new Pair<>(part.getA(), potentiallyTrimmedTimeRange)); } } - if (!trimmedTimeRanges.isEmpty()) { - executor.execute(cb->action.execute(trimmedTimeRanges, cb), - actionCategory, new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback)); - } else { - new ExecutorCallback(action.getTimeRanges(), trimmedTimeRanges, callback).onSuccess(null); - } + executor.execute(cb -> action.execute(trimmedTimeRanges, cb), actionCategory, execCallback); } private TimeRangeResultCache getSubResultCache(Key key) { diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java index efe4f909a61..c5958f850e2 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeAsyncCallback.java @@ -15,11 +15,12 @@ import com.sap.sse.common.Util.Pair; *

    *
  1. {@link TimeRangeAsyncAction} gets executed with a compound request for potentially several keys and returns a * compound server response of type {@code Result} - *
  2. The response gets split up into {@link SubResult}s by {@link Key} by {@link #unzipResults(Object)}. + *
  3. The response gets split up into {@link SubResult}s by {@link Key} by {@link #unzipResult(Object)}. *
  4. By {@link Key} the {@link SubResult}s get cached and get put into {@link #joinSubResults(TimeRange, List)} along * with all other required cached {@link SubResult}s.
  5. - *
  6. The completed {@link SubResult}s returned by {@link #joinSubResults(TimeRange, List)} get collected and put into - * {@link #onSuccess(Map)}.
  7. + *
  8. The completed {@link SubResult}s returned by {@link #joinSubResults(TimeRange, List)} get collected and zipped + * by {@link #zipSubResults(Map)}.
  9. + *
  10. Finally the {@link Result} is given to {@link #onSuccess(Object)}. *
* * @param @@ -36,12 +37,25 @@ import com.sap.sse.common.Util.Pair; public interface TimeRangeAsyncCallback { /** * Splits up a server response into {@link SubResult}s by {@link Key}. + * Inverse operation of {@link #zipSubResults()}. * * @param result * {@link Result} returned by server. * @return {@link Map} of {@link Key} {@link SubResult} pairs. + * @see #zipSubResults() */ - Map unzipResults(Result result); + Map unzipResult(Result result); + + /** + * Combines {@link SubResult}s by {@link Key} into a {@link Result}. + * Inverse operation of {@link #unzipResult(Object)}. + * + * @param subResultMap + * {@link Map} of {@link SubResult}s by {@link Key} to be combined. + * @return {@link Result} + * @see #unzipResult(Object) + */ + Result zipSubResults(Map subResultMap); /** * Joins possibly multiple {@link SubResult}s with different {@link TimeRange}s together to form the originally @@ -56,13 +70,12 @@ public interface TimeRangeAsyncCallback { SubResult joinSubResults(TimeRange timeRange, List> toJoin); /** - * Called after successful request and joining of {@link SubResult}s.
- * It might be useful to apply the reverse operation of {@link #unzipResults(Object)} to {@code results}. + * Called after successful request and joining of {@link SubResult}s. * - * @param results + * @param result * result of the remote procedure call. */ - void onSuccess(Map> results); + void onSuccess(Result result); /** * Called when an exception occurs. From 3a901e68e7e159425f361f1f11e954d149fbda9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 12:25:44 +0200 Subject: [PATCH 050/235] bug5259: Updated TimeRangeActionsExecutorTest to match changes in TimeRangeActionsExecutor --- .../async/TimeRangeActionsExecutorTest.java | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java index 3416ef1eda4..4d74f5f236e 100644 --- a/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java +++ b/java/com.sap.sse.gwt.test/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutorTest.java @@ -1,6 +1,7 @@ package com.sap.sse.gwt.client.async; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -54,16 +55,16 @@ public class TimeRangeActionsExecutorTest { } }, new TimeRangeAsyncCallback, Integer, String>() { @Override - public Map unzipResults(Map result) { + public Map unzipResult(Map result) { return result; } @Override - public void onSuccess(Map> results) { + public void onSuccess(Map result) { callbackHasRun.set(true); - assertEquals(2, results.size()); - for (Map.Entry> result : results.entrySet()) { - assertEquals(result.getKey().length(), result.getValue().getB().intValue()); + assertEquals(2, result.size()); + for (Map.Entry entry : result.entrySet()) { + assertEquals(entry.getKey().length(), entry.getValue().intValue()); } } @@ -76,6 +77,11 @@ public class TimeRangeActionsExecutorTest { public Integer joinSubResults(TimeRange timeRange, List> toJoin) { return toJoin.get(0).getB(); } + + @Override + public Map zipSubResults(Map subResultMap) { + return subResultMap; + } }); assertTrue(actionHasRun.get()); assertTrue(callbackHasRun.get()); @@ -105,13 +111,13 @@ public class TimeRangeActionsExecutorTest { }, new TimeRangeAsyncCallback, Integer, String>() { @Override - public Map unzipResults(Map result) { + public Map unzipResult(Map result) { return result; } @Override - public void onSuccess(Map> results) { - assertEquals(Integer.valueOf(1), results.get("x").getB()); + public void onSuccess(Map result) { + assertEquals(Integer.valueOf(1), result.get("x")); } @Override @@ -123,6 +129,11 @@ public class TimeRangeActionsExecutorTest { public Integer joinSubResults(TimeRange timeRange, List> toJoin) { return toJoin.stream().mapToInt(Pair::getB).sum(); } + + @Override + public Map zipSubResults(Map subResultMap) { + return subResultMap; + } }); // Fire off second request which requires the results from the first request but that one is still in transit final TimeRange timeRange = TimeRangeImpl.create(110_000L, 130_000L); @@ -144,17 +155,15 @@ public class TimeRangeActionsExecutorTest { }, new TimeRangeAsyncCallback, Integer, String>() { @Override - public Map unzipResults(Map result) { + public Map unzipResult(Map result) { return result; } @Override - public void onSuccess(Map> results) { - Pair result = results.get("x"); - assertNotNull(result); - assertEquals(timeRange, result.getA()); - assertEquals(Integer.valueOf(3), result.getB()); - callbackHasRun.set(true); + public void onSuccess(Map result) { + assertNotNull(result.get("x")); + assertEquals(Integer.valueOf(3), result.get("x")); + assertFalse(callbackHasRun.getAndSet(true)); } @Override @@ -166,6 +175,11 @@ public class TimeRangeActionsExecutorTest { public Integer joinSubResults(TimeRange timeRange, List> toJoin) { return toJoin.stream().mapToInt(Pair::getB).sum(); } + + @Override + public Map zipSubResults(Map subResultMap) { + return subResultMap; + } }); // After the second request has finished the first one now gets answered Map resultsFromFirstRequest = new HashMap<>(); From c819aa1d46522373d145da0412e098cb2d37aeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 15:54:48 +0200 Subject: [PATCH 051/235] bug5259: Updated documentation for TimeRangeResultCache.java --- .../client/async/TimeRangeResultCache.java | 169 ++++++++++++------ 1 file changed, 118 insertions(+), 51 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 8fb27c3cc63..04c654adb8f 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -18,10 +18,9 @@ import com.sap.sse.common.Util.Pair; * results. In the context of a {@link TimeRangeActionsExecutor} such a cache is responsible for managing the requests * for a single {@code Key}, such as a competitor or a boat class. *

- * + * * Before making a request for a time range, the executor has to call - * {@link #trimAndRegisterRequest(TimeRange, boolean)}. When {@code false} is used for the {@code forceTimeRange} - * parameter, this cache will + * {@link #trimAndRegisterRequest(TimeRange, boolean)}. * * @param * type of the results of the remote calls which are to be cached @@ -40,14 +39,18 @@ public class TimeRangeResultCache { * trimming. *

* - * This request keeps track of whether it {@link #hasResult() has received a result}. It furthermore manages a set - * of {@link AsyncCallback} callbacks that will be triggered exactly once when a result has been - * {@link #setResult(Pair) received} for this request. + * Dependent requests are represented as a Petri Net. {@link Request}s are vertices and {@link #childrenSet} + * and {@link #parentSet} make up doubly-linked edges. {@link Result}s are tokens which get handled by a transition + * function implemented in {@link #notifyActionSuccessIfHasAllResults()}. *

* - * Dependent requests are represented as a petri net. {@link Request}s are vertices and {@link #childrenSet} and - * {@link #parentSet} make up doubly-linked edges. {@link Result}s are tokens which get handled by a transition - * function implemented in {@link #notifyActionSuccessIfHasAllResults()}. + * To keep things organized a "child" {@link Request} will never act on its own but always notify its "parent" + * {@link Request}s to act on its behalf. For this reason most methods are written from the "parents" perspective. + *

+ * + * This request keeps track of whether it {@link #hasResult() has received a result}. It furthermore manages a set + * of {@link AsyncCallback} {@link #callback} that will be triggered exactly once when a result has been + * {@link #setResult(Pair) received} for this request. */ protected class Request { private final TimeRangeAsyncAction action; @@ -72,20 +75,27 @@ public class TimeRangeResultCache { /** * Obtains all other requests (excluding {@code this} request) of which all or parts of their results are * required to combine them with the results of this trimmed request into a result that reflects the response to - * the original request from which this request was derived by trimming. + * the original request {@link #action} from which this request was derived by trimming. */ public Set getChildrenSet() { return childrenSet; } /** - * Might immediately call {@link #notifyActionSuccessIfHasAllResults()}. + * Adds a {@link List} of {@link Request}s to this one indicating that this {@link Request} is dependent on + * their {@link Result}s. + *

+ * Might immediately call {@link #notifyActionSuccessIfHasAllResults()} if all children have their results. + * + * @throws IllegalStateException + * if invoked after {@link #callback} has been called. */ - public void addChildren(List requests) { + public void addChildren(List requests) throws IllegalStateException { if (callbackWasCalled) { throw new IllegalStateException("Children may not be added after results have been submitted!"); } - childrenWithoutResultCounter++; // This will later be undone and serves the purpose of delaying the call to notifyActionSuccessIfHasAllResults until all children have been added + childrenWithoutResultCounter++; // This will later be undone and serves the purpose of delaying the call to + // notifyActionSuccessIfHasAllResults until all children have been added for (Request request : requests) { assert !this.equals(request); final boolean notPresentBefore = childrenSet.add(request); @@ -98,6 +108,10 @@ public class TimeRangeResultCache { notifyActionSuccessIfHasAllResults(); } + /** + * Clears {@link #childrenSet} and evicts the removed {@link Request}s from + * {@link TimeRangeResultCache#requestCache} if they are no longer needed. + */ private void releaseChildren() { for (Request child : childrenSet) { child.removeParent(this); @@ -108,10 +122,20 @@ public class TimeRangeResultCache { childrenSet.clear(); } + /** + * Obtains all {@link Request}s that still depend on this one. Those {@link Request}s will be notified by this + * one once it receives its {@link Result}. + */ public Set getParentSet() { return parentSet; } + /** + * Adds a new parent to {@link #parentSet}. + * + * @param parent + * {@link Request} to add as a parent. Must not be {@code this}. + */ private void addParent(Request parent) { assert !this.equals(parent); final boolean notPresentBefore = parentSet.add(parent); @@ -124,31 +148,58 @@ public class TimeRangeResultCache { parentSet.remove(parent); } + /** + * This method is to be called if {@code this} {@link Request} has received its {@link Result}. This + * {@link Request} will subsequently notify its parent {@link Request}s. + *

+ * If all children have their {@link Result}s the {@link #callback} will be notified. + * + * @param result + * this {@link Request}'s {@link Result} + */ public void onSuccess(Result result) { setResult(result); notifyActionSuccessIfHasAllResults(); parentSet.forEach(parent -> parent.onChildSuccess()); } + /** + * This method is to be called if an error occurred while getting {@code this} {@link Request}'s {@link Result}. + * After calling the {@link #callback} the parents will be notified. + * + * @param caught + * {@link Throwable} that occurred + */ public void onFailure(Throwable caught) { notifyActionFailure(caught); - Set parents = new HashSet<>(parentSet); //TODO ConcurrentModificationError + HashSet parents = new HashSet<>(parentSet); // onChildFailure will remove elements from parentSet parents.forEach(parent -> parent.onChildFailure(caught)); } + /** + * To be called by a child when it receives it's {@link Result} via {@link #onSuccess(Object)}. + */ private void onChildSuccess() { childrenWithoutResultCounter--; notifyActionSuccessIfHasAllResults(); } + /** + * To be called by a child when it encounters an error and is informed of this in {@link #onFailure(Throwable)}. + * + * @param caught + * {@link Throwable} that occurred + */ private void onChildFailure(Throwable caught) { - // TODO - notifyActionFailure(caught); + notifyActionFailure(new Throwable("Child encountered an error", caught)); } + /** + * Notifies {@link #callback} once if all needed {@link Result}s are available.

+ * Implements the transition function used by this Petri Net. + */ private void notifyActionSuccessIfHasAllResults() { if (!callbackWasCalled && hasResult && childrenWithoutResultCounter <= 0) { - // TODO List> results = new ArrayList<>(childrenSet.size() + 1); results.add(new Pair<>(getTrimmedTimeRange(), getResult())); for (Request child : childrenSet) { @@ -163,8 +214,13 @@ public class TimeRangeResultCache { } } + /** + * Notifies {@link #callback} of an error that occurred. + * + * @param caught + * {@link Throwable} that was encountered. + */ private void notifyActionFailure(Throwable caught) { - //TODO if (!callbackWasCalled) { callback.onFailure(caught); callbackWasCalled = true; @@ -175,46 +231,68 @@ public class TimeRangeResultCache { } } + /** + * Determines if this {@link Request} can be evicted from the cache. + * @return {@code true} if it can be evicted + */ protected boolean canBeEvicted() { return callbackWasCalled && parentSet.isEmpty(); } + /** + * Obtains the {@link Result} of this {@link Request}. + * @return {@code null} if {@link #hasResult()} is {@code false} + */ public Result getResult() { return result; } /** * Sets the result for this request's total time range; {@link #hasResult()} will return {@code true} after this - * method returns, and {@link #getResult()} will return {@code result} from then on. All callbacks that were - * registered using {@link #registerOnResultCallback(AsyncCallback)} up to this time will be - * {@link AsyncCallback#onSuccess(Object) notified}. + * method returns, and {@link #getResult()} will return {@code result} from then on. */ private void setResult(Result result) { this.hasResult = true; this.result = result; } + /** + * @return {@code true} if a {@link Result} has been set. + */ public boolean hasResult() { return hasResult; } + /** + * Obtains the {@link TimeRange} originally requested by the {@link #action}. + */ public TimeRange getActionTimeRange() { return actionTimeRange; } + /** + * Obtains the {@link TimeRange} that {@link #getActionTimeRange()} was trimmed to due to other {@link Request}s + * in cache. + */ public TimeRange getTrimmedTimeRange() { return trimmedTimeRange; } + /** + * Sets the trimmed {@link TimeRange}. + * + * @param timeRange + * trimmed {@link TimeRange} + */ public void setTrimmedTimeRange(TimeRange timeRange) { this.trimmedTimeRange = timeRange; } } /** - * The size-limited {@link Request} cache. The keys are the time ranges that may have resulted from trimming and - * represent those time ranges for which requests are effectively made. The corresponding value {@link Request} - * object captures {@link Request#getChildrenSet() requests on which the request depends} after having been trimmed. + * The {@link Request} cache. The keys are the actions for which requests are effectively made. The corresponding + * value {@link Request} object captures {@link Request#getChildrenSet() requests on which the request depends} + * after having been trimmed. */ private final Map, Request> requestCache = new HashMap<>(CACHE_SIZE); @@ -236,9 +314,13 @@ public class TimeRangeResultCache { * to trim subsequent requests. * * @param toTrim - * {@link TimeRange} to request. + * {@link TimeRange} to request * @param forceTimeRange - * if {@code false} the request will be optimized with cached results. + * if {@code false} the request will be optimized with cached results + * @param action + * {@link TimeRangeAsyncAction} that this request belongs to + * @param callback + * {@link AsyncCallback} which will be called exactly once when all needed {@link Result}s are ready * @return {@link TimeRange} to effectively request (potentially trimmed from {@code toTrim}) or {@code null} if no * request is to be made since the results are cached. */ @@ -259,21 +341,10 @@ public class TimeRangeResultCache { * Registers that a request has returned with a result and collects all other needed cached results that are needed * to construct a complete, time-contiguous {@link Result}. * - * @param effectiveRequestTimeRange - * {@link TimeRange} that the request that now returned a result was trimmed to. + * @param action + * {@link TimeRangeAsyncAction} that the request corresponds to * @param result - * {@link Result} of the trimmed request; this result will be cached, keyed by its trimmed - * {@code timeRange} - * @param callbackIfResultsAreMissing - * {@link AsyncCallback}{@code } to call {@link AsyncCallback#onSuccess(Object)} on once a request - * this one depends on and which was still in transit returns. Note that this then does not imply that - * all requests needed to fulfill the original request have delivered their result yet; the same may - * happen again for another dependency when trying again later. - * @return {@link List} of {@link TimeRange}, {@link Result} {@link Pair}s that contain all data needed to construct - * a time-continuous {@link Result} object, or {@code null} if not all results on which the request for - * {@code effectiveRequestTimeRange} depended have been received yet; in this case the - * {@code callbackIfResultsAreMissing} will later receive a callback if one (but maybe not all) of those - * results has been received. + * {@link Result} of the trimmed request; this result will be cached */ public void registerResult(TimeRangeAsyncAction action, Result result) { Request request = requestCache.get(action); @@ -286,8 +357,10 @@ public class TimeRangeResultCache { /** * Removes the failed request from cache. * - * @param timeRange - * effective {@link TimeRange} of failed request (result of potential trimming) + * @param action + * corresponding {@link TimeRangeAsyncAction} + * @param cause + * {@link Throwable} that occurred */ public void registerFailure(TimeRangeAsyncAction action, Throwable cause) { Request request = requestCache.get(action); @@ -300,21 +373,15 @@ public class TimeRangeResultCache { * Trims a request based on cached results, returning a potentially trimmed time range that reflects which part(s) * is/are missing from the cache. For simplicity {@code toTrim} will currently not be split up into multiple * TimeRanges if a part in the middle is cached. - *

- * - * * - * @param toTrim - * {@link TimeRange} to trim. * @param request - * {@link Request} to attach dependencies to that reflect of which other requests the results are - * required in order to fulfill the request for the complete {@code toTrim} time range - * @param rangesToTrimWith - * {@link TimeRange}s that {@code toTrim} will be trimmed against. + * {@link Request} to trim and attach dependencies to that reflect of which other requests the results + * are required in order to fulfill the request * @return {@link TimeRange} which to request because it is not covered by {@code rangesToTrimWith} or {@code null} * if no request to the server is to be made. */ private TimeRange trimTimeRangeAndAttachDeps(Request request) { + //TODO There is a lot of potential for improvements here TimeRange toTrim = request.getActionTimeRange(); List rangesToTrimWithAsList = new LinkedList<>(); //TODO Profile against ArrayList (swap and remove) requestCache.values().forEach(rangesToTrimWithAsList::add); From 688e39827ba67ecdc46be5e41d18a7a46c2f136c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 16:29:46 +0200 Subject: [PATCH 052/235] bug5259: Updated documentation for TimeRangeActionsExecutor.java --- .../async/TimeRangeActionsExecutor.java | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java index c3515402c0e..10f892b5c55 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeActionsExecutor.java @@ -60,6 +60,7 @@ public class TimeRangeActionsExecutor { private final class ExecutorCallback implements AsyncCallback { private final TimeRangeAsyncAction action; private final TimeRangeAsyncCallback callback; + private boolean callbackWasCalled = false; private final Map requestedTimeRangeMap; private final Map>> subResultsMap; @@ -87,23 +88,45 @@ public class TimeRangeActionsExecutor { @Override public void onFailure(Throwable caught) { requestedTimeRangeMap.keySet().forEach(key -> getSubResultCache(key).registerFailure(action, caught)); - callback.onFailure(caught); + if (!callbackWasCalled) { + callback.onFailure(caught); + callbackWasCalled = true; + } } + /** + * Called by a corresponding {@link TimeRangeResultCache#Request} once it has collected all needed + * {@link SubResult}s. + * + * @param key + * {@link Key} of the {@link TimeRangeResultCache#Request} + * @param results + * {@link TimeRange} and {@link SubResult} {@link Pair}s that cover the requested {@link TimeRange} + */ public void onSubResultSuccess(Key key, List> results) { subResultsMap.put(key, results); - if (subResultsMap.size() == requestedTimeRangeMap.size()) { + if (!callbackWasCalled && subResultsMap.size() == requestedTimeRangeMap.size()) { final Map unzippedResult = new HashMap<>(subResultsMap.size()); for (Map.Entry>> entry : subResultsMap.entrySet()) { TimeRange requestedTimeRange = requestedTimeRangeMap.get(entry.getKey()); unzippedResult.put(entry.getKey(), callback.joinSubResults(requestedTimeRange, entry.getValue())); } callback.onSuccess(callback.zipSubResults(unzippedResult)); + callbackWasCalled = true; } } + /** + * Called by a corresponding {@link TimeRangeResultCache#Request} once an error occurs. + * + * @param caught + * {@link Throwable} that occurred + */ public void onSubResultFailure(Throwable caught) { - //TODO + if (!callbackWasCalled) { + callback.onFailure(caught); + callbackWasCalled = true; + } } } From 97f760f0022c8d5ed8051f797392f656dc0d4517 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Hessenm=C3=BCller?= Date: Fri, 17 Jul 2020 16:43:52 +0200 Subject: [PATCH 053/235] bug5259: Refactored duplicate code in TimeRangeResultCache --- .../client/async/TimeRangeResultCache.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java index 04c654adb8f..319147323aa 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/async/TimeRangeResultCache.java @@ -207,10 +207,7 @@ public class TimeRangeResultCache { } callback.onSuccess(results); callbackWasCalled = true; - releaseChildren(); - if (canBeEvicted()) { - TimeRangeResultCache.this.evictRequestFromCache(this); - } + releaseChildrenAndEvictSelf(); } } @@ -224,10 +221,7 @@ public class TimeRangeResultCache { if (!callbackWasCalled) { callback.onFailure(caught); callbackWasCalled = true; - releaseChildren(); - if (canBeEvicted()) { - TimeRangeResultCache.this.evictRequestFromCache(this); - } + releaseChildrenAndEvictSelf(); } } @@ -239,6 +233,16 @@ public class TimeRangeResultCache { return callbackWasCalled && parentSet.isEmpty(); } + /** + * Calls {@link #releaseChildren()} and then removes itself from the cache if no longer needed. + */ + private void releaseChildrenAndEvictSelf() { + releaseChildren(); + if (canBeEvicted()) { + TimeRangeResultCache.this.evictRequestFromCache(this); + } + } + /** * Obtains the {@link Result} of this {@link Request}. * @return {@code null} if {@link #hasResult()} is {@code false} From 9b92f7d7cb4c342abb0a3ec8e3dc95366bac6f3a Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Mon, 20 Jul 2020 12:29:31 +0200 Subject: [PATCH 054/235] Revert "bug5312: using thumbnail image instead of logo" This reverts commit c3b5f992d28fbe963987cfba19361117478a337b. --- .../gwt/home/desktop/partials/eventheader/EventHeader.java | 2 +- .../gwt/home/desktop/partials/seriesheader/SeriesHeader.java | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java index 65736385d73..7ec0671fc6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java @@ -93,7 +93,7 @@ public class EventHeader extends Composite { @Override public String getImageUrl() { - return event.getThumbnailImageURL(); + return LogoUtil.resolveLogoURL(event); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java index 663705b3e9b..85b9eee6066 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java @@ -12,7 +12,6 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.event.EventAndLeaderboardReferenceWithStateDTO; -import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO; import com.sap.sailing.gwt.home.communication.event.EventState; import com.sap.sailing.gwt.home.communication.fakeseries.EventSeriesViewDTO; import com.sap.sailing.gwt.home.desktop.partials.sharing.SharingButtons; @@ -23,6 +22,7 @@ import com.sap.sailing.gwt.home.desktop.places.fakeseries.SeriesView.Presenter; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources; import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil; +import com.sap.sailing.gwt.home.shared.utils.LogoUtil; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.LinkUtil; @@ -69,8 +69,7 @@ public class SeriesHeader extends Composite { @Override public String getImageUrl() { - EventMetadataDTO mostRecentEventInSeries = series.getEventsDescending().get(0); - return mostRecentEventInSeries.getThumbnailImageURL(); + return LogoUtil.resolveLogoURL(series); } @Override From 092b5a1f2a1b8ffa00211ad76ddaefb960915e2f Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Mon, 20 Jul 2020 12:29:34 +0200 Subject: [PATCH 055/235] Revert "bug5312: including required meta tag og:url" This reverts commit d5e153ebcd6caac42e71cf8898f851c369654e28. --- .../gwt/home/desktop/partials/sharing/SharingButtons.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java index b2bfc00467d..c4a165f7d0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java @@ -27,7 +27,6 @@ public class SharingButtons extends Composite { private static final String OG_TYPE = "og:type"; private static final String OG_TITLE = "og:title"; private static final String OG_DESCRIPTION = "og:description"; - private static final String OG_URL = "og:url"; private static SharingButtonsUiBinder uiBinder = GWT.create(SharingButtonsUiBinder.class); interface SharingButtonsUiBinder extends UiBinder { @@ -82,10 +81,6 @@ public class SharingButtons extends Composite { imageMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_IMAGE); imageMetaTag.setContent(provider.getImageUrl()); metaElements.add(imageMetaTag); - final MetaElement urlMetaTag = findOrCreateMetaElement(OG_URL, document); - urlMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_URL); - urlMetaTag.setContent(Window.Location.getHref()); - metaElements.add(urlMetaTag); appendMetaElements(metaElements); } From 8c446aeda83d12e93c1c7bda80792638da38d908 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Mon, 20 Jul 2020 12:29:37 +0200 Subject: [PATCH 056/235] Revert "bug5312: Introducing open graph meta tags for desktop event header" This reverts commit 652b050155b7d40cb16d8cf8f0914e16bae0cae7. --- .../partials/eventheader/EventHeader.java | 17 +----- .../partials/seriesheader/SeriesHeader.java | 16 ----- .../partials/sharing/SharingButtons.java | 59 +------------------ .../sharing/SharingMetadataProvider.java | 7 +-- .../partials/eventheader/EventHeader.java | 2 +- .../partials/seriesheader/SeriesHeader.java | 2 +- .../gwt/home/shared/utils/LogoUtil.java | 8 +-- 7 files changed, 8 insertions(+), 103 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java index 7ec0671fc6c..12f94026c00 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/EventHeader.java @@ -90,26 +90,11 @@ public class EventHeader extends Composite { String dateString = EventDatesFormatterUtil.formatDateRangeWithYear(event.getStartDate(), event.getEndDate()); return StringMessages.INSTANCE.eventSharingLongText(event.getDisplayName(), event.getLocationOrVenue(), dateString, url); } - - @Override - public String getImageUrl() { - return LogoUtil.resolveLogoURL(event); - } - - @Override - public String getTitle() { - return event.getDisplayName(); - } - - @Override - public String getDescription() { - return event.getDescription() != null ? event.getDescription() : getShortText(); - } }); } private void initFields() { - LogoUtil.setLogo(eventLogo, event); + LogoUtil.setEventLogo(eventLogo, event); if (presenter.showRegattaMetadata()) { presenter.getCurrentEventNavigation().configureAnchorElement(eventLogoAnchorUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java index 85b9eee6066..e9035cd9f4c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/seriesheader/SeriesHeader.java @@ -22,7 +22,6 @@ import com.sap.sailing.gwt.home.desktop.places.fakeseries.SeriesView.Presenter; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; import com.sap.sailing.gwt.home.shared.resources.SharedHomeResources; import com.sap.sailing.gwt.home.shared.utils.LabelTypeUtil; -import com.sap.sailing.gwt.home.shared.utils.LogoUtil; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sse.gwt.client.LinkUtil; @@ -66,21 +65,6 @@ public class SeriesHeader extends Composite { public String getLongText(String url) { return StringMessages.INSTANCE.seriesSharingLongText(series.getDisplayName(), url); } - - @Override - public String getImageUrl() { - return LogoUtil.resolveLogoURL(series); - } - - @Override - public String getTitle() { - return series.getDisplayName(); - } - - @Override - public String getDescription() { - return getShortText(); - } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java index c4a165f7d0f..f5a98882a8c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingButtons.java @@ -2,14 +2,8 @@ package com.sap.sailing.gwt.home.desktop.partials.sharing; import static com.google.gwt.dom.client.Style.Display.NONE; -import java.util.ArrayList; - import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.MetaElement; -import com.google.gwt.dom.client.NodeList; import com.google.gwt.http.client.UrlBuilder; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; @@ -21,12 +15,6 @@ import com.sap.sse.gwt.shared.ClientConfiguration; public class SharingButtons extends Composite { - private static final String RDFA_ATTRIBUTE_IDENTIFIER = "property"; - private static final String DEFAULT_TYPE_PROPERTY_VALUE = "website"; - private static final String OG_IMAGE = "og:image"; - private static final String OG_TYPE = "og:type"; - private static final String OG_TITLE = "og:title"; - private static final String OG_DESCRIPTION = "og:description"; private static SharingButtonsUiBinder uiBinder = GWT.create(SharingButtonsUiBinder.class); interface SharingButtonsUiBinder extends UiBinder { @@ -49,9 +37,10 @@ public class SharingButtons extends Composite { if (!ClientConfiguration.getInstance().isBrandingActive()) { return; } + String shortText = provider.getShortText(); String longText = provider.getLongText(Window.Location.getHref()); - appendOpenGraphMetaTags(provider); + UrlBuilder mailtoLink = new UrlBuilder().setProtocol("mailto").setParameter("subject", shortText).setParameter("body", longText); // URLBuilder encodes spaces in parameters using "+" instead of "%20". This causes problems in Mail programs that do not decode "+" as space. mail.setHref(mailtoLink.buildString().replace("+", "%20")); @@ -61,48 +50,4 @@ public class SharingButtons extends Composite { .setPath("sharer/sharer.php").setParameter("u", Window.Location.getHref()); facebook.setHref(facebookLink.buildString()); } - - private void appendOpenGraphMetaTags(SharingMetadataProvider provider) { - final Document document = Document.get(); - final ArrayList metaElements = new ArrayList<>(); - final MetaElement descriptionMetaTag = findOrCreateMetaElement(OG_DESCRIPTION, document); - descriptionMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_DESCRIPTION); - descriptionMetaTag.setContent(provider.getDescription()); - metaElements.add(descriptionMetaTag); - final MetaElement titleMetaTag = findOrCreateMetaElement(OG_TITLE, document); - titleMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_TITLE); - titleMetaTag.setContent(provider.getTitle()); - metaElements.add(titleMetaTag); - final MetaElement typeMetaTag = findOrCreateMetaElement(OG_TYPE, document); - typeMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_TYPE); - typeMetaTag.setContent(DEFAULT_TYPE_PROPERTY_VALUE); - metaElements.add(typeMetaTag); - final MetaElement imageMetaTag = findOrCreateMetaElement(OG_IMAGE, document); - imageMetaTag.setAttribute(RDFA_ATTRIBUTE_IDENTIFIER, OG_IMAGE); - imageMetaTag.setContent(provider.getImageUrl()); - metaElements.add(imageMetaTag); - appendMetaElements(metaElements); - } - - private MetaElement findOrCreateMetaElement(final String tagId, final Document document) { - final Element elementById = document.getElementById(tagId); - MetaElement metaElement; - if(elementById != null && elementById instanceof MetaElement) { - metaElement = (MetaElement) elementById; - }else { - metaElement = document.createMetaElement(); - } - metaElement.setId(tagId); - metaElement.removeFromParent(); - return metaElement; - } - - private void appendMetaElements(final ArrayList metaElements) { - final Document document = Document.get(); - final NodeList nodes = document.getElementsByTagName("head"); - final Element head = (Element) nodes.getItem(0); - for(final MetaElement metaTag : metaElements) { - head.appendChild(metaTag); - } - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java index 3fc32acc602..d1d3d03fb84 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/sharing/SharingMetadataProvider.java @@ -2,13 +2,8 @@ package com.sap.sailing.gwt.home.desktop.partials.sharing; public interface SharingMetadataProvider { - String getTitle(); - - String getDescription(); - String getShortText(); String getLongText(String url); - - String getImageUrl(); + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java index f6383642490..d08bf84ce83 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventheader/EventHeader.java @@ -47,7 +47,7 @@ public class EventHeader extends Composite { nameSeparatorUi.removeFromParent(); } LabelTypeUtil.renderLabelType(eventStateUi, event.getState().getStateMarker()); - LogoUtil.setLogo(eventLogoUi, event); + LogoUtil.setEventLogo(eventLogoUi, event); if (logoNavigation != null) { logoNavigation.configureAnchorElement(eventLogoUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java index cdbf3f5d09a..0a437bbd460 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/seriesheader/SeriesHeader.java @@ -37,7 +37,7 @@ public class SeriesHeader extends Composite { } private void setUiFieldValues(EventSeriesViewDTO event, PlaceNavigation logoNavigation) { - LogoUtil.setLogo(eventLogoUi, event); + LogoUtil.setEventLogo(eventLogoUi, event); if (logoNavigation != null) { logoNavigation.configureAnchorElement(eventLogoUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java index ffa2c46b428..8af83592426 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/utils/LogoUtil.java @@ -20,15 +20,11 @@ public abstract class LogoUtil { * @param eventLogoUi {@link Element} where the logo is set * @param event {@link EventViewDTO} providing data */ - public static void setLogo(Element eventLogoUi, HasLogo event) { - String logoUrl = resolveLogoURL(event); + public static void setEventLogo(Element eventLogoUi, HasLogo event) { + String logoUrl = event.getLogoImage() != null ? event.getLogoImage().getSourceRef() : DEFAULT_EVENT_LOGO.asString(); setLogoAndTitle(eventLogoUi, logoUrl, event.getDisplayName()); } - public static String resolveLogoURL(HasLogo event) { - return event.getLogoImage() != null ? event.getLogoImage().getSourceRef() : DEFAULT_EVENT_LOGO.asString(); - } - private static void setLogoAndTitle(Element logoUi, String logoUrl, String logoTitle) { logoUi.getStyle().setBackgroundImage("url(" + logoUrl + ")"); logoUi.setTitle(logoTitle); From 1040c4d8701521d4650ba9250bbebf1bf00c93b9 Mon Sep 17 00:00:00 2001 From: Dennis Aulenbacher Date: Mon, 20 Jul 2020 15:54:38 +0200 Subject: [PATCH 057/235] bug5312: added static default open graph meta tags to relevant .html pages --- java/com.sap.sailing.gwt.ui/AdminConsole.html | 5 ++++- java/com.sap.sailing.gwt.ui/AutoPlay.html | 4 ++++ java/com.sap.sailing.gwt.ui/DataMining.html | 4 ++++ java/com.sap.sailing.gwt.ui/Home.html | 4 ++++ java/com.sap.sailing.gwt.ui/HomeDesktop.html | 4 ++++ java/com.sap.sailing.gwt.ui/HomeMobile.html | 4 ++++ java/com.sap.sailing.gwt.ui/Leaderboard.html | 4 ++++ java/com.sap.sailing.gwt.ui/LeaderboardEditing.html | 4 ++++ java/com.sap.sailing.gwt.ui/PairingList.html | 4 ++++ java/com.sap.sailing.gwt.ui/RaceBoard.html | 4 ++++ java/com.sap.sailing.gwt.ui/RegattaOverview.html | 4 ++++ java/com.sap.sailing.gwt.ui/Simulator.html | 4 ++++ java/com.sap.sailing.gwt.ui/Spectator.html | 4 ++++ 13 files changed, 52 insertions(+), 1 deletion(-) diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.html b/java/com.sap.sailing.gwt.ui/AdminConsole.html index db0e90a4034..3e8b9d5ac8b 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.html +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.html @@ -8,7 +8,10 @@ - + + + + diff --git a/java/com.sap.sailing.gwt.ui/AutoPlay.html b/java/com.sap.sailing.gwt.ui/AutoPlay.html index 622fe347c41..e6937596a5a 100644 --- a/java/com.sap.sailing.gwt.ui/AutoPlay.html +++ b/java/com.sap.sailing.gwt.ui/AutoPlay.html @@ -9,6 +9,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/DataMining.html b/java/com.sap.sailing.gwt.ui/DataMining.html index cea6a7dfbbd..19acc9062c9 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.html +++ b/java/com.sap.sailing.gwt.ui/DataMining.html @@ -7,6 +7,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/Home.html b/java/com.sap.sailing.gwt.ui/Home.html index 3014189d5ef..30b5efcfd77 100644 --- a/java/com.sap.sailing.gwt.ui/Home.html +++ b/java/com.sap.sailing.gwt.ui/Home.html @@ -3,6 +3,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/HomeDesktop.html b/java/com.sap.sailing.gwt.ui/HomeDesktop.html index baad2fbccaa..e391a5d2552 100644 --- a/java/com.sap.sailing.gwt.ui/HomeDesktop.html +++ b/java/com.sap.sailing.gwt.ui/HomeDesktop.html @@ -2,6 +2,10 @@ + + + + ${SAP}Sailing - Homepage diff --git a/java/com.sap.sailing.gwt.ui/HomeMobile.html b/java/com.sap.sailing.gwt.ui/HomeMobile.html index baad2fbccaa..e391a5d2552 100644 --- a/java/com.sap.sailing.gwt.ui/HomeMobile.html +++ b/java/com.sap.sailing.gwt.ui/HomeMobile.html @@ -2,6 +2,10 @@ + + + + ${SAP}Sailing - Homepage diff --git a/java/com.sap.sailing.gwt.ui/Leaderboard.html b/java/com.sap.sailing.gwt.ui/Leaderboard.html index ada1fcb3db8..6d4e21a1e65 100755 --- a/java/com.sap.sailing.gwt.ui/Leaderboard.html +++ b/java/com.sap.sailing.gwt.ui/Leaderboard.html @@ -10,6 +10,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html b/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html index 00cd76c45d2..8dc3be6b458 100755 --- a/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html +++ b/java/com.sap.sailing.gwt.ui/LeaderboardEditing.html @@ -8,6 +8,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/PairingList.html b/java/com.sap.sailing.gwt.ui/PairingList.html index f0812a97fdd..cda00f752df 100644 --- a/java/com.sap.sailing.gwt.ui/PairingList.html +++ b/java/com.sap.sailing.gwt.ui/PairingList.html @@ -10,6 +10,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/RaceBoard.html b/java/com.sap.sailing.gwt.ui/RaceBoard.html index de796021d04..cabf327b97f 100644 --- a/java/com.sap.sailing.gwt.ui/RaceBoard.html +++ b/java/com.sap.sailing.gwt.ui/RaceBoard.html @@ -10,6 +10,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/RegattaOverview.html b/java/com.sap.sailing.gwt.ui/RegattaOverview.html index 7197ecb9282..a587269a4b8 100644 --- a/java/com.sap.sailing.gwt.ui/RegattaOverview.html +++ b/java/com.sap.sailing.gwt.ui/RegattaOverview.html @@ -8,6 +8,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/Simulator.html b/java/com.sap.sailing.gwt.ui/Simulator.html index 30f341ff11c..eaa302ad4f5 100644 --- a/java/com.sap.sailing.gwt.ui/Simulator.html +++ b/java/com.sap.sailing.gwt.ui/Simulator.html @@ -7,6 +7,10 @@ + + + + diff --git a/java/com.sap.sailing.gwt.ui/Spectator.html b/java/com.sap.sailing.gwt.ui/Spectator.html index ce1142917d6..6c8c573349c 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.html +++ b/java/com.sap.sailing.gwt.ui/Spectator.html @@ -12,6 +12,10 @@ + + + + From 1c771446584828ef703f8bae35bf0b009797b22c Mon Sep 17 00:00:00 2001 From: Georg Herdt Date: Mon, 20 Jul 2020 16:25:17 +0200 Subject: [PATCH 058/235] schedule deferred, enforce redraw bug 5232 --- .../home/desktop/partials/header/Header.java | 43 ++++++++++++++++--- .../desktop/partials/header/Header.ui.xml | 2 +- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java index 4deb8265b72..d24dbb31605 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.java @@ -13,10 +13,12 @@ import java.util.Map; import java.util.logging.Logger; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Text; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyPressEvent; @@ -80,6 +82,7 @@ public class Header extends Composite implements HeaderConstants { @UiField Button searchButton; @UiField Anchor headerNavigationIcon; @UiField Element headerNavigationDropDownMenuContainer; + @UiField Element rightMenuPanel; @UiField Anchor usermenu; @@ -101,11 +104,13 @@ public class Header extends Composite implements HeaderConstants { private final Map menuToDropDownItemMap; private final Anchor headerNavigationIcon; private final DropdownHandler dropdownHandler; + private final Element rightMenuPanel; - public MenuItemVisibilityHandler(Map menuToDropDownItemMap, DropdownHandler dropdownHandler, Anchor headerNavigationIcon) { + public MenuItemVisibilityHandler(Map menuToDropDownItemMap, DropdownHandler dropdownHandler, Anchor headerNavigationIcon, Element rightMenuPanel) { this.dropdownHandler = dropdownHandler; this.headerNavigationIcon = headerNavigationIcon; this.menuToDropDownItemMap = menuToDropDownItemMap; + this.rightMenuPanel = rightMenuPanel; } @Override @@ -117,8 +122,10 @@ public class Header extends Composite implements HeaderConstants { int offsetTop = anchor.getElement().getOffsetTop(); return offsetTop < visibiltyThreshold; } + public void refreshVisibility() { + LOG.info("refesh visibility"); int noOfVisibleItems = 0; for (Map.Entry item : menuToDropDownItemMap.entrySet()) { Anchor menuAnchor = item.getKey(); @@ -136,7 +143,6 @@ public class Header extends Composite implements HeaderConstants { public void refreshVisibility(int delayMillis) { new Timer() { - @Override public void run() { refreshVisibility(); @@ -144,6 +150,26 @@ public class Header extends Composite implements HeaderConstants { }.schedule(delayMillis); } + public void refreshVisibilityDeferred() { + Anchor anchor = (Anchor)menuToDropDownItemMap.values().toArray()[0]; + Element element = anchor.getElement(); + redraw(element); + Scheduler.get().scheduleDeferred(this::refreshVisibility); + } + + private void redraw(Element element) { + Text dummyText = element.getOwnerDocument().createTextNode(" "); + element.appendChild(dummyText); + String strDisplay = element.getStyle().getDisplay(); + Display display = strDisplay == null || strDisplay.equals("") ? Display.INITIAL : Display.valueOf(strDisplay.toUpperCase()); + element.getStyle().setDisplay(Display.NONE); + + Scheduler.get().scheduleDeferred(() -> { + element.removeChild(dummyText); + element.getStyle().setDisplay(display); + }); + } + } private static HeaderUiBinder uiBinder = GWT.create(HeaderUiBinder.class); @@ -161,11 +187,16 @@ public class Header extends Composite implements HeaderConstants { menuToDropDownItemMap.put(solutionsPageLink, solutionsPageLinkMenu); menuToDropDownItemMap.put(adminConsolePageLink, adminConsolePageLinkMenu); menuToDropDownItemMap.put(dataMiningPageLink, dataMiningPageLinkMenu); + + startPageLink.getElement().setId("startPageLink"); + eventsPageLink.getElement().setId("eventsPageLink"); + solutionsPageLink.getElement().setId("solutionsPageLink"); + adminConsolePageLink.getElement().setId("adminConsolePageLink"); + dataMiningPageLink.getElement().setId("dataMiningPageLink"); headerNavigationDropDownMenuContainer.getStyle().setDisplay(Display.NONE); final DropdownHandler dropdownHandler = new DropdownHandler(headerNavigationIcon, headerNavigationDropDownMenuContainer); - menuItemVisibilityHandler = new MenuItemVisibilityHandler(menuToDropDownItemMap, dropdownHandler, headerNavigationIcon); + menuItemVisibilityHandler = new MenuItemVisibilityHandler(menuToDropDownItemMap, dropdownHandler, headerNavigationIcon, rightMenuPanel); Window.addResizeHandler(menuItemVisibilityHandler); - links = Arrays.asList(new Anchor[] { startPageLink, eventsPageLink, solutionsPageLink, adminConsolePageLink, dataMiningPageLink }); homeNavigation = navigator.getHomeNavigation(); eventsNavigation = navigator.getEventsNavigation(); @@ -206,7 +237,7 @@ public class Header extends Composite implements HeaderConstants { } else { dataMiningPageLink.getElement().getStyle().setDisplay(Display.NONE); } - menuItemVisibilityHandler.refreshVisibility(); + menuItemVisibilityHandler.refreshVisibilityDeferred(); }); searchText.getElement().setAttribute("placeholder", StringMessages.INSTANCE.headerSearchPlaceholder()); searchText.addFocusHandler((focusEvent) -> menuItemVisibilityHandler.refreshVisibility(370)); @@ -244,7 +275,7 @@ public class Header extends Composite implements HeaderConstants { solutionsPageLinkMenu.addClickHandler(this::goToSolutions); //remaining entries please see at registration for AuthenticationContextEvent // refresh after UI has been put into place - menuItemVisibilityHandler.refreshVisibility(100); + menuItemVisibilityHandler.refreshVisibilityDeferred(); } @UiHandler("startPageLink") diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml index 580078bcc64..4e697992911 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/header/Header.ui.xml @@ -29,7 +29,7 @@ -