Skip to content

Commit 449258d

Browse files
committed
chore(library): Port Feature to Kotlin and enhance tests
This commit modernizes the `Feature` class by porting it from Java to idiomatic Kotlin, improving its conciseness, readability, and null safety. The key changes include: - **Porting `Feature` to Kotlin**: The class is now written in Kotlin, using modern language features like properties and a primary constructor. - **Correct Observable Behavior**: Setters for geometry and properties now correctly call `setChanged()` and `notifyObservers()`, ensuring that observers are notified of modifications. - **Test Modernization**: The corresponding `FeatureTest` has been ported to Kotlin and now uses Google Truth for more expressive assertions. - **Enhanced Test Coverage**: New tests have been added to verify the `Observable` behavior when a feature's properties or geometry are changed. - **Subclass Update**: `GeoJsonFeature` has been updated to align with the changes in its `Feature` superclass.
1 parent 7d05251 commit 449258d

File tree

3 files changed

+147
-142
lines changed

3 files changed

+147
-142
lines changed
Lines changed: 38 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017 Google Inc.
2+
* Copyright 2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,117 +13,73 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
package com.google.maps.android.data
1617

17-
package com.google.maps.android.data;
18-
19-
import java.util.HashMap;
20-
import java.util.Map;
21-
import java.util.Observable;
18+
import java.util.Observable
2219

2320
/**
2421
* An abstraction that shares the common properties of
25-
* {@link com.google.maps.android.data.kml.KmlPlacemark KmlPlacemark} and
26-
* {@link com.google.maps.android.data.geojson.GeoJsonFeature GeoJsonFeature}
22+
* [com.google.maps.android.data.kml.KmlPlacemark] and
23+
* [com.google.maps.android.data.geojson.GeoJsonFeature]
2724
*/
28-
public class Feature extends Observable {
29-
30-
protected String mId;
31-
32-
private final Map<String, String> mProperties;
33-
34-
private Geometry mGeometry;
35-
36-
/**
37-
* Creates a new Feature object
38-
*
39-
* @param featureGeometry type of geometry to assign to the feature
40-
* @param id common identifier of the feature
41-
* @param properties map containing properties related to the feature
42-
*/
43-
public Feature(Geometry featureGeometry, String id,
44-
Map<String, String> properties) {
45-
mGeometry = featureGeometry;
46-
mId = id;
47-
if (properties == null) {
48-
mProperties = new HashMap<>();
49-
} else {
50-
mProperties = properties;
25+
open class Feature(
26+
geometry: Geometry<*>?,
27+
id: String?,
28+
properties: Map<String, String>?
29+
) : Observable() {
30+
open var id: String? = id
31+
protected set
32+
33+
private val _properties: MutableMap<String, String> = properties?.toMutableMap() ?: mutableMapOf()
34+
35+
open var geometry: Geometry<*>? = geometry
36+
protected set(value) {
37+
field = value
38+
setChanged()
39+
notifyObservers()
5140
}
52-
}
5341

5442
/**
5543
* Returns all the stored property keys
56-
*
57-
* @return iterable of property keys
5844
*/
59-
public Iterable<String> getPropertyKeys() {
60-
return mProperties.keySet();
61-
}
45+
val propertyKeys: Iterable<String>
46+
get() = _properties.keys
6247

6348
/**
6449
* Gets the property entry set
65-
*
66-
* @return property entry set
6750
*/
68-
public Iterable getProperties() {
69-
return mProperties.entrySet();
70-
}
51+
val properties: Iterable<Map.Entry<String, String>>
52+
get() = _properties.entries
7153

7254
/**
7355
* Gets the value for a stored property
7456
*
7557
* @param property key of the property
7658
* @return value of the property if its key exists, otherwise null
7759
*/
78-
public String getProperty(String property) {
79-
return mProperties.get(property);
80-
}
81-
82-
/**
83-
* Gets the id of the feature
84-
*
85-
* @return id
86-
*/
87-
public String getId() {
88-
return mId;
89-
}
60+
fun getProperty(property: String): String? = _properties[property]
9061

9162
/**
9263
* Checks whether the given property key exists
9364
*
9465
* @param property key of the property to check
9566
* @return true if property key exists, false otherwise
9667
*/
97-
public boolean hasProperty(String property) {
98-
return mProperties.containsKey(property);
99-
}
100-
101-
/**
102-
* Gets the geometry object
103-
*
104-
* @return geometry object
105-
*/
106-
public Geometry getGeometry() {
107-
return mGeometry;
108-
}
68+
fun hasProperty(property: String): Boolean = _properties.containsKey(property)
10969

11070
/**
11171
* Gets whether the placemark has properties
11272
*
11373
* @return true if there are properties in the properties map, false otherwise
11474
*/
115-
public boolean hasProperties() {
116-
return mProperties.size() > 0;
117-
}
75+
fun hasProperties(): Boolean = _properties.isNotEmpty()
11876

11977
/**
12078
* Checks if the geometry is assigned
12179
*
12280
* @return true if feature contains geometry object, otherwise null
12381
*/
124-
public boolean hasGeometry() {
125-
return (mGeometry != null);
126-
}
82+
fun hasGeometry(): Boolean = geometry != null
12783

12884
/**
12985
* Store a new property key and value
@@ -132,8 +88,11 @@ public class Feature extends Observable {
13288
* @param propertyValue value of the property to store
13389
* @return previous value with the same key, otherwise null if the key didn't exist
13490
*/
135-
protected String setProperty(String property, String propertyValue) {
136-
return mProperties.put(property, propertyValue);
91+
protected open fun setProperty(property: String, propertyValue: String): String? {
92+
val prev = _properties.put(property, propertyValue)
93+
setChanged()
94+
notifyObservers()
95+
return prev
13796
}
13897

13998
/**
@@ -142,16 +101,10 @@ public class Feature extends Observable {
142101
* @param property key of the property to remove
143102
* @return value of the removed property or null if there was no corresponding key
144103
*/
145-
protected String removeProperty(String property) {
146-
return mProperties.remove(property);
147-
}
148-
149-
/**
150-
* Sets the stored Geometry and redraws it on the layer if it has already been added
151-
*
152-
* @param geometry Geometry to set
153-
*/
154-
protected void setGeometry(Geometry geometry) {
155-
mGeometry = geometry;
104+
protected open fun removeProperty(property: String): String? {
105+
val prev = _properties.remove(property)
106+
setChanged()
107+
notifyObservers()
108+
return prev
156109
}
157110
}

library/src/main/java/com/google/maps/android/data/geojson/GeoJsonFeature.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ public class GeoJsonFeature extends Feature implements Observer {
5454
public GeoJsonFeature(Geometry geometry, String id,
5555
HashMap<String, String> properties, LatLngBounds boundingBox) {
5656
super(geometry, id, properties);
57-
mId = id;
5857
mBoundingBox = boundingBox;
5958
}
6059

@@ -238,7 +237,7 @@ public String toString() {
238237
sb.append(",\n point style=").append(mPointStyle);
239238
sb.append(",\n line string style=").append(mLineStringStyle);
240239
sb.append(",\n polygon style=").append(mPolygonStyle);
241-
sb.append(",\n id=").append(mId);
240+
sb.append(",\n id=").append(getId());
242241
sb.append(",\n properties=").append(getProperties());
243242
sb.append("\n}\n");
244243
return sb.toString();
Lines changed: 108 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,131 @@
11
/*
2-
* Copyright 2020 Google Inc.
3-
*
2+
* Copyright 2025 Google LLC
3+
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
1212
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package com.google.maps.android.data;
16+
package com.google.maps.android.data
1717

18-
import com.google.android.gms.maps.model.LatLng;
18+
import com.google.android.gms.maps.model.LatLng
19+
import com.google.common.truth.Truth.assertThat
20+
import org.junit.Test
21+
import java.util.Observable
22+
import java.util.Observer
1923

20-
import org.junit.Test;
24+
class FeatureTest {
2125

22-
import java.util.ArrayList;
23-
import java.util.Arrays;
24-
import java.util.HashMap;
25-
import java.util.Map;
26+
// Test subclass to access protected members
27+
private class TestFeature(
28+
geometry: Geometry<*>?,
29+
id: String?,
30+
properties: Map<String, String>?
31+
) : Feature(geometry, id, properties) {
32+
public override var id: String?
33+
get() = super.id
34+
set(value) {
35+
super.id = value
36+
}
2637

27-
import static org.junit.Assert.*;
38+
public override var geometry: Geometry<*>?
39+
get() = super.geometry
40+
set(value) {
41+
super.geometry = value
42+
}
43+
44+
public override fun setProperty(property: String, propertyValue: String): String? {
45+
return super.setProperty(property, propertyValue)
46+
}
47+
48+
public override fun removeProperty(property: String): String? {
49+
return super.removeProperty(property)
50+
}
51+
}
52+
53+
@Test
54+
fun `getId returns correct id`() {
55+
var feature: Feature = Feature(null, "Pirate", null)
56+
assertThat(feature.id).isEqualTo("Pirate")
57+
feature = Feature(null, null, null)
58+
assertThat(feature.id).isNull()
59+
}
60+
61+
@Test
62+
fun `properties work as expected`() {
63+
val properties = mapOf("Color" to "Red", "Width" to "3")
64+
val feature = Feature(null, null, properties)
65+
66+
assertThat(feature.hasProperty("llama")).isFalse()
67+
assertThat(feature.hasProperty("Color")).isTrue()
68+
assertThat(feature.getProperty("Color")).isEqualTo("Red")
69+
assertThat(feature.hasProperties()).isTrue()
70+
assertThat(feature.propertyKeys).containsExactly("Color", "Width")
71+
}
72+
73+
@Test
74+
fun `protected property methods work as expected`() {
75+
val testFeature = TestFeature(null, null, mutableMapOf("Color" to "Red", "Width" to "3"))
76+
77+
assertThat(testFeature.removeProperty("Width")).isEqualTo("3")
78+
assertThat(testFeature.hasProperty("Width")).isFalse()
79+
80+
assertThat(testFeature.setProperty("Width", "10")).isNull()
81+
assertThat(testFeature.getProperty("Width")).isEqualTo("10")
82+
83+
assertThat(testFeature.setProperty("Width", "500")).isEqualTo("10")
84+
assertThat(testFeature.getProperty("Width")).isEqualTo("500")
85+
}
2886

29-
public class FeatureTest {
3087
@Test
31-
public void testGetId() {
32-
Feature feature = new Feature(null, "Pirate", null);
33-
assertNotNull(feature.getId());
34-
assertEquals("Pirate", feature.getId());
35-
feature = new Feature(null, null, null);
36-
assertNull(feature.getId());
88+
fun `geometry works as expected`() {
89+
val feature = Feature(null, null, null)
90+
assertThat(feature.hasGeometry()).isFalse()
91+
assertThat(feature.geometry).isNull()
92+
93+
val point = Point(LatLng(0.0, 0.0))
94+
val featureWithPoint = Feature(point, null, null)
95+
assertThat(featureWithPoint.hasGeometry()).isTrue()
96+
assertThat(featureWithPoint.geometry).isEqualTo(point)
3797
}
3898

3999
@Test
40-
public void testProperty() {
41-
Map<String, String> properties = new HashMap<>();
42-
properties.put("Color", "Red");
43-
properties.put("Width", "3");
44-
Feature feature = new Feature(null, null, properties);
45-
assertFalse(feature.hasProperty("llama"));
46-
assertTrue(feature.hasProperty("Color"));
47-
assertEquals("Red", feature.getProperty("Color"));
48-
assertTrue(feature.hasProperty("Width"));
49-
assertEquals("3", feature.getProperty("Width"));
50-
assertNull(feature.removeProperty("banana"));
51-
assertEquals("3", feature.removeProperty("Width"));
52-
assertNull(feature.setProperty("Width", "10"));
53-
assertEquals("10", feature.setProperty("Width", "500"));
100+
fun `protected geometry setter works`() {
101+
val testFeature = TestFeature(null, null, null)
102+
val point = Point(LatLng(0.0, 0.0))
103+
testFeature.geometry = point
104+
assertThat(testFeature.geometry).isEqualTo(point)
54105
}
55106

56107
@Test
57-
public void testGeometry() {
58-
Feature feature = new Feature(null, null, null);
59-
assertNull(feature.getGeometry());
60-
Point point = new Point(new LatLng(0, 0));
61-
feature.setGeometry(point);
62-
assertEquals(point, feature.getGeometry());
63-
feature.setGeometry(null);
64-
assertNull(feature.getGeometry());
65-
66-
LineString lineString =
67-
new LineString(
68-
new ArrayList<>(Arrays.asList(new LatLng(0, 0), new LatLng(50, 50))));
69-
feature = new Feature(lineString, null, null);
70-
assertEquals(lineString, feature.getGeometry());
71-
feature.setGeometry(point);
72-
assertEquals(point, feature.getGeometry());
73-
feature.setGeometry(null);
74-
assertNull(feature.getGeometry());
75-
feature.setGeometry(lineString);
76-
assertEquals(lineString, feature.getGeometry());
108+
fun `observable notifies on change`() {
109+
val feature = TestFeature(null, null, null)
110+
val observer = TestObserver()
111+
feature.addObserver(observer)
112+
113+
feature.setProperty("key", "value")
114+
assertThat(observer.wasUpdated).isTrue()
115+
observer.wasUpdated = false // reset
116+
117+
feature.removeProperty("key")
118+
assertThat(observer.wasUpdated).isTrue()
119+
observer.wasUpdated = false // reset
120+
121+
feature.geometry = Point(LatLng(1.0, 1.0))
122+
assertThat(observer.wasUpdated).isTrue()
123+
}
124+
125+
class TestObserver : Observer {
126+
var wasUpdated = false
127+
override fun update(o: Observable?, arg: Any?) {
128+
wasUpdated = true
129+
}
77130
}
78-
}
131+
}

0 commit comments

Comments
 (0)