Merge branch 'master' into ubilabs--tracking_app--develop

This commit is contained in:
Bennet Brunsen
2016-04-14 11:42:41 +02:00
30 changed files with 469 additions and 230 deletions
@@ -14,7 +14,8 @@ public enum FleetColors {
RED (232, 74, 26, 0),
PURPLE (212, 28, 175, 0),
NEON_GREEN (118, 255, 33, 0),
ORANGE (255, 174, 23, 0);
ORANGE (255, 174, 23, 0),
WHITE (250, 250, 250, 0);
private Color color;
@@ -42,7 +42,9 @@ public enum MaxPointsReason {
/** Retired */
RET(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true),
/** Uniform Flag Disqualification */
UFD(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
UFD(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true),
/** Time limit Expired */
TLE(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
private final boolean discardable;
@@ -98,11 +98,9 @@ public class MultiRegattaListSteps extends Composite {
}
private String getLeaderboardButtonStyle(RegattaState regattaState) {
switch (regattaState) {
case UPCOMING: return SharedResources.INSTANCE.mainCss().buttoninactive();
case RUNNING: return SharedResources.INSTANCE.mainCss().buttonred();
default: return SharedResources.INSTANCE.mainCss().buttonprimary();
}
String liveStyle = SharedResources.INSTANCE.mainCss().buttonred();
String defaultStyle = SharedResources.INSTANCE.mainCss().buttonprimary();
return RegattaState.RUNNING == regattaState ? liveStyle : defaultStyle;
}
}
@@ -242,7 +242,7 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog<T> extends DataEn
protected void fillCourseAreaListBox(EventDTO selectedEvent) {
courseAreaListBox.addItem(stringMessages.selectCourseArea());
for (CourseAreaDTO courseArea : selectedEvent.venue.getCourseAreas()) {
courseAreaListBox.addItem(courseArea.getName());
courseAreaListBox.addItem(courseArea.getName(), courseArea.id.toString());
if (courseArea.id.equals(regatta.defaultCourseAreaUuid)) {
courseAreaListBox.setSelectedIndex(courseAreaListBox.getItemCount() - 1);
}
@@ -270,9 +270,9 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog<T> extends DataEn
EventDTO event = getSelectedEvent();
int selIndex = courseAreaListBox.getSelectedIndex();
if (selIndex > 0 && event != null) { // the zero index represents the 'no selection' text
String itemText = courseAreaListBox.getItemText(selIndex);
String selectedCourseAreaIdAsString = courseAreaListBox.getValue(selIndex);
for (CourseAreaDTO courseAreaDTO : event.venue.getCourseAreas()) {
if (courseAreaDTO.getName().equals(itemText)) {
if (courseAreaDTO.id.toString().equals(selectedCourseAreaIdAsString)) {
result = courseAreaDTO;
break;
}
@@ -0,0 +1,58 @@
package com.sap.sailing.gwt.ui.adminconsole;
import java.util.List;
import com.google.gwt.resources.client.ImageResource;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.StringMessages;
import com.sap.sse.gwt.client.controls.listedit.ExpandedListEditorUi;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.ListEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.ListEditorUiStrategy;
public class CourseAreaListInlineEditorComposite extends GenericStringListInlineEditorComposite<CourseAreaDTO> {
public CourseAreaListInlineEditorComposite(Iterable<CourseAreaDTO> initialValues,
ListEditorUiStrategy<CourseAreaDTO> activeUi) {
super(initialValues, activeUi);
}
public CourseAreaListInlineEditorComposite(Iterable<CourseAreaDTO> initialValues, StringMessages stringMessages,
ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
super(initialValues, stringMessages, removeImage, suggestValues, textBoxSize);
}
public static class CollapsedUi extends GenericStringListEditorComposite.CollapsedUi<CourseAreaDTO> {
public CollapsedUi(StringMessages stringMessages, String dialogTitle, ExpandedListEditorUi<CourseAreaDTO> expandedUi) {
super(stringMessages, dialogTitle, expandedUi);
}
@Override
protected ListEditorComposite<CourseAreaDTO> createExpandedUi(Iterable<CourseAreaDTO> initialValues, ExpandedListEditorUi<CourseAreaDTO> ui) {
return new CourseAreaListInlineEditorComposite(initialValues, ui);
}
}
@Override
protected CourseAreaDTO parse(String s) {
return new CourseAreaDTO(s);
}
@Override
protected CourseAreaDTO parse(String s, CourseAreaDTO valueToUpdate) {
final CourseAreaDTO result;
if (Util.equalsWithNull(valueToUpdate.getName(), s)) {
result = valueToUpdate;
} else {
result = parse(s);
}
return result;
}
@Override
protected String toString(CourseAreaDTO value) {
return value.getName();
}
}
@@ -7,6 +7,7 @@ import java.util.List;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sse.gwt.client.media.ImageDTO;
@@ -45,6 +46,6 @@ public class EventCreateDialog extends EventDialog {
imagesListComposite.fillImages(Collections.<ImageDTO>emptyList());
videosListComposite.fillVideos(Collections.<VideoDTO>emptyList());
// add default course area
courseAreaNameList.setValue(Collections.singletonList("Default"));
courseAreaNameList.setValue(Collections.singletonList(new CourseAreaDTO("Default")));
}
}
@@ -34,8 +34,8 @@ import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite;
import com.sap.sse.gwt.client.media.ImageDTO;
import com.sap.sse.gwt.client.media.VideoDTO;
@@ -50,12 +50,12 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
protected UUID id;
protected TextBox officialWebsiteURLEntryField;
protected Map<String, TextBox> sailorsInfoWebsiteURLEntryFields = new HashMap<>();
protected StringListInlineEditorComposite courseAreaNameList;
protected CourseAreaListInlineEditorComposite courseAreaNameList;
protected StringConstantsListEditorComposite leaderboardGroupList;
protected Map<String, LeaderboardGroupDTO> availableLeaderboardGroupsByName;
protected ImagesListComposite imagesListComposite;
protected VideosListComposite videosListComposite;
protected ImagesListComposite imagesListComposite;
protected VideosListComposite videosListComposite;
protected static class EventParameterValidator implements Validator<EventDTO> {
private StringMessages stringMessages;
@@ -76,8 +76,9 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
if (courseAreaNotEmpty) {
for (CourseAreaDTO courseArea : eventToValidate.venue.getCourseAreas()) {
courseAreaNotEmpty = courseArea.getName() != null && courseArea.getName().length() > 0;
if (!courseAreaNotEmpty)
if (!courseAreaNotEmpty) {
break;
}
}
}
@@ -138,11 +139,17 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
validate();
}
};
final ValueChangeHandler<Iterable<CourseAreaDTO>> courseAreaValueChangeHandler = new ValueChangeHandler<Iterable<CourseAreaDTO>>() {
@Override
public void onValueChange(ValueChangeEvent<Iterable<CourseAreaDTO>> event) {
validate();
}
};
courseAreaNameList = new StringListInlineEditorComposite(Collections.<String> emptyList(),
new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
courseAreaNameList = new CourseAreaListInlineEditorComposite(Collections.<CourseAreaDTO> emptyList(),
new GenericStringListInlineEditorComposite.ExpandedUi<CourseAreaDTO>(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
SuggestedCourseAreaNames.suggestedCourseAreaNames, stringMessages.enterCourseAreaName(), 50));
courseAreaNameList.addValueChangeHandler(valueChangeHandler);
courseAreaNameList.addValueChangeHandler(courseAreaValueChangeHandler);
List<String> leaderboardGroupNames = new ArrayList<>();
for(LeaderboardGroupDTO leaderboardGroupDTO: availableLeaderboardGroups) {
leaderboardGroupNames.add(leaderboardGroupDTO.getName());
@@ -178,16 +185,11 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
result.endDate = endDateBox.getValue();
result.isPublic = isPublicCheckBox.getValue();
result.id = id;
List<CourseAreaDTO> courseAreas = new ArrayList<CourseAreaDTO>();
for (String courseAreaName : courseAreaNameList.getValue()) {
CourseAreaDTO courseAreaDTO = new CourseAreaDTO();
courseAreaDTO.setName(courseAreaName);
courseAreas.add(courseAreaDTO);
}
for (ImageDTO image: imagesListComposite.getAllImages()) {
List<CourseAreaDTO> courseAreas = courseAreaNameList.getValue();
for (ImageDTO image : imagesListComposite.getAllImages()) {
result.addImage(image);
}
for (VideoDTO video: videosListComposite.getAllVideos()) {
for (VideoDTO video : videosListComposite.getAllVideos()) {
result.addVideo(video);
}
result.venue = new VenueDTO(venueEntryField.getText(), courseAreas);
@@ -6,7 +6,6 @@ import java.util.List;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
@@ -29,17 +28,10 @@ public class EventEditDialog extends EventDialog {
isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(event.isPublic);
id = event.id;
List<String> courseAreaNames = new ArrayList<>();
if (event.venue.getCourseAreas() != null && event.venue.getCourseAreas().size() > 0) {
for (CourseAreaDTO courseArea : event.venue.getCourseAreas()) {
courseAreaNames.add(courseArea.getName());
}
}
officialWebsiteURLEntryField = createTextBox(event.getOfficialWebsiteURL());
officialWebsiteURLEntryField.setVisibleLength(50);
sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(event.getSailorsInfoWebsiteURLs());
courseAreaNameList.setValue(courseAreaNames);
courseAreaNameList.setValue(new ArrayList<>(event.venue.getCourseAreas()));
List<String> leaderboardGroupNames = new ArrayList<>();
for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) {
leaderboardGroupNames.add(leaderboardGroupDTO.getName());
@@ -130,9 +130,9 @@ public abstract class FlexibleLeaderboardDialog extends AbstractLeaderboardDialo
}
private void fillCourseAreaListBox(EventDTO selectedEvent) {
courseAreaListBox.addItem("Please select a course area...");
courseAreaListBox.addItem(stringMessages.pleaseSelectACourseArea());
for (CourseAreaDTO courseArea : selectedEvent.venue.getCourseAreas()) {
courseAreaListBox.addItem(courseArea.getName());
courseAreaListBox.addItem(courseArea.getName(), courseArea.id.toString());
}
courseAreaListBox.setEnabled(true);
}
@@ -156,10 +156,10 @@ public abstract class FlexibleLeaderboardDialog extends AbstractLeaderboardDialo
CourseAreaDTO result = null;
EventDTO event = getSelectedEvent();
int selIndex = courseAreaListBox.getSelectedIndex();
if(selIndex > 0 && event != null) { // the zero index represents the 'no selection' text
String itemText = courseAreaListBox.getItemText(selIndex);
for(CourseAreaDTO courseAreaDTO: event.venue.getCourseAreas()) {
if(courseAreaDTO.getName().equals(itemText)) {
if (selIndex > 0 && event != null) { // the zero index represents the 'no selection' text
String selectedCourseAreaId = courseAreaListBox.getValue(selIndex);
for (CourseAreaDTO courseAreaDTO : event.venue.getCourseAreas()) {
if (courseAreaDTO.id.toString().equals(selectedCourseAreaId)) {
result = courseAreaDTO;
break;
}
@@ -27,6 +27,7 @@ import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.controls.busyindicator.BusyIndicator;
import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.media.ImageDTO;
@@ -139,7 +140,7 @@ public abstract class ImageDialog extends DataEntryDialog<ImageDTO> {
});
tagsListEditor = new StringListInlineEditorComposite(Collections.<String> emptyList(),
new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
new GenericStringListInlineEditorComposite.ExpandedUi<String>(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
MediaConstants.imageTagSuggestions, "Enter tags for the image", 30));
}
@@ -31,6 +31,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.RegattaDTO;
import com.sap.sailing.gwt.ui.shared.SeriesDTO;
import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringListEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
@@ -234,7 +235,7 @@ public class SeriesEditDialog extends DataEntryDialog<SeriesDescriptor> {
return selectedSeries;
}
private class RaceNamesEditorUi extends StringListInlineEditorComposite.ExpandedUi {
private class RaceNamesEditorUi extends GenericStringListInlineEditorComposite.ExpandedUi<String> {
private final RegattaDTO regatta;
private final ListBox addRacesFromListBox;
@@ -22,6 +22,7 @@ import com.sap.sse.common.media.MimeType;
import com.sap.sse.gwt.adminconsole.URLFieldWithFileUpload;
import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.controls.IntegerBox;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringListInlineEditorComposite;
import com.sap.sse.gwt.client.dialog.DataEntryDialog;
import com.sap.sse.gwt.client.media.VideoDTO;
@@ -89,7 +90,7 @@ public abstract class VideoDialog extends DataEntryDialog<VideoDTO> {
});
thumbnailURLAndUploadComposite = new URLFieldWithFileUpload(stringMessages);
tagsListEditor = new StringListInlineEditorComposite(Collections.<String> emptyList(),
new StringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
new GenericStringListInlineEditorComposite.ExpandedUi<String>(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */
MediaConstants.videoTagSuggestions, "Enter tags for the video", 50));
}
@@ -6,7 +6,7 @@ import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.dto.NamedDTO;
/**
* Equality and hash code are based on the course area's name, as inherited from {@link NamedDTO}.
* Equality and hash code are based on the course area's {@link #id}.
*/
public class CourseAreaDTO extends NamedDTO implements IsSerializable {
private static final long serialVersionUID = -5279690838452265454L;
@@ -18,4 +18,26 @@ public class CourseAreaDTO extends NamedDTO implements IsSerializable {
public CourseAreaDTO(String name) {
super(name);
}
@Override
public int hashCode() {
final int prime = 31;
int result = prime * ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
CourseAreaDTO other = (CourseAreaDTO) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -11,6 +11,8 @@
<booleanAttribute key="default_auto_start" value="true"/>
<intAttribute key="default_start_level" value="4"/>
<booleanAttribute key="includeOptional" value="true"/>
<stringAttribute key="org.eclipse.debug.core.source_locator_id" value="org.eclipse.pde.ui.launcher.PDESourceLookupDirector"/>
<stringAttribute key="org.eclipse.debug.core.source_locator_memento" value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#13;&#10;&lt;sourceLookupDirector&gt;&#13;&#10;&lt;sourceContainers duplicates=&quot;false&quot;&gt;&#13;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#13;&amp;#10;&amp;lt;classpathContainer path=&amp;quot;org.eclipse.jdt.launching.JRE_CONTAINER&amp;quot;/&amp;gt;&amp;#13;&amp;#10;&quot; typeId=&quot;org.eclipse.jdt.launching.sourceContainer.classpathContainer&quot;/&gt;&#13;&#10;&lt;container memento=&quot;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; standalone=&amp;quot;no&amp;quot;?&amp;gt;&amp;#13;&amp;#10;&amp;lt;default/&amp;gt;&amp;#13;&amp;#10;&quot; typeId=&quot;org.eclipse.debug.core.containerType.default&quot;/&gt;&#13;&#10;&lt;/sourceContainers&gt;&#13;&#10;&lt;/sourceLookupDirector&gt;&#13;&#10;"/>
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
@@ -7,7 +7,6 @@ import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.PushButton;
import com.google.gwt.user.client.ui.ValueBoxBase;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.StringMessages;
@@ -108,7 +107,10 @@ public abstract class ExpandedListEditorUi<ValueType> extends ListEditorUi<Value
onRowRemoved();
}
protected void setValueFromValueWidget(ValueBoxBase<ValueType> valueWidget, ValueType newValue, boolean fireEvents) {
/**
* @param valueWidget a widget returned previously from {@link #createValueWidget(int, Object)}
*/
protected void setValueFromValueWidget(Widget valueWidget, ValueType newValue, boolean fireEvents) {
for (int i = 0; i < expandedValuesGrid.getRowCount(); i++) {
Widget gridWidget = expandedValuesGrid.getWidget(i, 0);
if (gridWidget.getElement() == valueWidget.getElement()) {
@@ -0,0 +1,156 @@
package com.sap.sse.gwt.client.controls.listedit;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.StringMessages;
/**
* A list editor that renders values of type {@code ValueType} as {@link String}s and lets the user edit them
* as strings. For this to work, implementing subclasses need to specify the
*
* @author Lukas Niemeier
* @author Axel Uhl (d043530)
*
* @param <ValueType>
*/
public abstract class GenericStringListEditorComposite<ValueType> extends ListEditorComposite<ValueType> {
protected GenericStringListEditorComposite(Iterable<ValueType> initialValues, StringMessages stringMessages,
ImageResource removeImage, Iterable<String> suggestValues) {
super(initialValues, new ExpandedUi<ValueType>(stringMessages, removeImage, suggestValues));
}
protected GenericStringListEditorComposite(Iterable<ValueType> initialValues, StringMessages stringMessages,
ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
super(initialValues, new ExpandedUi<ValueType>(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox));
}
protected GenericStringListEditorComposite(Iterable<ValueType> initialValues, ListEditorUiStrategy<ValueType> activeUi) {
super(initialValues, activeUi);
}
/**
* Parses a new value provided as string {@code s} into a new object of type {@code ValueType}
*/
abstract protected ValueType parse(String s);
/**
* Tries to update an existing value {@code valueToUpdate} from the string specification {@code s}.
* If an in-place update works, {@code valueToUpdate} is returned, otherwise a new {@code ValueType}
* object is created using {@link #parse(String)}.
*/
abstract protected ValueType parse(String s, ValueType valueToUpdate);
/**
* Renders a {@code value} as {@link String} for representation in labels and text input fields. It
* must be symmetric with {@link #parse(String, Object)} such that if in-place updates are generally supported
* by the implementation then {@code toString(parse(s, v)).equals(s)} must be satisfied.
*/
abstract protected String toString(ValueType value);
public abstract static class CollapsedUi<ValueType> extends CollapsedListEditorUi<ValueType> {
public CollapsedUi(StringMessages stringMessages, String dialogTitle, ExpandedListEditorUi<ValueType> expandedUi) {
super(stringMessages, dialogTitle, expandedUi);
}
@Override
protected String getCollapsedValueText(Iterable<ValueType> value) {
StringBuilder valuesText = new StringBuilder();
for (int i = 0; i < Util.size(value); i++) {
if (i > 0) {
valuesText.append(',');
}
valuesText.append(Util.get(value, i));
}
String condensedValue = valuesText.toString();
return condensedValue;
}
}
public static class ExpandedUi<ValueType> extends ExpandedListEditorUi<ValueType> {
protected final MultiWordSuggestOracle inputOracle;
protected final String placeholderTextForAddTextbox;
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues) {
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null);
}
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
super(stringMessages, removeImage, /*canRemoveItems*/true);
this.placeholderTextForAddTextbox = placeholderTextForAddTextbox;
this.inputOracle = new MultiWordSuggestOracle();
for (String suggestValue : suggestValues) {
inputOracle.add(suggestValue);
}
List<String> defaultSuggestions = new ArrayList<>();
Util.addAll(suggestValues, defaultSuggestions);
this.inputOracle.setDefaultSuggestionsFromText(defaultSuggestions);
}
protected GenericStringListEditorComposite<ValueType> getContext() {
return (GenericStringListEditorComposite<ValueType>) context;
}
@Override
public void setContext(ListEditorComposite<ValueType> context) {
super.setContext(context);
for (final ValueType value : context.getValue()) {
inputOracle.add(getContext().toString(value));
}
}
protected SuggestBox createSuggestBox() {
final SuggestBox result = new SuggestBox(inputOracle);
if (placeholderTextForAddTextbox != null) {
result.getElement().setAttribute("placeholder", placeholderTextForAddTextbox);
}
return result;
}
@Override
protected Widget createAddWidget() {
final SuggestBox inputBox = createSuggestBox();
inputBox.ensureDebugId("InputSuggestBox");
final Button addButton = new Button(getStringMessages().add());
addButton.ensureDebugId("AddButton");
addButton.setEnabled(false);
addButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
addValue(getContext().parse(inputBox.getValue()));
inputBox.setText("");
}
});
inputBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
addButton.setEnabled(!inputBox.getValue().isEmpty());
}
});
HorizontalPanel panel = new HorizontalPanel();
panel.add(inputBox);
panel.add(addButton);
return panel;
}
@Override
protected Widget createValueWidget(int rowIndex, ValueType newValue) {
return new Label(getContext().toString(newValue));
}
}
}
@@ -0,0 +1,77 @@
package com.sap.sse.gwt.client.controls.listedit;
import java.util.List;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.StringMessages;
/**
* In its expanded view, as opposed to its superclass which renders the values only as {@link Label}, this
* class renders the values as {@link TextBox} and allows users to edit the values in place, without having
* to remove and add them.
*/
public abstract class GenericStringListInlineEditorComposite<ValueType> extends GenericStringListEditorComposite<ValueType> {
public GenericStringListInlineEditorComposite(Iterable<ValueType> initialValues, StringMessages stringMessages,
ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
super(initialValues, new ExpandedUi<ValueType>(stringMessages, removeImage, suggestValues, textBoxSize));
}
public GenericStringListInlineEditorComposite(Iterable<ValueType> initialValues, ListEditorUiStrategy<ValueType> activeUi) {
super(initialValues, activeUi);
}
public static class ExpandedUi<ValueType> extends GenericStringListEditorComposite.ExpandedUi<ValueType> {
private final int textBoxSize;
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null, textBoxSize);
}
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues, String placeholderTextForAddTextbox, int textBoxSize) {
super(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox);
this.textBoxSize = textBoxSize;
}
@Override
protected SuggestBox createSuggestBox() {
SuggestBox result = super.createSuggestBox();
InputElement inputElement = result.getElement().cast();
inputElement.setSize(textBoxSize);
return result;
}
@Override
protected Widget createValueWidget(final int rowIndex, ValueType newValue) {
final TextBox textBox = new TextBox();
textBox.setVisibleLength(textBoxSize);
textBox.ensureDebugId("ValueTextBox");
textBox.setValue(getContext().toString(newValue));
textBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
textBox.setFocus(false);
// this ensures that the value is copied into the TextBox.getValue() result and a ChangeEvent is fired
textBox.setFocus(true);
}
});
textBox.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
ValueType oldValue = getContext().getValue().get(rowIndex);
ValueType newValue = getContext().parse(event.getValue(), oldValue);
setValueFromValueWidget(textBox, newValue, /* fireEvents */ true);
}
});
return textBox;
}
}
}
@@ -1,23 +1,18 @@
package com.sap.sse.gwt.client.controls.listedit;
import java.util.ArrayList;
import java.util.List;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.common.Util;
import com.sap.sse.gwt.client.StringMessages;
public class StringListEditorComposite extends ListEditorComposite<String> {
/**
* A simple string list editor for {@link String} values with trivial implementations for parsing and
* serializing to strings.
*
* @author Lukas Niemeier
* @author Axel Uhl (d043530)
*
*/
public class StringListEditorComposite extends GenericStringListEditorComposite<String> {
public StringListEditorComposite(Iterable<String> initialValues, StringMessages stringMessages,
String popupDialogTitle, ImageResource removeImage, Iterable<String> suggestValues) {
this(initialValues, stringMessages, popupDialogTitle, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null);
@@ -26,113 +21,46 @@ public class StringListEditorComposite extends ListEditorComposite<String> {
public StringListEditorComposite(Iterable<String> initialValues, StringMessages stringMessages,
String popupDialogTitle, ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
super(initialValues, new CollapsedUi(stringMessages, popupDialogTitle,
new ExpandedUi(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox)));
new ExpandedUi<String>(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox)));
}
public StringListEditorComposite(Iterable<String> initialValues, StringMessages stringMessages,
ImageResource removeImage, Iterable<String> suggestValues) {
super(initialValues, new ExpandedUi(stringMessages, removeImage, suggestValues));
super(initialValues, new ExpandedUi<String>(stringMessages, removeImage, suggestValues));
}
public StringListEditorComposite(Iterable<String> initialValues, StringMessages stringMessages,
ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
super(initialValues, new ExpandedUi(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox));
super(initialValues, new ExpandedUi<String>(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox));
}
public StringListEditorComposite(Iterable<String> initialValues, ListEditorUiStrategy<String> activeUi) {
super(initialValues, activeUi);
}
public static class CollapsedUi extends CollapsedListEditorUi<String> {
public static class CollapsedUi extends GenericStringListEditorComposite.CollapsedUi<String> {
public CollapsedUi(StringMessages stringMessages, String dialogTitle, ExpandedListEditorUi<String> expandedUi) {
super(stringMessages, dialogTitle, expandedUi);
}
@Override
protected String getCollapsedValueText(Iterable<String> value) {
StringBuilder valuesText = new StringBuilder();
for (int i = 0; i < Util.size(value); i++) {
if (i > 0) {
valuesText.append(',');
}
valuesText.append(Util.get(value, i));
}
String condensedValue = valuesText.toString();
return condensedValue;
}
@Override
protected ListEditorComposite<String> createExpandedUi(Iterable<String> initialValues, ExpandedListEditorUi<String> ui) {
return new StringListEditorComposite(initialValues, ui);
}
}
public static class ExpandedUi extends ExpandedListEditorUi<String> {
@Override
protected String parse(String s) {
return s;
}
protected final MultiWordSuggestOracle inputOracle;
protected final String placeholderTextForAddTextbox;
@Override
protected String parse(String s, String valueToUpdate) {
return Util.equalsWithNull(valueToUpdate, s) ? valueToUpdate : s; // try to preserve identity where possible
}
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues) {
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null);
}
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable<String> suggestValues, String placeholderTextForAddTextbox) {
super(stringMessages, removeImage, /*canRemoveItems*/true);
this.placeholderTextForAddTextbox = placeholderTextForAddTextbox;
this.inputOracle = new MultiWordSuggestOracle();
for (String suggestValue : suggestValues) {
inputOracle.add(suggestValue);
}
List<String> defaultSuggestions = new ArrayList<>();
Util.addAll(suggestValues, defaultSuggestions);
this.inputOracle.setDefaultSuggestionsFromText(defaultSuggestions);
}
@Override
public void setContext(ListEditorComposite<String> context) {
super.setContext(context);
inputOracle.addAll(context.getValue());
}
protected SuggestBox createSuggestBox() {
final SuggestBox result = new SuggestBox(inputOracle);
if (placeholderTextForAddTextbox != null) {
result.getElement().setAttribute("placeholder", placeholderTextForAddTextbox);
}
return result;
}
@Override
protected Widget createAddWidget() {
final SuggestBox inputBox = createSuggestBox();
inputBox.ensureDebugId("InputSuggestBox");
final Button addButton = new Button(getStringMessages().add());
addButton.ensureDebugId("AddButton");
addButton.setEnabled(false);
addButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
addValue(inputBox.getValue());
inputBox.setText("");
}
});
inputBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
addButton.setEnabled(!inputBox.getValue().isEmpty());
}
});
HorizontalPanel panel = new HorizontalPanel();
panel.add(inputBox);
panel.add(addButton);
return panel;
}
@Override
protected Widget createValueWidget(int rowIndex, String newValue) {
return new Label(newValue);
}
@Override
protected String toString(String value) {
return value;
}
}
@@ -1,30 +1,15 @@
package com.sap.sse.gwt.client.controls.listedit;
import java.util.List;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sse.gwt.client.StringMessages;
/**
* In its expanded view, as opposed to its superclass which renders the values only as {@link Label}, this
* class renders the values as {@link TextBox} and allows users to edit the values in place, without having
* to remove and add them.
*/
public class StringListInlineEditorComposite extends StringListEditorComposite {
public StringListInlineEditorComposite(List<String> initialValues, StringMessages stringMessages,
String popupDialogTitle, ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
super(initialValues, new CollapsedUi(stringMessages, popupDialogTitle,
new ExpandedUi(stringMessages, removeImage, suggestValues, textBoxSize)));
}
public StringListInlineEditorComposite(Iterable<String> initialValues, StringMessages stringMessages,
ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
super(initialValues, new ExpandedUi(stringMessages, removeImage, suggestValues, textBoxSize));
}
public StringListInlineEditorComposite(Iterable<String> initialValues, ListEditorUiStrategy<String> activeUi) {
super(initialValues, activeUi);
}
@@ -39,51 +24,4 @@ public class StringListInlineEditorComposite extends StringListEditorComposite {
return new StringListInlineEditorComposite(initialValues, ui);
}
}
public static class ExpandedUi extends StringListEditorComposite.ExpandedUi {
private final int textBoxSize;
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues, int textBoxSize) {
this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null, textBoxSize);
}
public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, List<String> suggestValues, String placeholderTextForAddTextbox, int textBoxSize) {
super(stringMessages, removeImage, suggestValues, placeholderTextForAddTextbox);
this.textBoxSize = textBoxSize;
}
@Override
protected SuggestBox createSuggestBox() {
SuggestBox result = super.createSuggestBox();
InputElement inputElement = result.getElement().cast();
inputElement.setSize(textBoxSize);
return result;
}
@Override
protected Widget createValueWidget(final int rowIndex, String newValue) {
final TextBox textBox = new TextBox();
textBox.setVisibleLength(textBoxSize);
textBox.ensureDebugId("ValueTextBox");
textBox.setValue(newValue);
textBox.addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
textBox.setFocus(false);
// this ensures that the value is copied into the TextBox.getValue() result and a ChangeEvent is fired
textBox.setFocus(true);
}
});
textBox.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
String newValue = event.getValue();
setValueFromValueWidget(textBox, newValue, true);
}
});
return textBox;
}
}
}
@@ -7,6 +7,7 @@ import org.junit.Before;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.mail.MailService;
import com.sap.sse.mail.MailServiceResolver;
import com.sap.sse.mail.impl.MailServiceImpl;
import com.sap.sse.replication.testsupport.AbstractServerReplicationTestSetUp;
import com.sap.sse.replication.testsupport.AbstractServerWithSingleServiceReplicationTest;
@@ -24,7 +25,14 @@ public class AbstractMailServiceReplicationTest extends AbstractServerWithSingle
}
public static MailServiceImpl createMailCountingService(final boolean canSendMail) {
return new MailServiceImpl(null) {
final MailServiceImpl[] mailService = new MailServiceImpl[1];
MailServiceResolver mailServiceResolver = new MailServiceResolver() {
@Override
public MailService getMailService() {
return mailService[0];
}
};
mailService[0] = new MailServiceImpl(null, mailServiceResolver) {
@Override
protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter)
throws MailException {
@@ -34,6 +42,7 @@ public class AbstractMailServiceReplicationTest extends AbstractServerWithSingle
}
}
};
return mailService[0];
}
public static class MailServerReplicationTestSetUp extends AbstractServerReplicationTestSetUp<MailService, MailServiceImpl> {
@@ -2,6 +2,7 @@ package com.sap.sse.mail;
import javax.mail.Multipart;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.mail.impl.ReplicableMailService;
import com.sap.sse.replication.Replicable;
@@ -22,7 +23,7 @@ import com.sap.sse.util.ClearStateTestSupport;
* @author Fredrik Teschke
*
*/
public interface MailService extends ReplicableWithObjectInputStream<ReplicableMailService, MailOperation<?>>, ClearStateTestSupport {
public interface MailService extends ReplicableWithObjectInputStream<ReplicableMailService, MailOperation<?>>, ClearStateTestSupport, IsManagedByCache<MailServiceResolver> {
void sendMail(String toAddress, String subject, String body) throws MailException;
/**
@@ -0,0 +1,9 @@
package com.sap.sse.mail;
public interface MailServiceResolver {
/**
* @return {@code null} if this service is not known
*/
MailService getMailService();
}
@@ -14,6 +14,7 @@ import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import com.sap.sse.mail.MailService;
import com.sap.sse.osgi.CachedOsgiTypeBasedServiceFinderFactory;
import com.sap.sse.replication.Replicable;
import com.sap.sse.util.ClearStateTestSupport;
@@ -34,7 +35,8 @@ public class Activator implements BundleActivator {
logger.log(Level.SEVERE, "Couldn't read mail properties from " + propertiesfile.getCanonicalPath(), ioe);
}
MailService mailService = new MailServiceImpl(mailProperties);
MailService mailService = new MailServiceImpl(mailProperties, new MailServiceResolverAgainstOsgiRegistryImpl(
new CachedOsgiTypeBasedServiceFinderFactory(context).createServiceFinder(MailService.class)));
registration = context.registerService(MailService.class, mailService, null);
final Dictionary<String, String> replicableServiceProperties = new Hashtable<>();
replicableServiceProperties.put(Replicable.OSGi_Service_Registry_ID_Property_Name, mailService.getId()
@@ -24,11 +24,14 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.mail.MailException;
import com.sap.sse.mail.MailServiceResolver;
import com.sap.sse.replication.OperationExecutionListener;
import com.sap.sse.replication.OperationWithResult;
import com.sap.sse.replication.ReplicationMasterDescriptor;
import com.sap.sse.replication.impl.OperationWithResultWithIdWrapper;
import com.sap.sse.util.ObjectInputStreamResolvingAgainstCache;
public class MailServiceImpl implements ReplicableMailService {
private static final Logger logger = Logger.getLogger(MailServiceImpl.class.getName());
@@ -45,9 +48,12 @@ public class MailServiceImpl implements ReplicableMailService {
private ThreadLocal<Boolean> currentlyFillingFromInitialLoadOrApplyingOperationReceivedFromMaster = ThreadLocal
.withInitial(() -> false);
public MailServiceImpl(Properties mailProperties) {
private final MailServiceResolver mailServiceResolver;
public MailServiceImpl(Properties mailProperties, MailServiceResolver mailServiceResolver) {
this.mailProperties = mailProperties;
this.operationExecutionListeners = new ConcurrentHashMap<>();
this.mailServiceResolver = mailServiceResolver;
}
private class SMTPAuthenticator extends javax.mail.Authenticator {
@@ -66,7 +72,7 @@ public class MailServiceImpl implements ReplicableMailService {
return mailProperties != null && mailProperties.containsKey("mail.transport.protocol");
}
//protected for testing purposes
// protected for testing purposes
protected void internalSendMail(String toAddress, String subject, ContentSetter contentSetter) throws MailException {
if (canSendMail()) {
if (toAddress != null) {
@@ -104,11 +110,9 @@ public class MailServiceImpl implements ReplicableMailService {
}
}
} else {
logger.warning("No mail properties provided. Cannot send e-mail about "
+ subject
+ " to "
+ toAddress
+ ". This could also mean that this is running on a replica server in which case this is perfectly fine.");
logger.warning("No mail properties provided. Cannot send e-mail about " + subject + " to " + toAddress
+ ". This could also mean that this is running on a replica server in which case this is perfectly fine. "
+ "The master "+getMasterDescriptor()+" will handle the mail sending operation in this case.");
}
}
@@ -162,7 +166,12 @@ public class MailServiceImpl implements ReplicableMailService {
@Override
public ObjectInputStream createObjectInputStreamResolvingAgainstCache(InputStream is) throws IOException {
return new ObjectInputStream(is);
return new ObjectInputStreamResolvingAgainstCache<MailServiceResolver>(is, mailServiceResolver) {};
}
@Override
public IsManagedByCache<MailServiceResolver> resolve(MailServiceResolver cache) {
return cache.getMailService();
}
@Override
@@ -0,0 +1,24 @@
package com.sap.sse.mail.impl;
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
import com.sap.sse.common.TypeBasedServiceFinder;
import com.sap.sse.mail.MailService;
import com.sap.sse.mail.MailServiceResolver;
public class MailServiceResolverAgainstOsgiRegistryImpl implements MailServiceResolver {
private final TypeBasedServiceFinder<MailService> serviceFinder;
public MailServiceResolverAgainstOsgiRegistryImpl(TypeBasedServiceFinder<MailService> serviceFinder) {
this.serviceFinder = serviceFinder;
}
@Override
public MailService getMailService() {
try {
return serviceFinder.findService(null);
} catch (NoCorrespondingServiceRegisteredException e) {
return null;
}
}
}
@@ -34,9 +34,12 @@ public abstract class ObjectInputStreamResolvingAgainstCache<C> extends ObjectIn
/**
* Package protected on purpose; instances to be created using a factory method on the {@link Replicable}. This
* constructor {@link #enableResolveObject(boolean) enables resolving} by default.
*
* @param cache must not be {@code null}
*/
protected ObjectInputStreamResolvingAgainstCache(InputStream in, C cache) throws IOException {
super(in);
assert cache != null;
this.cache = cache;
enableResolveObject(true);
}
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sap.sailing.android.tracking.app"
android:versionCode="1"
android:versionName="1 - Altitude">
android:versionCode="7"
android:versionName="1.5">
<uses-sdk
android:minSdkVersion="14"
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.sap.sailing.android.buoy.positioning.app"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0">
android:versionCode="7"
android:versionName="1.5">
<uses-sdk
android:minSdkVersion="14"
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.sap.sailing.racecommittee.app"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="7"
android:versionName="7 - Laser">
android:versionCode="13"
android:versionName="1.5">
<uses-sdk
android:minSdkVersion="14"