content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.datastax.dse.driver.internal.core.graph;
  
  import com.datastax.dse.driver.api.core.data.geometry.Geometry;
  import com.datastax.dse.driver.api.core.data.geometry.LineString;
  import com.datastax.dse.driver.api.core.data.geometry.Point;
  import com.datastax.dse.driver.api.core.data.geometry.Polygon;
  import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
  import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
  import com.datastax.oss.driver.shaded.guava.common.net.InetAddresses;
  import java.io.IOException;
  import java.net.Inet4Address;
  import java.net.Inet6Address;
  import java.net.InetAddress;
  import java.util.List;
  import java.util.Map;
  import org.apache.tinkerpop.shaded.jackson.core.JsonGenerator;
  import org.apache.tinkerpop.shaded.jackson.core.JsonParseException;
  import org.apache.tinkerpop.shaded.jackson.core.JsonParser;
  import org.apache.tinkerpop.shaded.jackson.core.Version;
  import org.apache.tinkerpop.shaded.jackson.databind.DeserializationContext;
  import org.apache.tinkerpop.shaded.jackson.databind.JsonDeserializer;
  import org.apache.tinkerpop.shaded.jackson.databind.JsonSerializer;
  import org.apache.tinkerpop.shaded.jackson.databind.SerializerProvider;
  import org.apache.tinkerpop.shaded.jackson.databind.deser.std.StdDeserializer;
  import org.apache.tinkerpop.shaded.jackson.databind.module.SimpleModule;
  import org.apache.tinkerpop.shaded.jackson.databind.ser.std.StdSerializer;
  
  public class GraphSON1SerdeTP {
  
    //////////////////////// DESERIALIZERS ////////////////////////
  
    /**
     * Default deserializer used by the driver for {@link InetAddress} instances. The actual subclass
     * returned by this deserializer depends on the type of address: {@link Inet4Address IPV4} or
     * {@link Inet6Address IPV6}.
     */
    static class DefaultInetAddressDeserializer<T extends InetAddress> extends StdDeserializer<T> {
  
      private static final long serialVersionUID = 1L;
  
      private final Class<T> inetClass;
  
      DefaultInetAddressDeserializer(Class<T> inetClass) {
        super(inetClass);
        this.inetClass = inetClass;
      }
  
      @Override
      public T deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
        String ip = parser.readValueAs(String.class);
        try {
          InetAddress inet = InetAddresses.forString(ip);
          return inetClass.cast(inet);
        } catch (ClassCastException e) {
          throw new JsonParseException(
              parser,
              String.format("Inet address cannot be cast to %s: %s", inetClass.getSimpleName(), ip),
              e);
        } catch (IllegalArgumentException e) {
          throw new JsonParseException(parser, String.format("Expected inet address, got %s", ip), e);
        }
      }
    }
  
    /**
     * Default deserializer used by the driver for geospatial types. It deserializes such types into
     * {@link Geometry} instances. The actual subclass depends on the type being deserialized.
     */
    static class DefaultGeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
  
      private static final long serialVersionUID = 1L;
  
      private final Class<T> geometryClass;
  
      DefaultGeometryDeserializer(Class<T> geometryClass) {
        super(geometryClass);
        this.geometryClass = geometryClass;
      }
  
      @Override
      public T deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
        String wkt = parser.readValueAs(String.class);
        Geometry geometry;
        if (wkt.startsWith("POINT")) geometry = Point.fromWellKnownText(wkt);
        else if (wkt.startsWith("LINESTRING")) geometry = LineString.fromWellKnownText(wkt);
        else if (wkt.startsWith("POLYGON")) geometry = Polygon.fromWellKnownText(wkt);
        else throw new JsonParseException(parser, "Unknown geometry type: " + wkt);
        return geometryClass.cast(geometry);
      }
    }
  
    /** Base class for serializing the {@code java.time.*} types to ISO-8061 formats. */
    abstract static class AbstractJavaTimeSerializer<T> extends StdSerializer<T> {
  
      private static final long serialVersionUID = 1L;
  
      AbstractJavaTimeSerializer(final Class<T> clazz) {
        super(clazz);
      }
  
      @Override
      public void serialize(
          final T value, final JsonGenerator gen, final SerializerProvider serializerProvider)
          throws IOException {
        gen.writeString(value.toString());
      }
    }
  
    /** Base class for deserializing the {@code java.time.*} types from ISO-8061 formats. */
    abstract static class AbstractJavaTimeJacksonDeserializer<T> extends StdDeserializer<T> {
  
      private static final long serialVersionUID = 1L;
  
      AbstractJavaTimeJacksonDeserializer(final Class<T> clazz) {
        super(clazz);
      }
  
      abstract T parse(final String val);
  
      @Override
      public T deserialize(
          final JsonParser jsonParser, final DeserializationContext deserializationContext)
          throws IOException {
        return parse(jsonParser.getText());
      }
    }
  
    static final class DurationJacksonSerializer
        extends AbstractJavaTimeSerializer<java.time.Duration> {
  
      private static final long serialVersionUID = 1L;
  
      DurationJacksonSerializer() {
        super(java.time.Duration.class);
      }
    }
  
    static final class DurationJacksonDeserializer
        extends AbstractJavaTimeJacksonDeserializer<java.time.Duration> {
  
      private static final long serialVersionUID = 1L;
  
      DurationJacksonDeserializer() {
        super(java.time.Duration.class);
      }
  
      @Override
      public java.time.Duration parse(final String val) {
        return java.time.Duration.parse(val);
      }
    }
  
    static final class InstantJacksonSerializer
        extends AbstractJavaTimeSerializer<java.time.Instant> {
  
      private static final long serialVersionUID = 1L;
  
      InstantJacksonSerializer() {
        super(java.time.Instant.class);
      }
    }
  
    static final class InstantJacksonDeserializer
        extends AbstractJavaTimeJacksonDeserializer<java.time.Instant> {
  
      private static final long serialVersionUID = 1L;
  
      InstantJacksonDeserializer() {
        super(java.time.Instant.class);
      }
  
      @Override
      public java.time.Instant parse(final String val) {
        return java.time.Instant.parse(val);
      }
    }
  
    static final class LocalDateJacksonSerializer
        extends AbstractJavaTimeSerializer<java.time.LocalDate> {
  
      private static final long serialVersionUID = 1L;
  
      LocalDateJacksonSerializer() {
        super(java.time.LocalDate.class);
      }
    }
  
    static final class LocalDateJacksonDeserializer
        extends AbstractJavaTimeJacksonDeserializer<java.time.LocalDate> {
  
      private static final long serialVersionUID = 1L;
  
      LocalDateJacksonDeserializer() {
        super(java.time.LocalDate.class);
      }
  
      @Override
      public java.time.LocalDate parse(final String val) {
        return java.time.LocalDate.parse(val);
      }
    }
  
    static final class LocalTimeJacksonSerializer
        extends AbstractJavaTimeSerializer<java.time.LocalTime> {
  
      private static final long serialVersionUID = 1L;
  
      LocalTimeJacksonSerializer() {
        super(java.time.LocalTime.class);
      }
    }
  
    static final class LocalTimeJacksonDeserializer
        extends AbstractJavaTimeJacksonDeserializer<java.time.LocalTime> {
  
      private static final long serialVersionUID = 1L;
  
      LocalTimeJacksonDeserializer() {
        super(java.time.LocalTime.class);
      }
  
      @Override
      public java.time.LocalTime parse(final String val) {
        return java.time.LocalTime.parse(val);
      }
    }
  
    //////////////////////// SERIALIZERS ////////////////////////
  
    /** Default serializer used by the driver for {@link LegacyGraphNode} instances. */
    static class DefaultGraphNodeSerializer extends StdSerializer<LegacyGraphNode> {
  
      private static final long serialVersionUID = 1L;
  
      DefaultGraphNodeSerializer() {
        super(LegacyGraphNode.class);
      }
  
      @Override
      public void serialize(
          LegacyGraphNode value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
          throws IOException {
        jsonGenerator.writeTree(value.getDelegate());
      }
    }
  
    /**
     * Default serializer used by the driver for geospatial types. It serializes {@link Geometry}
     * instances into their Well-Known Text (WKT) equivalent.
     */
    static class DefaultGeometrySerializer extends StdSerializer<Geometry> {
  
      private static final long serialVersionUID = 1L;
  
      DefaultGeometrySerializer() {
        super(Geometry.class);
      }
  
      @Override
      public void serialize(
          Geometry value, JsonGenerator jsonGenerator, SerializerProvider serializers)
          throws IOException {
        jsonGenerator.writeString(value.asWellKnownText());
      }
    }
  
    /** The default Jackson module used by DSE Graph. */
    static class GraphSON1DefaultModule extends SimpleModule {
  
      private static final long serialVersionUID = 1L;
  
      GraphSON1DefaultModule(String name, Version version) {
        super(name, version, createDeserializers(), createSerializers());
      }
  
      private static Map<Class<?>, JsonDeserializer<?>> createDeserializers() {
  
        return ImmutableMap.<Class<?>, JsonDeserializer<?>>builder()
  
            // Inet (there is no built-in deserializer for InetAddress and subclasses)
            .put(InetAddress.class, new DefaultInetAddressDeserializer<>(InetAddress.class))
            .put(Inet4Address.class, new DefaultInetAddressDeserializer<>(Inet4Address.class))
            .put(Inet6Address.class, new DefaultInetAddressDeserializer<>(Inet6Address.class))
  
            // Geospatial types
            .put(Geometry.class, new DefaultGeometryDeserializer<>(Geometry.class))
            .put(Point.class, new DefaultGeometryDeserializer<>(Point.class))
            .put(LineString.class, new DefaultGeometryDeserializer<>(LineString.class))
            .put(Polygon.class, new DefaultGeometryDeserializer<>(Polygon.class))
            .build();
      }
  
      private static List<JsonSerializer<?>> createSerializers() {
        return ImmutableList.<JsonSerializer<?>>builder()
            .add(new DefaultGraphNodeSerializer())
            .add(new DefaultGeometrySerializer())
            .build();
      }
    }
  
    /** Serializers and deserializers for JSR 310 {@code java.time.*}. */
    static class GraphSON1JavaTimeModule extends SimpleModule {
  
      private static final long serialVersionUID = 1L;
  
      GraphSON1JavaTimeModule(String name, Version version) {
        super(name, version, createDeserializers(), createSerializers());
      }
  
      private static Map<Class<?>, JsonDeserializer<?>> createDeserializers() {
  
        return ImmutableMap.<Class<?>, JsonDeserializer<?>>builder()
            .put(java.time.Duration.class, new DurationJacksonDeserializer())
            .put(java.time.Instant.class, new InstantJacksonDeserializer())
            .put(java.time.LocalDate.class, new LocalDateJacksonDeserializer())
            .put(java.time.LocalTime.class, new LocalTimeJacksonDeserializer())
            .build();
      }
  
      private static List<JsonSerializer<?>> createSerializers() {
        return ImmutableList.<JsonSerializer<?>>builder()
            .add(new DurationJacksonSerializer())
            .add(new InstantJacksonSerializer())
            .add(new LocalDateJacksonSerializer())
            .add(new LocalTimeJacksonSerializer())
            .build();
      }
    }
  }
  