summaryrefslogtreecommitdiff
path: root/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
diff options
context:
space:
mode:
authorTobrun <tobrun.van.nuland@gmail.com>2017-01-16 09:06:29 +0100
committerGitHub <noreply@github.com>2017-01-16 09:06:29 +0100
commit5c61ea08697cd9b4b21e0689431bc68617ad646c (patch)
tree3d4fbf96209964fa73087397fc3d470dfb768541 /platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
parent9a635672a6251f05b864efc71214046f9ea4acff (diff)
downloadqtlocation-mapboxgl-5c61ea08697cd9b4b21e0689431bc68617ad646c.tar.gz
[android] - safe guard making asyncrhonous calls to map object while it's beeing destroyed (#7613)
Diffstat (limited to 'platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java')
-rwxr-xr-xplatform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java315
1 files changed, 311 insertions, 4 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
index 8300e8024b..05d1bf1750 100755
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java
@@ -9,6 +9,7 @@ import android.graphics.RectF;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.Surface;
@@ -16,6 +17,7 @@ import com.mapbox.mapboxsdk.annotations.Icon;
import com.mapbox.mapboxsdk.annotations.Marker;
import com.mapbox.mapboxsdk.annotations.Polygon;
import com.mapbox.mapboxsdk.annotations.Polyline;
+import com.mapbox.mapboxsdk.constants.MapboxConstants;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.ProjectedMeters;
import com.mapbox.mapboxsdk.offline.OfflineManager;
@@ -102,6 +104,15 @@ final class NativeMapView {
// Methods
//
+ private boolean isDestroyedOn(String callingMethod) {
+ if (destroyed && !TextUtils.isEmpty(callingMethod)) {
+ Timber.e(String.format(MapboxConstants.MAPBOX_LOCALE,
+ "You're calling `%s` after the `MapView` was destroyed, were you invoking it after `onDestroy()`?",
+ callingMethod));
+ }
+ return destroyed;
+ }
+
public void destroy() {
nativeDestroy(nativeMapViewPtr);
nativeMapViewPtr = 0;
@@ -109,43 +120,66 @@ final class NativeMapView {
destroyed = true;
}
- public boolean wasDestroyed() {
- return destroyed;
- }
-
public void initializeDisplay() {
+ if (isDestroyedOn("initializeDisplay")) {
+ return;
+ }
nativeInitializeDisplay(nativeMapViewPtr);
}
public void terminateDisplay() {
+ if (isDestroyedOn("terminateDisplay")) {
+ return;
+ }
nativeTerminateDisplay(nativeMapViewPtr);
}
public void initializeContext() {
+ if (isDestroyedOn("initializeContext")) {
+ return;
+ }
nativeInitializeContext(nativeMapViewPtr);
}
public void terminateContext() {
+ if (isDestroyedOn("terminateContext")) {
+ return;
+ }
nativeTerminateContext(nativeMapViewPtr);
}
public void createSurface(Surface surface) {
+ if (isDestroyedOn("createSurface")) {
+ return;
+ }
nativeCreateSurface(nativeMapViewPtr, surface);
}
public void destroySurface() {
+ if (isDestroyedOn("destroySurface")) {
+ return;
+ }
nativeDestroySurface(nativeMapViewPtr);
}
public void update() {
+ if (isDestroyedOn("update")) {
+ return;
+ }
nativeUpdate(nativeMapViewPtr);
}
public void render() {
+ if (isDestroyedOn("render")) {
+ return;
+ }
nativeRender(nativeMapViewPtr);
}
public void resizeView(int width, int height) {
+ if (isDestroyedOn("resizeView")) {
+ return;
+ }
width = (int) (width / pixelRatio);
height = (int) (height / pixelRatio);
@@ -174,6 +208,9 @@ final class NativeMapView {
}
public void resizeFramebuffer(int fbWidth, int fbHeight) {
+ if (isDestroyedOn("resizeFramebuffer")) {
+ return;
+ }
if (fbWidth < 0) {
throw new IllegalArgumentException("fbWidth cannot be negative.");
}
@@ -195,159 +232,276 @@ final class NativeMapView {
}
public void addClass(String clazz) {
+ if (isDestroyedOn("addClass")) {
+ return;
+ }
nativeAddClass(nativeMapViewPtr, clazz);
}
public void removeClass(String clazz) {
+ if (isDestroyedOn("removeClass")) {
+ return;
+ }
nativeRemoveClass(nativeMapViewPtr, clazz);
}
public boolean hasClass(String clazz) {
+ if (isDestroyedOn("hasClass")) {
+ return false;
+ }
return nativeHasClass(nativeMapViewPtr, clazz);
}
public void setClasses(List<String> classes) {
+ if (isDestroyedOn("setClasses")) {
+ return;
+ }
nativeSetClasses(nativeMapViewPtr, classes);
}
public List<String> getClasses() {
+ if (isDestroyedOn("getClasses")) {
+ return new ArrayList<>();
+ }
return nativeGetClasses(nativeMapViewPtr);
}
public void setStyleUrl(String url) {
+ if (isDestroyedOn("setStyleUrl")) {
+ return;
+ }
nativeSetStyleUrl(nativeMapViewPtr, url);
}
public String getStyleUrl() {
+ if (isDestroyedOn("getStyleUrl")) {
+ return null;
+ }
return nativeGetStyleUrl(nativeMapViewPtr);
}
public void setStyleJson(String newStyleJson) {
+ if (isDestroyedOn("setStyleJson")) {
+ return;
+ }
nativeSetStyleJson(nativeMapViewPtr, newStyleJson);
}
public String getStyleJson() {
+ if (isDestroyedOn("getStyleJson")) {
+ return null;
+ }
return nativeGetStyleJson(nativeMapViewPtr);
}
public void setAccessToken(String accessToken) {
+ if (isDestroyedOn("setAccessToken")) {
+ return;
+ }
nativeSetAccessToken(nativeMapViewPtr, accessToken);
}
public String getAccessToken() {
+ if (isDestroyedOn("getAccessToken")) {
+ return null;
+ }
return nativeGetAccessToken(nativeMapViewPtr);
}
public void cancelTransitions() {
+ if (isDestroyedOn("cancelTransitions")) {
+ return;
+ }
nativeCancelTransitions(nativeMapViewPtr);
}
public void setGestureInProgress(boolean inProgress) {
+ if (isDestroyedOn("setGestureInProgress")) {
+ return;
+ }
nativeSetGestureInProgress(nativeMapViewPtr, inProgress);
}
public void moveBy(double dx, double dy) {
+ if (isDestroyedOn("moveBy")) {
+ return;
+ }
moveBy(dx, dy, 0);
}
public void moveBy(double dx, double dy, long duration) {
+ if (isDestroyedOn("moveBy")) {
+ return;
+ }
nativeMoveBy(nativeMapViewPtr, dx / pixelRatio, dy / pixelRatio, duration);
}
public void setLatLng(LatLng latLng) {
+ if (isDestroyedOn("setLatLng")) {
+ return;
+ }
setLatLng(latLng, 0);
}
public void setLatLng(LatLng latLng, long duration) {
+ if (isDestroyedOn("setLatLng")) {
+ return;
+ }
nativeSetLatLng(nativeMapViewPtr, latLng.getLatitude(), latLng.getLongitude(), duration);
}
public LatLng getLatLng() {
+ if (isDestroyedOn("")) {
+ return new LatLng();
+ }
return nativeGetLatLng(nativeMapViewPtr);
}
public void resetPosition() {
+ if (isDestroyedOn("resetPosition")) {
+ return;
+ }
nativeResetPosition(nativeMapViewPtr);
}
public double getPitch() {
+ if (isDestroyedOn("getPitch")) {
+ return 0;
+ }
return nativeGetPitch(nativeMapViewPtr);
}
public void setPitch(double pitch, long duration) {
+ if (isDestroyedOn("setPitch")) {
+ return;
+ }
nativeSetPitch(nativeMapViewPtr, pitch, duration);
}
public void scaleBy(double ds) {
+ if (isDestroyedOn("scaleBy")) {
+ return;
+ }
scaleBy(ds, Double.NaN, Double.NaN);
}
public void scaleBy(double ds, double cx, double cy) {
+ if (isDestroyedOn("scaleBy")) {
+ return;
+ }
scaleBy(ds, cx, cy, 0);
}
public void scaleBy(double ds, double cx, double cy, long duration) {
+ if (isDestroyedOn("scaleBy")) {
+ return;
+ }
nativeScaleBy(nativeMapViewPtr, ds, cx / pixelRatio, cy / pixelRatio, duration);
}
public void setScale(double scale) {
+ if (isDestroyedOn("setScale")) {
+ return;
+ }
setScale(scale, Double.NaN, Double.NaN);
}
public void setScale(double scale, double cx, double cy) {
+ if (isDestroyedOn("setScale")) {
+ return;
+ }
setScale(scale, cx, cy, 0);
}
public void setScale(double scale, double cx, double cy, long duration) {
+ if (isDestroyedOn("setScale")) {
+ return;
+ }
nativeSetScale(nativeMapViewPtr, scale, cx / pixelRatio, cy / pixelRatio, duration);
}
public double getScale() {
+ if (isDestroyedOn("getScale")) {
+ return 0;
+ }
return nativeGetScale(nativeMapViewPtr);
}
public void setZoom(double zoom) {
+ if (isDestroyedOn("setZoom")) {
+ return;
+ }
setZoom(zoom, 0);
}
public void setZoom(double zoom, long duration) {
+ if (isDestroyedOn("setZoom")) {
+ return;
+ }
nativeSetZoom(nativeMapViewPtr, zoom, duration);
}
public double getZoom() {
+ if (isDestroyedOn("getZoom")) {
+ return 0;
+ }
return nativeGetZoom(nativeMapViewPtr);
}
public void resetZoom() {
+ if (isDestroyedOn("resetZoom")) {
+ return;
+ }
nativeResetZoom(nativeMapViewPtr);
}
public void setMinZoom(double zoom) {
+ if (isDestroyedOn("setMinZoom")) {
+ return;
+ }
nativeSetMinZoom(nativeMapViewPtr, zoom);
}
public double getMinZoom() {
+ if (isDestroyedOn("getMinZoom")) {
+ return 0;
+ }
return nativeGetMinZoom(nativeMapViewPtr);
}
public void setMaxZoom(double zoom) {
+ if (isDestroyedOn("setMaxZoom")) {
+ return;
+ }
nativeSetMaxZoom(nativeMapViewPtr, zoom);
}
public double getMaxZoom() {
+ if (isDestroyedOn("getMaxZoom")) {
+ return 0;
+ }
return nativeGetMaxZoom(nativeMapViewPtr);
}
public void rotateBy(double sx, double sy, double ex, double ey) {
+ if (isDestroyedOn("rotateBy")) {
+ return;
+ }
rotateBy(sx, sy, ex, ey, 0);
}
public void rotateBy(double sx, double sy, double ex, double ey,
long duration) {
+ if (isDestroyedOn("rotateBy")) {
+ return;
+ }
nativeRotateBy(nativeMapViewPtr, sx / pixelRatio, sy / pixelRatio, ex, ey, duration);
}
public void setContentPadding(int[] padding) {
+ if (isDestroyedOn("setContentPadding")) {
+ return;
+ }
nativeSetContentPadding(nativeMapViewPtr,
padding[1] / pixelRatio,
padding[0] / pixelRatio,
@@ -356,191 +510,323 @@ final class NativeMapView {
}
public void setBearing(double degrees) {
+ if (isDestroyedOn("setBearing")) {
+ return;
+ }
setBearing(degrees, 0);
}
public void setBearing(double degrees, long duration) {
+ if (isDestroyedOn("setBearing")) {
+ return;
+ }
nativeSetBearing(nativeMapViewPtr, degrees, duration);
}
public void setBearing(double degrees, double cx, double cy) {
+ if (isDestroyedOn("setBearing")) {
+ return;
+ }
nativeSetBearingXY(nativeMapViewPtr, degrees, cx / pixelRatio, cy / pixelRatio);
}
public double getBearing() {
+ if (isDestroyedOn("getBearing")) {
+ return 0;
+ }
return nativeGetBearing(nativeMapViewPtr);
}
public void resetNorth() {
+ if (isDestroyedOn("resetNorth")) {
+ return;
+ }
nativeResetNorth(nativeMapViewPtr);
}
public long addMarker(Marker marker) {
+ if (isDestroyedOn("addMarker")) {
+ return 0;
+ }
Marker[] markers = {marker};
return nativeAddMarkers(nativeMapViewPtr, markers)[0];
}
public long[] addMarkers(List<Marker> markers) {
+ if (isDestroyedOn("addMarkers")) {
+ return new long[] {};
+ }
return nativeAddMarkers(nativeMapViewPtr, markers.toArray(new Marker[markers.size()]));
}
public long addPolyline(Polyline polyline) {
+ if (isDestroyedOn("addPolyline")) {
+ return 0;
+ }
Polyline[] polylines = {polyline};
return nativeAddPolylines(nativeMapViewPtr, polylines)[0];
}
public long[] addPolylines(List<Polyline> polylines) {
+ if (isDestroyedOn("addPolylines")) {
+ return new long[] {};
+ }
return nativeAddPolylines(nativeMapViewPtr, polylines.toArray(new Polyline[polylines.size()]));
}
public long addPolygon(Polygon polygon) {
+ if (isDestroyedOn("addPolygon")) {
+ return 0;
+ }
Polygon[] polygons = {polygon};
return nativeAddPolygons(nativeMapViewPtr, polygons)[0];
}
public long[] addPolygons(List<Polygon> polygons) {
+ if (isDestroyedOn("addPolygons")) {
+ return new long[] {};
+ }
return nativeAddPolygons(nativeMapViewPtr, polygons.toArray(new Polygon[polygons.size()]));
}
public void updateMarker(Marker marker) {
+ if (isDestroyedOn("updateMarker")) {
+ return;
+ }
LatLng position = marker.getPosition();
Icon icon = marker.getIcon();
nativeUpdateMarker(nativeMapViewPtr, marker.getId(), position.getLatitude(), position.getLongitude(), icon.getId());
}
public void updatePolygon(Polygon polygon) {
+ if (isDestroyedOn("updatePolygon")) {
+ return;
+ }
nativeUpdatePolygon(nativeMapViewPtr, polygon.getId(), polygon);
}
public void updatePolyline(Polyline polyline) {
+ if (isDestroyedOn("updatePolyline")) {
+ return;
+ }
nativeUpdatePolyline(nativeMapViewPtr, polyline.getId(), polyline);
}
public void removeAnnotation(long id) {
+ if (isDestroyedOn("removeAnnotation")) {
+ return;
+ }
long[] ids = {id};
removeAnnotations(ids);
}
public void removeAnnotations(long[] ids) {
+ if (isDestroyedOn("removeAnnotations")) {
+ return;
+ }
nativeRemoveAnnotations(nativeMapViewPtr, ids);
}
public long[] queryPointAnnotations(RectF rect) {
+ if (isDestroyedOn("queryPointAnnotations")) {
+ return new long[] {};
+ }
return nativeQueryPointAnnotations(nativeMapViewPtr, rect);
}
public void addAnnotationIcon(String symbol, int width, int height, float scale, byte[] pixels) {
+ if (isDestroyedOn("addAnnotationIcon")) {
+ return;
+ }
nativeAddAnnotationIcon(nativeMapViewPtr, symbol, width, height, scale, pixels);
}
public void setVisibleCoordinateBounds(LatLng[] coordinates, RectF padding, double direction, long duration) {
+ if (isDestroyedOn("setVisibleCoordinateBounds")) {
+ return;
+ }
nativeSetVisibleCoordinateBounds(nativeMapViewPtr, coordinates, padding, direction, duration);
}
public void onLowMemory() {
+ if (isDestroyedOn("onLowMemory")) {
+ return;
+ }
nativeOnLowMemory(nativeMapViewPtr);
}
public void setDebug(boolean debug) {
+ if (isDestroyedOn("setDebug")) {
+ return;
+ }
nativeSetDebug(nativeMapViewPtr, debug);
}
public void cycleDebugOptions() {
+ if (isDestroyedOn("cycleDebugOptions")) {
+ return;
+ }
nativeToggleDebug(nativeMapViewPtr);
}
public boolean getDebug() {
+ if (isDestroyedOn("getDebug")) {
+ return false;
+ }
return nativeGetDebug(nativeMapViewPtr);
}
public boolean isFullyLoaded() {
+ if (isDestroyedOn("isFullyLoaded")) {
+ return false;
+ }
return nativeIsFullyLoaded(nativeMapViewPtr);
}
public void setReachability(boolean status) {
+ if (isDestroyedOn("setReachability")) {
+ return;
+ }
nativeSetReachability(nativeMapViewPtr, status);
}
public double getMetersPerPixelAtLatitude(double lat) {
+ if (isDestroyedOn("getMetersPerPixelAtLatitude")) {
+ return 0;
+ }
return nativeGetMetersPerPixelAtLatitude(nativeMapViewPtr, lat, getZoom());
}
public ProjectedMeters projectedMetersForLatLng(LatLng latLng) {
+ if (isDestroyedOn("projectedMetersForLatLng")) {
+ return null;
+ }
return nativeProjectedMetersForLatLng(nativeMapViewPtr, latLng.getLatitude(), latLng.getLongitude());
}
public LatLng latLngForProjectedMeters(ProjectedMeters projectedMeters) {
+ if (isDestroyedOn("latLngForProjectedMeters")) {
+ return new LatLng();
+ }
return nativeLatLngForProjectedMeters(nativeMapViewPtr, projectedMeters.getNorthing(),
projectedMeters.getEasting());
}
public PointF pixelForLatLng(LatLng latLng) {
+ if (isDestroyedOn("pixelForLatLng")) {
+ return new PointF();
+ }
PointF pointF = nativePixelForLatLng(nativeMapViewPtr, latLng.getLatitude(), latLng.getLongitude());
pointF.set(pointF.x * pixelRatio, pointF.y * pixelRatio);
return pointF;
}
public LatLng latLngForPixel(PointF pixel) {
+ if (isDestroyedOn("latLngForPixel")) {
+ return new LatLng();
+ }
return nativeLatLngForPixel(nativeMapViewPtr, pixel.x / pixelRatio, pixel.y / pixelRatio);
}
public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) {
+ if (isDestroyedOn("getTopOffsetPixelsForAnnotationSymbol")) {
+ return 0;
+ }
return nativeGetTopOffsetPixelsForAnnotationSymbol(nativeMapViewPtr, symbolName);
}
public void jumpTo(double angle, LatLng center, double pitch, double zoom) {
+ if (isDestroyedOn("jumpTo")) {
+ return;
+ }
nativeJumpTo(nativeMapViewPtr, angle, center.getLatitude(), center.getLongitude(), pitch, zoom);
}
public void easeTo(double angle, LatLng center, long duration, double pitch, double zoom,
boolean easingInterpolator) {
+ if (isDestroyedOn("easeTo")) {
+ return;
+ }
nativeEaseTo(nativeMapViewPtr, angle, center.getLatitude(), center.getLongitude(), duration, pitch, zoom,
easingInterpolator);
}
public void flyTo(double angle, LatLng center, long duration, double pitch, double zoom) {
+ if (isDestroyedOn("flyTo")) {
+ return;
+ }
nativeFlyTo(nativeMapViewPtr, angle, center.getLatitude(), center.getLongitude(), duration, pitch, zoom);
}
public double[] getCameraValues() {
+ if (isDestroyedOn("getCameraValues")) {
+ return new double[] {};
+ }
return nativeGetCameraValues(nativeMapViewPtr);
}
// Runtime style Api
public Layer getLayer(String layerId) {
+ if (isDestroyedOn("getLayer")) {
+ return null;
+ }
return nativeGetLayer(nativeMapViewPtr, layerId);
}
public void addLayer(@NonNull Layer layer, @Nullable String before) {
+ if (isDestroyedOn("")) {
+ return;
+ }
nativeAddLayer(nativeMapViewPtr, layer.getNativePtr(), before);
}
public void removeLayer(@NonNull String layerId) throws NoSuchLayerException {
+ if (isDestroyedOn("removeLayer")) {
+ return;
+ }
nativeRemoveLayerById(nativeMapViewPtr, layerId);
}
public void removeLayer(@NonNull Layer layer) throws NoSuchLayerException {
+ if (isDestroyedOn("removeLayer")) {
+ return;
+ }
nativeRemoveLayer(nativeMapViewPtr, layer.getNativePtr());
}
public Source getSource(@NonNull String sourceId) {
+ if (isDestroyedOn("getSource")) {
+ return null;
+ }
return nativeGetSource(nativeMapViewPtr, sourceId);
}
public void addSource(@NonNull Source source) {
+ if (isDestroyedOn("addSource")) {
+ return;
+ }
nativeAddSource(nativeMapViewPtr, source.getNativePtr());
}
public void removeSource(@NonNull String sourceId) throws NoSuchSourceException {
+ if (isDestroyedOn("removeSource")) {
+ return;
+ }
nativeRemoveSourceById(nativeMapViewPtr, sourceId);
}
public void removeSource(@NonNull Source source) throws NoSuchSourceException {
+ if (isDestroyedOn("removeSource")) {
+ return;
+ }
nativeRemoveSource(nativeMapViewPtr, source.getNativePtr());
}
public void addImage(@NonNull String name, @NonNull Bitmap image) {
+ if (isDestroyedOn("addImage")) {
+ return;
+ }
//Check/correct config
if (image.getConfig() != Bitmap.Config.ARGB_8888) {
image = image.copy(Bitmap.Config.ARGB_8888, false);
@@ -558,6 +844,9 @@ final class NativeMapView {
}
public void removeImage(String name) {
+ if (isDestroyedOn("removeImage")) {
+ return;
+ }
nativeRemoveImage(nativeMapViewPtr, name);
}
@@ -565,6 +854,9 @@ final class NativeMapView {
@NonNull
public List<Feature> queryRenderedFeatures(PointF coordinates, String... layerIds) {
+ if (isDestroyedOn("queryRenderedFeatures")) {
+ return new ArrayList<>();
+ }
Feature[] features = nativeQueryRenderedFeaturesForPoint(nativeMapViewPtr, coordinates.x / pixelRatio,
coordinates.y / pixelRatio, layerIds);
return features != null ? Arrays.asList(features) : new ArrayList<Feature>();
@@ -572,6 +864,9 @@ final class NativeMapView {
@NonNull
public List<Feature> queryRenderedFeatures(RectF coordinates, String... layerIds) {
+ if (isDestroyedOn("queryRenderedFeatures")) {
+ return new ArrayList<>();
+ }
Feature[] features = nativeQueryRenderedFeaturesForBox(
nativeMapViewPtr,
coordinates.left / pixelRatio,
@@ -583,10 +878,16 @@ final class NativeMapView {
}
public void scheduleTakeSnapshot() {
+ if (isDestroyedOn("scheduleTakeSnapshot")) {
+ return;
+ }
nativeScheduleTakeSnapshot(nativeMapViewPtr);
}
public void setApiBaseUrl(String baseUrl) {
+ if (isDestroyedOn("setApiBaseUrl")) {
+ return;
+ }
nativeSetAPIBaseURL(nativeMapViewPtr, baseUrl);
}
@@ -833,10 +1134,16 @@ final class NativeMapView {
private native void nativeSetAPIBaseURL(long nativeMapViewPtr, String baseUrl);
int getWidth() {
+ if (isDestroyedOn("")) {
+ return 0;
+ }
return mapView.getWidth();
}
int getHeight() {
+ if (isDestroyedOn("")) {
+ return 0;
+ }
return mapView.getHeight();
}