diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java new file mode 100755 index 00000000000..fb1182e2ec9 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ImageSize.java @@ -0,0 +1,8 @@ +package com.sap.sailing.domain.common; + +import java.io.Serializable; + +public interface ImageSize extends Serializable { + int getWidth(); + int getHeight(); +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java new file mode 100755 index 00000000000..a26a7fb4bcf --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/ImageSizeImpl.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.common.impl; + +import com.sap.sailing.domain.common.ImageSize; + +public class ImageSizeImpl implements ImageSize { + private static final long serialVersionUID = 1170701774852068780L; + private int width; + private int height; + ImageSizeImpl() {} // for GWT serialization + public ImageSizeImpl(int width, int height) { + super(); + this.width = width; + this.height = height; + } + @Override + public int getWidth() { + return width; + } + @Override + public int getHeight() { + return height; + } + @Override + public String toString() { + return "("+getWidth()+"x"+getHeight()+")"; + } +} + diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java index 1d8db40138f..db395fc2984 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/EventBase.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.base; import java.net.URL; import java.util.concurrent.ExecutionException; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.Renamable; import com.sap.sailing.domain.common.TimePoint; import com.sap.sailing.domain.common.WithID; @@ -121,11 +122,6 @@ public interface EventBase extends Named, WithDescription, Renamable, WithID { Iterable getLeaderboardGroups(); - public static interface ImageSize { - int getWidth(); - int getHeight(); - } - /** * For the images references by the image URLs in {@link #getImageURLs()}, {@link #getSponsorImageURLs()} and {@link #getLogoImageURL()} * determines the image dimensions. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java index 8187ee6ab9c..acb5610715d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/EventBaseImpl.java @@ -221,26 +221,4 @@ public abstract class EventBaseImpl implements EventBase { this.officialWebsiteURL = officialWebsiteURL; } - public static class ImageSizeImpl implements ImageSize { - private final int width; - private final int height; - public ImageSizeImpl(int width, int height) { - super(); - this.width = width; - this.height = height; - } - @Override - public int getWidth() { - return width; - } - @Override - public int getHeight() { - return height; - } - @Override - public String toString() { - return "("+getWidth()+"x"+getHeight()+")"; - } - } - } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java index d7908eb363e..5f7d1b2a0da 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/StrippedEventImpl.java @@ -9,6 +9,7 @@ import java.util.concurrent.ExecutionException; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.TimePoint; /** @@ -33,7 +34,7 @@ public class StrippedEventImpl extends EventBaseImpl { boolean isPublic, UUID id, Iterable leaderboardGroups) { super(name, startDate, endDate, venue, isPublic, id); this.leaderboardGroups = leaderboardGroups; - this.imageSizes = new HashMap(); + this.imageSizes = new HashMap(); } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java index c2dd2e478d4..1abf52edd38 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java @@ -4,6 +4,7 @@ import java.net.URL; import java.util.UUID; import java.util.concurrent.ExecutionException; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java index 91252a33d96..7664b9c0f54 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java @@ -24,7 +24,9 @@ import javax.imageio.stream.ImageInputStream; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sse.common.Util; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java index f26e7e26aaf..5541aaaa286 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/start/StartActivity.java @@ -1,15 +1,14 @@ package com.sap.sailing.gwt.home.client.place.start; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; -import java.util.GregorianCalendar; import java.util.List; import com.google.gwt.activity.shared.AbstractActivity; import com.google.gwt.event.shared.EventBus; +import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; @@ -50,9 +49,8 @@ public class StartActivity extends AbstractActivity { List upcomingSoonEvents = new ArrayList(); List popularEvents = new ArrayList(); Date now = new Date(); - Calendar cal = new GregorianCalendar(); - cal.setTime(now); - int currentYear = cal.get(Calendar.YEAR); + DateTimeFormat yearFetcher = DateTimeFormat.getFormat("yyyy"); + String currentYear = yearFetcher.format(now); final int MAX_STAGE_EVENTS = 10; final long FOUR_WEEK_IN_MS = 4L * (1000 * 60 * 60 * 24 * 7); @@ -62,11 +60,8 @@ public class StartActivity extends AbstractActivity { featuredEvents.add(new Pair(StageEventType.RUNNING, event)); } else if (event.startDate.after(now) && event.startDate.getTime() - now.getTime() < FOUR_WEEK_IN_MS) { upcomingSoonEvents.add(event); - } else { - cal.setTime(event.endDate); - if (event.endDate.before(now) && cal.get(Calendar.YEAR) == currentYear) { - recentEventsOfSameYear.add(event); - } + } else if (event.endDate.before(now) && yearFetcher.format(event.endDate).equals(currentYear)) { + recentEventsOfSameYear.add(event); } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 88fe862a047..bf2f79619d3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -3271,17 +3271,32 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.id = (UUID) event.getId(); eventDTO.setDescription(event.getDescription()); eventDTO.setOfficialWebsiteURL(event.getOfficialWebsiteURL() != null ? event.getOfficialWebsiteURL().toString() : null); - eventDTO.setLogoImageURL(event.getLogoImageURL() != null ? event.getLogoImageURL().toString() : null); + if (event.getLogoImageURL() == null) { + eventDTO.setLogoImageURL(null); + } else { + eventDTO.setLogoImageURL(event.getLogoImageURL().toString()); + setImageSize(event, eventDTO, event.getLogoImageURL()); + } for(URL url: event.getSponsorImageURLs()) { eventDTO.addSponsorImageURL(url.toString()); + setImageSize(event, eventDTO, url); } for(URL url: event.getImageURLs()) { eventDTO.addImageURL(url.toString()); + setImageSize(event, eventDTO, url); } for(URL url: event.getVideoURLs()) { eventDTO.addVideoURL(url.toString()); } } + + private void setImageSize(EventBase event, EventBaseDTO eventDTO, URL imageURL) { + try { + eventDTO.setImageSize(imageURL.toString(), event.getImageSize(imageURL)); + } catch (InterruptedException | ExecutionException e) { + logger.log(Level.FINE, "Was unable to obtain image size for "+imageURL+" earlier.", e); + } + } private EventDTO convertToEventDTO(Event event, boolean withStatisticalData) { EventDTO eventDTO = new EventDTO(event.getName()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java index ac70cce679d..c23f3c17a24 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/EventBaseDTO.java @@ -1,15 +1,22 @@ package com.sap.sailing.gwt.ui.shared; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.domain.common.dto.NamedDTO; +import com.sap.sse.common.Util; public class EventBaseDTO extends NamedDTO implements IsSerializable { private static final long serialVersionUID = 818666323178097939L; + private static final String STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE = "stage"; public VenueDTO venue; public Date startDate; public Date endDate; @@ -26,6 +33,11 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { /** placeholder for social media URL's -> attributes will be implemented later on */ private String facebookURL; private String twitterURL; + /** + * For the image URL keys holds the sizes of these images if known. An image size is "known" by this object if it was + * provided to the {@link #setImageSize} method. + */ + private Map imageSizes; /** * The base URL for the server instance on which the data for this event can be reached. Could be something like @@ -45,11 +57,13 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { public EventBaseDTO(List leaderboardGroups) { this.leaderboardGroups = leaderboardGroups; + this.imageSizes = new HashMap(); } public EventBaseDTO(String name, List leaderboardGroups) { super(name); this.leaderboardGroups = leaderboardGroups; + this.imageSizes = new HashMap(); } public boolean isRunning() { @@ -113,24 +127,63 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { return imageURLs; } + /** + * The stage image is determined from the {@link #imageURLs} collection by a series of heuristics and fall-back rules: + *
    + *
  1. If one or more image URLs has "stage" (ignoring case) in its name, only they are considered candidates.
  2. + *
  3. If no image URL has "stage" (ignoring case) in its name, all images from {@link #imageURLs} are considered candidates.
  4. + *
  5. From all candidates, the one with the biggest known size (determined by the product of width and height) is chosen.
  6. + *
  7. If the size isn't known for any candidate, the first candidate in {@link #imageURLs} is picked.
  8. + *
+ */ public String getStageImageURL() { - String result = null; - for(String imageUrl: imageURLs) { - // FIXME consolidate with getPhotoGalleryImageURLs, removing redundant "STAGE" code - // TODO use image size to find largest image(s) in case none is explicitly marked with "state" in its URL - if(imageUrl.contains("stage") || imageUrl.contains("STAGE")) { - result = imageUrl; - break; - } + final String result; + if (imageURLs.isEmpty()) { + result = null; + } else { + Comparator stageImageComparator = new Comparator() { + @Override + public int compare(String o1, String o2) { + final int result; + if (o1.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + result = compareBySize(o1, o2); + } else { + // o1 has stage indicator substring in its URL but o2 doesn't; o1 ranks greater + result = 1; + } + } else { + if (o2.toLowerCase().contains(STAGE_IMAGE_URL_SUBSTRING_INDICATOR_CASE_INSENSITIVE)) { + result = -1; // o1 does not and o2 does have stage indicator substring, so o2 ranks greater + } else { + // both don't have stage indicator; compare by size + result = compareBySize(o1, o2); + } + } + return result; + } + + private int compareBySize(String o1, String o2) { + final int result; + final ImageSize o1Size = getImageSize(o1); + final ImageSize o2Size = getImageSize(o2); + result = (o1Size == null ? 0 : (o1Size.getWidth() * o1Size.getHeight())) + - (o2Size == null ? 0 : (o2Size.getWidth() * o2Size.getHeight())); + return result; + } + }; + List sortedImageURLs = new ArrayList<>(imageURLs); + Collections.sort(sortedImageURLs, stageImageComparator); + result = sortedImageURLs.get(sortedImageURLs.size() - 1); } return result; } public List getPhotoGalleryImageURLs() { + String stageImageURL = getStageImageURL(); // if set, exclude stage image from photo gallery List result = new ArrayList(); - for(String imageUrl: imageURLs) { - // FIXME consolidate with getStageImageURL, removing redundant "STAGE" code - if(!imageUrl.contains("stage") && !imageUrl.contains("STAGE")) { + for (String imageUrl : imageURLs) { + if (!Util.equalsWithNull(imageUrl, stageImageURL)) { result.add(imageUrl); } } @@ -172,5 +225,21 @@ public class EventBaseDTO extends NamedDTO implements IsSerializable { public void setTwitterURL(String twitterURL) { this.twitterURL = twitterURL; } + + public void setImageSize(String imageURL, ImageSize imageSize) { + if (imageSize == null) { + imageSizes.remove(imageURL); + } else { + imageSizes.put(imageURL, imageSize); + } + } + /** + * @return the size of the image referenced by imageURL or null if that size is not known + * of the image URL is none of those known to this event, in particular neither of {@link #getImageURLs()} + * or {@link #getSponsorImageURLs()}. + */ + public ImageSize getImageSize(String imageURL) { + return imageSizes.get(imageURL); + } } diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java index 55f8935e6e7..b329b762b79 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/deserialization/impl/EventBaseJsonDeserializer.java @@ -12,8 +12,8 @@ import org.json.simple.JSONObject; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; -import com.sap.sailing.domain.base.impl.EventBaseImpl; import com.sap.sailing.domain.base.impl.StrippedEventImpl; +import com.sap.sailing.domain.common.impl.ImageSizeImpl; import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; @@ -95,7 +95,7 @@ public class EventBaseJsonDeserializer implements JsonDeserializer { try { result.setImageSize( new URL((String) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_URL)), - new EventBaseImpl.ImageSizeImpl( + new ImageSizeImpl( ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_WIDTH)).intValue(), ((Number) imageURLAndSizeJson.get(EventBaseJsonSerializer.FIELD_IMAGE_HEIGHT)).intValue())); } catch (MalformedURLException e) { diff --git a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java index 90e7a4d3b99..d5d682ce31e 100644 --- a/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization/src/com/sap/sailing/server/gateway/serialization/impl/EventBaseJsonSerializer.java @@ -9,9 +9,9 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import com.sap.sailing.domain.base.EventBase; -import com.sap.sailing.domain.base.EventBase.ImageSize; import com.sap.sailing.domain.base.LeaderboardGroupBase; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.ImageSize; import com.sap.sailing.server.gateway.serialization.JsonSerializer; public class EventBaseJsonSerializer implements JsonSerializer {