diff --git a/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java b/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java index 7e738d2bd54..8ad52b4e7ee 100644 --- a/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java +++ b/matsim/src/main/java/org/matsim/api/core/v01/IdAnnotations.java @@ -23,20 +23,23 @@ import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; @@ -44,156 +47,91 @@ public interface IdAnnotations { @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside - @JsonSerialize(using = JsonPersonId.PersonIdSerializer.class) - @JsonDeserialize(using = JsonPersonId.PersonIdDeserializer.class) - public @interface JsonPersonId { - - static class PersonIdSerializer extends StdSerializer> { - - protected PersonIdSerializer() { - this(null); - } + @JsonSerialize(using = JsonIdSerializer.class) + @JsonDeserialize(using = JsonIdContextualDeserializer.class) + public @interface JsonId { - protected PersonIdSerializer(Class> vc) { - super(vc); - } + } - @Override - public void serialize(Id value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeString(value.toString()); - } + class JsonIdSerializer extends StdSerializer { + protected JsonIdSerializer() { + this(null); } - static class PersonIdDeserializer extends StdDeserializer> { - - protected PersonIdDeserializer() { - this(null); - } - - protected PersonIdDeserializer(Class vc) { - super(vc); - } - - @Override - public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode node = jp.getCodec().readTree(jp); - return Id.createPersonId(node.asText()); - } - + protected JsonIdSerializer(Class vc) { + super(vc); } - static class PersonIdKeyDeserializer extends KeyDeserializer { - - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { - return Id.createPersonId(key); - } - + @Override + public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeString(value.toString()); } } - @Retention(RetentionPolicy.RUNTIME) - @JacksonAnnotationsInside - @JsonSerialize(using = JsonLinkId.LinkIdSerializer.class) - @JsonDeserialize(using = JsonLinkId.LinkIdDeserializer.class) - public @interface JsonLinkId { - - static class LinkIdSerializer extends StdSerializer> { - - protected LinkIdSerializer() { - this(null); - } - - protected LinkIdSerializer(Class> vc) { - super(vc); - } + class JsonIdContextualDeserializer extends StdDeserializer> implements ContextualDeserializer { - @Override - public void serialize(Id value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeString(value.toString()); - } + private Class idClass; + protected JsonIdContextualDeserializer() { + this(null); } - static class LinkIdDeserializer extends StdDeserializer> { - - protected LinkIdDeserializer() { - this(null); - } + protected JsonIdContextualDeserializer(Class idClass) { + super(Object.class); + this.idClass = idClass; + } - protected LinkIdDeserializer(Class vc) { - super(vc); - } + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) + throws JsonMappingException { - @Override - public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode node = jp.getCodec().readTree(jp); - return Id.createLinkId(node.asText()); + final Class idClass; + { + final JavaType type; + if (property != null) + type = property.getType(); + else { + type = ctxt.getContextualType(); + } + idClass = type.containedType(0).getRawClass(); } + return JsonIdDeserializer.getInstance(idClass); } - static class LinkIdKeyDeserializer extends KeyDeserializer { - - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { - return Id.createLinkId(key); - } - + @Override + public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JacksonException { + JsonNode node = jp.getCodec().readTree(jp); + return Id.create(node.asText(), idClass); } } - @Retention(RetentionPolicy.RUNTIME) - @JacksonAnnotationsInside - @JsonSerialize(using = JsonNodeId.NodeIdSerializer.class) - @JsonDeserialize(using = JsonNodeId.NodeIdDeserializer.class) - public @interface JsonNodeId { - - static class NodeIdSerializer extends StdSerializer> { - - protected NodeIdSerializer() { - this(null); - } - - protected NodeIdSerializer(Class> vc) { - super(vc); - } + class JsonIdDeserializer extends StdDeserializer> { - @Override - public void serialize(Id value, JsonGenerator gen, SerializerProvider provider) throws IOException { - gen.writeString(value.toString()); - } + private static final Map, JsonIdDeserializer> CACHE = new HashMap<>(); + public static JsonIdDeserializer getInstance(Class clazz) { + return CACHE.computeIfAbsent(clazz, k -> new JsonIdDeserializer<>(k)); } - static class NodeIdDeserializer extends StdDeserializer> { - - protected NodeIdDeserializer() { - this(null); - } - - protected NodeIdDeserializer(Class vc) { - super(vc); - } - - @Override - public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { - JsonNode node = jp.getCodec().readTree(jp); - return Id.createNodeId(node.asText()); - } + private final Class idClass; + private JsonIdDeserializer() { + this(null); } - static class NodeIdKeyDeserializer extends KeyDeserializer { - - @Override - public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { - return Id.createNodeId(key); - } + private JsonIdDeserializer(Class idClass) { + super(Object.class); + this.idClass = idClass; + } + @Override + public Id deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + JsonNode node = jp.getCodec().readTree(jp); + return Id.create(node.asText(), idClass); } } diff --git a/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java b/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java index d2778358d6e..c25470b8169 100644 --- a/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java +++ b/matsim/src/main/java/org/matsim/api/core/v01/IdDeSerializationModule.java @@ -1,20 +1,13 @@ package org.matsim.api.core.v01; -import java.util.Collections; +import java.io.IOException; import java.util.HashMap; import java.util.Map; -import org.apache.commons.lang3.tuple.Triple; -import org.matsim.api.core.v01.IdAnnotations.JsonLinkId; -import org.matsim.api.core.v01.IdAnnotations.JsonNodeId; -import org.matsim.api.core.v01.IdAnnotations.JsonPersonId; -import org.matsim.api.core.v01.network.Link; -import org.matsim.api.core.v01.network.Node; -import org.matsim.api.core.v01.population.Person; - import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonMappingException; @@ -26,6 +19,7 @@ import com.fasterxml.jackson.databind.deser.Deserializers; import com.fasterxml.jackson.databind.deser.KeyDeserializers; import com.fasterxml.jackson.databind.ser.Serializers; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; /** * Use as follows with your {@link ObjectMapper} instance: @@ -36,24 +30,6 @@ public class IdDeSerializationModule extends Module { private static final String NAME = IdDeSerializationModule.class.getSimpleName(); private static final Version VERSION = new Version(0, 1, 0, null, "org.matsim", "api.core.v01"); - private static final Map, Triple, JsonDeserializer, KeyDeserializer>> DE_SERIALIZER_MAP; - static { - Map, Triple, JsonDeserializer, KeyDeserializer>> m = new HashMap<>(); - m.put(Person.class, Triple.of( - new JsonPersonId.PersonIdSerializer(), - new JsonPersonId.PersonIdDeserializer(), - new JsonPersonId.PersonIdKeyDeserializer())); - m.put(Node.class, Triple.of( - new JsonNodeId.NodeIdSerializer(), - new JsonNodeId.NodeIdDeserializer(), - new JsonNodeId.NodeIdKeyDeserializer())); - m.put(Link.class, Triple.of( - new JsonLinkId.LinkIdSerializer(), - new JsonLinkId.LinkIdDeserializer(), - new JsonLinkId.LinkIdKeyDeserializer())); - // Add your own classes below here - DE_SERIALIZER_MAP = Collections.unmodifiableMap(m); - } private static Module instance = null; @@ -85,13 +61,16 @@ public void setupModule(SetupContext context) { context.addKeyDeserializers(new IdKeyDeserializers()); } + private static final StdSerializer SERIALIZER = new IdAnnotations.JsonIdSerializer<>(Id.class); + private static final Map, KeyDeserializer> KEY_DESERIALIZER_CACHE = new HashMap<>(); + private static final class IdSerializers extends Serializers.Base { @Override public JsonSerializer findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) { if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { - return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getLeft(); + return SERIALIZER; } return null; } @@ -104,7 +83,7 @@ private static final class IdDeserializers extends Deserializers.Base { public JsonDeserializer findBeanDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { - return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getMiddle(); + return IdAnnotations.JsonIdDeserializer.getInstance(type.containedType(0).getRawClass()); } return null; } @@ -117,7 +96,15 @@ private static final class IdKeyDeserializers implements KeyDeserializers { public KeyDeserializer findKeyDeserializer(JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException { if (type.getRawClass().equals(Id.class) && type.containedTypeCount() == 1) { - return DE_SERIALIZER_MAP.get(type.containedType(0).getRawClass()).getRight(); + return KEY_DESERIALIZER_CACHE.computeIfAbsent(type.containedType(0).getRawClass(), + k -> new KeyDeserializer() { + + @Override + public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException { + return Id.create(key, k); + } + + }); } return null; } diff --git a/matsim/src/test/java/org/matsim/api/core/v01/IdAnnotationsTest.java b/matsim/src/test/java/org/matsim/api/core/v01/IdAnnotationsTest.java index f99a2a6d700..dcfa4db3a27 100644 --- a/matsim/src/test/java/org/matsim/api/core/v01/IdAnnotationsTest.java +++ b/matsim/src/test/java/org/matsim/api/core/v01/IdAnnotationsTest.java @@ -4,9 +4,7 @@ import org.junit.Assert; import org.junit.Test; -import org.matsim.api.core.v01.IdAnnotations.JsonLinkId; -import org.matsim.api.core.v01.IdAnnotations.JsonNodeId; -import org.matsim.api.core.v01.IdAnnotations.JsonPersonId; +import org.matsim.api.core.v01.IdAnnotations.JsonId; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Node; import org.matsim.api.core.v01.population.Person; @@ -22,8 +20,9 @@ public class IdAnnotationsTest { @Test public void testRecordJsonIds() throws JsonProcessingException { + Id personId = Id.createPersonId("person"); RecordWithIds recordWithIds1 = new RecordWithIds( - Id.createPersonId("person"), + personId, Id.createLinkId("link"), Id.createNodeId("node")); @@ -31,22 +30,28 @@ public void testRecordJsonIds() throws JsonProcessingException { RecordWithIds recordWithIds2 = objectMapper.readValue(s, RecordWithIds.class); Assert.assertEquals(recordWithIds1, recordWithIds2); + Assert.assertEquals(personId, recordWithIds2.personId); + Assert.assertSame(personId, recordWithIds2.personId); } @Test public void testRecordJsonIdsWithNull() throws JsonProcessingException { - RecordWithIds recordWithIds1 = new RecordWithIds(null, null, null); + Id personId = null; + RecordWithIds recordWithIds1 = new RecordWithIds(personId, null, null); String s = objectMapper.writeValueAsString(recordWithIds1); RecordWithIds recordWithIds2 = objectMapper.readValue(s, RecordWithIds.class); Assert.assertEquals(recordWithIds1, recordWithIds2); + Assert.assertEquals(personId, recordWithIds2.personId); + Assert.assertSame(personId, recordWithIds2.personId); } @Test public void testClassJsonIds() throws JsonProcessingException { + Id personId = Id.createPersonId("person"); ClassWithIds classWithIds1 = new ClassWithIds( - Id.createPersonId("person"), + personId, Id.createLinkId("link"), Id.createNodeId("node")); @@ -54,35 +59,40 @@ public void testClassJsonIds() throws JsonProcessingException { ClassWithIds classWithIds2 = objectMapper.readValue(s, ClassWithIds.class); Assert.assertEquals(classWithIds1, classWithIds2); + Assert.assertEquals(personId, classWithIds2.personId); + Assert.assertSame(personId, classWithIds2.personId); } @Test public void testClassJsonIdsWithNull() throws JsonProcessingException { - ClassWithIds classWithIds1 = new ClassWithIds(null, null, null); + Id personId = null; + ClassWithIds classWithIds1 = new ClassWithIds(personId, null, null); String s = objectMapper.writeValueAsString(classWithIds1); ClassWithIds classWithIds2 = objectMapper.readValue(s, ClassWithIds.class); Assert.assertEquals(classWithIds1, classWithIds2); + Assert.assertEquals(personId, classWithIds2.personId); + Assert.assertSame(personId, classWithIds2.personId); } @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) private static record RecordWithIds( - @JsonPersonId Id personId, - @JsonLinkId Id linkId, - @JsonNodeId Id nodeId) { + @JsonId Id personId, + @JsonId Id linkId, + @JsonId Id nodeId) { }; @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) private static class ClassWithIds { - @JsonPersonId + @JsonId Id personId; - @JsonLinkId + @JsonId Id linkId; - @JsonNodeId + @JsonId Id nodeId; ClassWithIds() { diff --git a/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java b/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java index 799ca6c8fbf..6550f16c2d4 100644 --- a/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java +++ b/matsim/src/test/java/org/matsim/api/core/v01/IdDeSerializationModuleTest.java @@ -30,8 +30,10 @@ public void testMapKey() { // create map with Id as keys Map, String> map0 = new LinkedHashMap<>(); - map0.put(Id.createLinkId("0"), "a"); - map0.put(Id.createLinkId("1"), "b"); + Id linkId0 = Id.createLinkId("0"); + Id linkId1 = Id.createLinkId("1"); + map0.put(linkId0, "a"); + map0.put(linkId1, "b"); // build writer JavaType linkIdType = TYPE_FACTORY.constructParametricType(Id.class, Link.class); @@ -56,6 +58,10 @@ public void testMapKey() { throw new RuntimeException(e); } Assert.assertEquals(map0, map1); + Assert.assertEquals(linkId0, + map1.keySet().stream().filter(lId -> lId.equals(linkId0)).findFirst().orElseThrow()); + Assert.assertSame(linkId0, + map1.keySet().stream().filter(lId -> lId.equals(linkId0)).findFirst().orElseThrow()); } @Test @@ -63,7 +69,8 @@ public void testMapValue() { // create map with Id as values Map> map0 = new LinkedHashMap<>(); - map0.put("a", Id.createLinkId("0")); + Id linkId0 = Id.createLinkId("0"); + map0.put("a", linkId0); map0.put("b", Id.createLinkId("1")); // build writer @@ -89,6 +96,8 @@ public void testMapValue() { throw new RuntimeException(e); } Assert.assertEquals(map0, map1); + Assert.assertEquals(linkId0, map1.get("a")); + Assert.assertSame(linkId0, map1.get("a")); } }