Commit 698a0669 authored by Hallvard Trætteberg's avatar Hallvard Trætteberg

Merge branch 'issue-9-LatLong-meta-data'

parents feec8dcc cf713a59
Pipeline #52846 passed with stage
in 2 minutes and 11 seconds
......@@ -15,6 +15,8 @@ public class LatLong {
private final double latitude;
private final double longitude;
private MetaData metaData;
/**
* Initialize a LatLong with provided latitude and longitude.
* @param latitude the latitude
......@@ -177,4 +179,24 @@ public class LatLong {
private static double rad2deg(final double rad) {
return (rad * 180 / Math.PI);
}
/**
* Checks if this object has meta data
* @return true if this LatLong object has meta data, false otherwise
*/
public boolean hasMetaData() {
return metaData != null && (! metaData.isEmpty());
}
/**
* Gets the meta data of this object. Will create it if missing,
* so it will always be safe to chain a call.
* @return the meta data of this object
*/
public MetaData getMetaData() {
if (metaData == null) {
metaData = new MetaData();
}
return metaData;
}
}
package simpleex.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class MetaData {
/**
* Name property name
*/
public final static String NAME_PROPERTY = "name";
/**
* Description property name
*/
public final static String DESCRIPTION_PROPERTY = "description";
private Collection<String> tags;
private List<String> properties;
/**
* Tells if there are tags and/or properties in this object.
* @return true if there are tags and/or properties in this object, false otherwise
*/
public boolean isEmpty() {
return (tags == null || tags.isEmpty()) && (properties == null || properties.isEmpty());
}
/**
* Gets the iterator for the tags.
* @return an iterator for the tags
*/
public Iterator<String> tags() {
return (tags != null ? tags.iterator() : Collections.emptyIterator());
}
/**
* Checks if all the provided tags are present.
* @param tags the tags to check
* @return true if all the provided tags are present, false otherwise
*/
public boolean hasTags(final String... tags) {
if (this.tags == null) {
return tags.length == 0;
}
for (final String tag : tags) {
if (! this.tags.contains(tag)) {
return false;
}
}
return true;
}
/**
* Sets the tags to those provided (old ones are removed).
* @param tags the new tags
*/
public void setTags(final String... tags) {
if (tags.length == 0) {
this.tags = null;
} else {
if (this.tags != null) {
this.tags.clear();
} else {
this.tags = new ArrayList<>();
}
// cannot use addAll, since tags may contain duplicates
addTags(tags);
}
}
/**
* Adds the provided tags.
* @param tags the tags to add
*/
public void addTags(final String... tags) {
if (this.tags == null && tags.length > 0) {
this.tags = new ArrayList<>();
}
// avoid duplicates
for (final String tag : tags) {
if (! this.tags.contains(tag)) {
this.tags.add(tag);
}
}
}
/**
* Removes the provided tags.
* @param tags the tags to remove
*/
public void removeTags(final String... tags) {
if (this.tags != null) {
this.tags.removeAll(Arrays.asList(tags));
}
if (this.tags.isEmpty()) {
this.tags = null;
}
}
/**
* Gets the iterator for the tags.
* @return an iterator for the tags
*/
public Iterator<String> propertyNames() {
if (properties == null || properties.isEmpty()) {
return Collections.emptyIterator();
}
return new Iterator<String>() {
int pos = 0;
@Override
public boolean hasNext() {
return properties == null || pos < properties.size();
}
@Override
public String next() {
final String propertyName = properties.get(pos);
pos += 2;
return propertyName;
}
@Override
public void remove() {
pos -= 2;
removeProperty(properties.get(pos));
}
};
}
private int indexOfProperty(final String propertyName) {
if (properties != null) {
for (int i = 0; i < properties.size(); i += 2) {
if (propertyName.equals(properties.get(i))) {
return i;
}
}
}
return -1;
}
/**
* Checks if a property with the provided name is present.
* @param propertyName the name to check
* @return true if the property is present, false otherwise
*/
public boolean hasProperty(final String propertyName) {
return indexOfProperty(propertyName) >= 0;
}
/**
* Gets the property value for the provided property name.
* @param propertyName the property name
* @return the value for the provided property name
*/
public String getProperty(final String propertyName) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
return properties.get(pos + 1);
}
return null;
}
/**
* Gets the property value for the provided property name, as an int.
* If the property value doesn't exist or isn't a valid integer, def is returned.
* @param propertyName the property name
* @return the value for the provided property name, as an int, or def it is missing
*/
public int getIntegerProperty(final String propertyName, final int def) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
try {
return Integer.valueOf(properties.get(pos + 1));
} catch (final NumberFormatException e) {
}
}
return def;
}
/**
* Gets the property value for the provided property name, as a double.
* If the property value doesn't exist or isn't a valid double, def is returned.
* @param propertyName the property name
* @return the value for the provided property name, as a double, or def it is missing
*/
public double getDoubleProperty(final String propertyName, final double def) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
try {
return Double.valueOf(properties.get(pos + 1));
} catch (final NumberFormatException e) {
}
}
return def;
}
/**
* Gets the property value for the provided property name, as a boolean.
* If the property value doesn't exist, def is returned.
* @param propertyName the property name
* @return the value for the provided property name, as a boolean, or def it is missing
*/
public boolean getBooleanProperty(final String propertyName, final boolean def) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
return Boolean.valueOf(properties.get(pos + 1));
}
return def;
}
/**
* Sets the property value for the provided property name.
* @param propertyName the property name
* @param propertyValue the (new) property value
*/
public void setProperty(final String propertyName, final String propertyValue) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
properties.set(pos + 1, propertyValue);
} else {
if (properties == null) {
properties = new ArrayList<>();
}
// add the property name
properties.add(propertyName);
// and the value
properties.add(propertyValue);
}
}
/**
* Convenience method for setting integer property.
* @param propertyName the property name
* @param propertyValue the (new) property value
*/
public void setIntegerProperty(final String propertyName, final int value) {
setProperty(propertyName, Integer.toString(value));
}
/**
* Convenience method for setting integer property.
* @param propertyName the property name
* @param propertyValue the (new) property value
*/
public void setDoubleProperty(final String propertyName, final double value) {
setProperty(propertyName, Double.toString(value));
}
/**
* Convenience method for setting boolean property.
* @param propertyName the property name
* @param propertyValue the (new) property value
*/
public void setBooleanProperty(final String propertyName, final boolean value) {
setProperty(propertyName, Boolean.toString(value));
}
/**
* Removes the property with the provided name.
* @param propertyName the property to remove.
*/
public void removeProperty(final String propertyName) {
final int pos = indexOfProperty(propertyName);
if (pos >= 0) {
// remove the property value
properties.remove(pos + 1);
// and the name
properties.remove(pos);
if (properties.isEmpty()) {
properties = null;
}
}
}
}
......@@ -2,8 +2,9 @@
Domenelaget utgjøres av en samling av geo-lokasjoner representert vha. to klasse:
- LatLong - en geo-lokasjon, representert vha. lengde og breddegrad
- LatLongs - en samling LatLong-objekter
- **LatLong** - en geo-lokasjon, representert vha. lengde og breddegrad
- **LatLongs** - en samling LatLong-objekter
- **MetaData** - tags og properties (nøkkel-verdi-par) som kan knyttes til **LatLong**-objekter
```plantuml
class LatLong {
......@@ -12,4 +13,6 @@ class LatLong {
}
class LatLongs
LatLongs *--> "*" LatLong: "latLongs"
class MetaData
LatLong *--> "1" MetaData: "metaData"
```
......@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import simpleex.core.LatLong;
import simpleex.core.MetaData;
/**
* JSON serializer for LatLong.
......@@ -36,7 +37,24 @@ public class LatLongDeserializer extends JsonDeserializer<LatLong> {
final ObjectNode objectNode = (ObjectNode) jsonNode;
final double latitude = objectNode.get(LatLongSerializer.LATITUDE_FIELD_NAME).asDouble();
final double longitude = objectNode.get(LatLongSerializer.LONGITUDE_FIELD_NAME).asDouble();
return new LatLong(latitude, longitude);
final LatLong latLon = new LatLong(latitude, longitude);
if (objectNode.has(LatLongSerializer.META_DATA_FIELD_NAME)) {
final ObjectNode metaDataNode = (ObjectNode) objectNode.get(LatLongSerializer.META_DATA_FIELD_NAME);
final MetaData metaData = latLon.getMetaData();
if (metaDataNode.has(LatLongSerializer.TAGS_FIELD_NAME)) {
for (final JsonNode tagNode : (ArrayNode) metaDataNode.get(LatLongSerializer.TAGS_FIELD_NAME)) {
metaData.addTags(tagNode.asText());
}
}
if (metaDataNode.has(LatLongSerializer.PROPERTIES_FIELD_NAME)) {
for (final JsonNode propertyNode : (ArrayNode) metaDataNode.get(LatLongSerializer.PROPERTIES_FIELD_NAME)) {
final String propertyName = (propertyNode instanceof ArrayNode ? ((ArrayNode) propertyNode).get(0) : ((ObjectNode) propertyNode).get(LatLongSerializer.PROPERTIES_NAME_FIELD_NAME)).asText();
final String propertyValue = (propertyNode instanceof ArrayNode ? ((ArrayNode) propertyNode).get(1) : ((ObjectNode) propertyNode).get(LatLongSerializer.PROPERTIES_VALUE_FIELD_NAME)).asText();
metaData.setProperty(propertyName, propertyValue);
}
}
}
return latLon;
} else if (jsonNode instanceof ArrayNode) {
final ArrayNode locationArray = (ArrayNode) jsonNode;
if (locationArray.size() == ARRAY_JSON_NODE_SIZE) {
......
......@@ -4,12 +4,19 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.Iterator;
import simpleex.core.LatLong;
import simpleex.core.MetaData;
public class LatLongSerializer extends JsonSerializer<LatLong> {
public static final String LONGITUDE_FIELD_NAME = "longitude";
public static final String LATITUDE_FIELD_NAME = "latitude";
public static final String META_DATA_FIELD_NAME = "metaData";
public static final String TAGS_FIELD_NAME = "tags";
public static final String PROPERTIES_FIELD_NAME = "properties";
public static final String PROPERTIES_NAME_FIELD_NAME = "name";
public static final String PROPERTIES_VALUE_FIELD_NAME = "value";
@Override
public void serialize(final LatLong latLon, final JsonGenerator jsonGen,
......@@ -19,6 +26,38 @@ public class LatLongSerializer extends JsonSerializer<LatLong> {
jsonGen.writeNumber(latLon.getLatitude());
jsonGen.writeFieldName(LONGITUDE_FIELD_NAME);
jsonGen.writeNumber(latLon.getLongitude());
// serialize meta-data
if (latLon.hasMetaData()) {
jsonGen.writeFieldName(META_DATA_FIELD_NAME);
jsonGen.writeStartObject();
final MetaData metaData = latLon.getMetaData();
final Iterator<String> tags = metaData.tags();
if (tags.hasNext()) {
jsonGen.writeFieldName(TAGS_FIELD_NAME);
jsonGen.writeStartArray();
while (tags.hasNext()) {
jsonGen.writeString(tags.next());
}
jsonGen.writeEndArray();
}
final Iterator<String> propertyNames = metaData.propertyNames();
if (propertyNames.hasNext()) {
jsonGen.writeFieldName(PROPERTIES_FIELD_NAME);
jsonGen.writeStartArray();
while (propertyNames.hasNext()) {
jsonGen.writeStartObject();
jsonGen.writeFieldName(PROPERTIES_NAME_FIELD_NAME);
final String propertyName = propertyNames.next();
jsonGen.writeString(propertyName);
jsonGen.writeFieldName(PROPERTIES_VALUE_FIELD_NAME);
jsonGen.writeString(metaData.getProperty(propertyName));
jsonGen.writeEndObject();
}
jsonGen.writeEndArray();
}
jsonGen.writeEndObject();
}
jsonGen.writeEndObject();
}
}
......@@ -68,4 +68,18 @@ public class LatLongTest {
private void checkDistance(final double d, final double lower, final double upper) {
Assert.assertTrue(d + " isn't between " + lower + " and " + upper, d <= upper && d >= lower);
}
@Test
public void testHasGetMetaData() {
final LatLong latLong = new LatLong(63.0, 10.0);
Assert.assertFalse(latLong.hasMetaData());
final MetaData metaData = latLong.getMetaData();
Assert.assertNotNull(metaData);
Assert.assertFalse(latLong.hasMetaData());
metaData.addTags("aTag");
Assert.assertTrue(latLong.hasMetaData());
Assert.assertSame(metaData, latLong.getMetaData());
metaData.removeTags("aTag");
Assert.assertFalse(latLong.hasMetaData());
}
}
package simpleex.core;
import java.util.Arrays;
import java.util.Iterator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class MetaDataTest {
private MetaData metaData;
@Before
public void setUp() {
metaData = new MetaData();
}
@Test
public void testHasTags() {
Assert.assertTrue(metaData.hasTags());
Assert.assertFalse(metaData.hasTags("aTag"));
metaData.addTags("aTag", "bTag");
Assert.assertTrue(metaData.hasTags());
Assert.assertTrue(metaData.hasTags("aTag"));
Assert.assertFalse(metaData.hasTags("aTag", "cTag"));
metaData.removeTags("aTag");
Assert.assertTrue(metaData.hasTags());
Assert.assertFalse(metaData.hasTags("aTag"));
Assert.assertTrue(metaData.hasTags("bTag"));
}
// relies on a certain order
private void check(final Iterator<String> it, final String... ss) {
final Iterator<String> it1 = Arrays.asList(ss).iterator();
while (it1.hasNext() && it.hasNext()) {
Assert.assertEquals(it1.next(), it.next());
}
Assert.assertEquals(it1.hasNext(), it.hasNext());
}
// relies on a certain order
private void checkTags(final String... tags) {
check(metaData.tags(), tags);
}
@Test
public void testMetaData() {
Assert.assertTrue(metaData.isEmpty());
checkTags();
}
@Test
public void testSetTags() {
metaData.setTags("aTag", "bTag");
checkTags("aTag", "bTag");
metaData.setTags("cTag", "bTag");
checkTags("cTag", "bTag");
metaData.setTags();
checkTags();
}
@Test
public void testAddRemoveTags() {
metaData.addTags("aTag", "bTag");
checkTags("aTag", "bTag");
metaData.addTags("cTag");
checkTags("aTag", "bTag", "cTag");
metaData.removeTags("bTag");
checkTags("aTag", "cTag");
metaData.removeTags("cTag");
checkTags("aTag");
metaData.removeTags("aTag");
Assert.assertTrue(metaData.isEmpty());
}
@Test
public void testHasProperty() {
Assert.assertFalse(metaData.hasProperty("aProp"));
metaData.setProperty("aProp", "aValue");
Assert.assertTrue(metaData.hasProperty("aProp"));
}
@Test
public void testGetSetProperty() {
metaData.setProperty("aProp", "aValue");
Assert.assertEquals("aValue", metaData.getProperty("aProp"));
metaData.setProperty("bProp", "bValue");
Assert.assertEquals("bValue", metaData.getProperty("bProp"));
metaData.setProperty("aProp", "anotherValue");
Assert.assertEquals("anotherValue", metaData.getProperty("aProp"));
Assert.assertEquals(-1, metaData.getIntegerProperty("iProp", -1));
metaData.setProperty("iProp", "notAnInteger");
Assert.assertEquals(-1, metaData.getIntegerProperty("dProp", -1));
metaData.setIntegerProperty("iProp", 42);
Assert.assertEquals(42, metaData.getIntegerProperty("iProp", -1));
Assert.assertEquals(-1.0, metaData.getDoubleProperty("dProp", -1.0), 0.0);
metaData.setProperty("dProp", "notADouble");
Assert.assertEquals(-1.0, metaData.getDoubleProperty("dProp", -1.0), 0.0);
metaData.setDoubleProperty("dProp", 42.0);
Assert.assertEquals(42.0, metaData.getDoubleProperty("dProp", -1.0), 0.0);
Assert.assertEquals(false, metaData.getBooleanProperty("bProp", false));
metaData.setProperty("bProp", "notABoolean");
Assert.assertEquals(false, metaData.getBooleanProperty("bProp", true));
metaData.setBooleanProperty("bProp", true);
Assert.assertEquals(true, metaData.getBooleanProperty("bProp", false));
}
@Test
public void testSetRemoveProperty() {
metaData.setProperty("aProp", "aValue");
Assert.assertTrue(metaData.hasProperty("aProp"));
metaData.removeProperty("aProp");
Assert.assertFalse(metaData.hasProperty("aProp"));
Assert.assertTrue(metaData.isEmpty());
}
// relies on a certain order
private void checkPropertyNames(final String... propertyNames) {
check(metaData.propertyNames(), propertyNames);
}
@Test
public void testPropertyNames() {
checkPropertyNames();
metaData.setProperty("aProp", "aValue");
checkPropertyNames("aProp");
metaData.removeProperty("aProp");
checkPropertyNames();
}
}
package simpleex.json;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.Iterator;
import org.junit.Assert;
import org.junit.Test;
import simpleex.core.LatLong;
import simpleex.core.LatLongs;
import simpleex.core.MetaData;
public class LatLongsJsonTest {
......@@ -43,4 +47,66 @@ public class LatLongsJsonTest {
Assert.assertEquals(latLong1(), latLongs.getLatLong(0));
Assert.assertEquals(latLong2(), latLongs.getLatLong(1));
}
@Test
public void testLatLongMetaDataSerialization() throws Exception {
final LatLong latLong = latLong1();
final MetaData metaData = latLong.getMetaData();
metaData.addTags("aTag", "bTag");
metaData.setProperty("aProperty", "aValue");
final String actualJson = objectMapper.writeValueAsString(latLong);
final String expectedJson = "{\"latitude\":63.1,\"longitude\":12.3,"
+ "\"metaData\":{\"tags\":[\"aTag\",\"bTag\"],\"properties\":[{\"name\":\"aProperty\",\"value\":\"aValue\"}]}}";
assertEqualsIgnoreWhitespace(expectedJson, actualJson);
}
@Test
public void testLatLongsMetaDataSerialization() {
final LatLong latLong = latLong1();
final MetaData metaData = latLong.getMetaData();
metaData.addTags("aTag", "bTag");
metaData.setProperty("aProperty", "aValue");
final LatLongs latLongs = new LatLongs(latLong);
try {
final String actualJson = objectMapper.writeValueAsString(latLongs);
final String expectedJson = "[{\"latitude\":63.1,\"longitude\":12.3,"
+ "\"metaData\":{\"tags\":[\"aTag\",\"bTag\"],\"properties\":[{\"name\":\"aProperty\",\"value\":\"aValue\"}]}}]";
assertEqualsIgnoreWhitespace(expectedJson, actualJson);
} catch (final JsonProcessingException e) {
Assert.fail();
} catch (final Exception e) {
Assert.fail();
}
}
@Test
public void testLatLongsMetaDataDeserialization() throws Exception {
final String json = "[{\"latitude\":63.1,\"longitude\":12.3,"
+ "\"metaData\":{\"tags\":[\"aTag\",\"bTag\"],\"properties\":[{\"name\":\"aProperty\",\"value\":\"aValue\"}]}}]";
final LatLongs latLongs = objectMapper.readValue(json, LatLongs.class);
Assert.assertEquals(1, latLongs.getLatLongCount());
Assert.assertTrue(latLongs.getLatLong(0).hasMetaData());
}
// relies on a certain order
private void check(final Iterator<String> it, final String... ss) {
final Iterator<String> it1 = Arrays.asList(ss).iterator();
while (it1.hasNext() && it.hasNext()) {
Assert.assertEquals(it1.next(), it.next());
}
Assert.assertEquals(it1.hasNext(), it.hasNext());
}
@Test
public void testLatLongMetaDataDeserialization() throws Exception {
final String json = "{\"latitude\":63.1,\"longitude\":12.3,"
+ "\"metaData\":{\"tags\":[\"aTag\",\"bTag\"],\"properties\":[{\"name\":\"aProperty\",\"value\":\"aValue\"},[\"bProperty\",\"bValue\"]]}}";
final LatLong latLong = objectMapper.readValue(json, LatLong.class);
final MetaData metaData = latLong.getMetaData();
Assert.assertTrue(metaData.hasTags("aTag", "bTag"));
Assert.assertEquals("aValue", metaData.getProperty("aProperty"));
Assert.assertEquals("bValue", metaData.getProperty("bProperty"));
check(metaData.tags(), "aTag", "bTag");
check(metaData.propertyNames(), "aProperty", "bProperty");
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment