content: package alien4cloud.rest.utils;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.TypeVariable;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import com.fasterxml.jackson.annotation.JsonInclude;
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.core.type.TypeReference;
  import com.fasterxml.jackson.databind.JavaType;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.SerializationFeature;
  
  import alien4cloud.rest.model.RestResponse;
  import com.fasterxml.jackson.databind.type.ArrayType;
  import com.fasterxml.jackson.databind.type.TypeFactory;
  import com.fasterxml.jackson.databind.util.LRUMap;
  import lombok.extern.slf4j.Slf4j;
  
  /**
   * Simple utility for JSon processing.
   */
  @Slf4j
  public final class JsonUtil {
  
      private static ObjectMapper getNewObjectMapper(boolean writeNullMapValues) {
          ObjectMapper mapper = new ObjectMapper();
          mapper.enable(SerializationFeature.INDENT_OUTPUT);
          mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
          mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, writeNullMapValues);
          return mapper;
      }
  
      private static ObjectMapper createRestMapper() {
          ObjectMapper mapper = new RestMapper();
          mapper.enable(SerializationFeature.INDENT_OUTPUT);
          mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
          mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
          return mapper;
      }
  
      private static ObjectMapper getNewObjectMapper() {
          return getNewObjectMapper(false);
      }
  
      private JsonUtil() {
      }
  
      /**
       * Parse a {@link RestResponse} by using the specified dataType as the expected data object's class.
       *
       * @param responseAsString The {@link RestResponse} as a JSon String
       * @param dataType The type of the data object.
       * @return The parsed {@link RestResponse} object matching the given JSon.
       * @throws IOException In case of an IO error.
       */
      public static <T> RestResponse<T> read(String responseAsString, Class<T> dataType) throws IOException {
          return read(responseAsString, dataType, createRestMapper());
      }
  
      /**
       * Parse a {@link RestResponse} by using the specified dataType as the expected data object's class.
       *
       * @param responseAsString The {@link RestResponse} as a JSon String
       * @param dataType The type of the data object.
       * @param @param mapper the {@link ObjectMapper} to use
       * @return The parsed {@link RestResponse} object matching the given JSon.
       * @throws IOException In case of an IO error.
       */
      public static <T> RestResponse<T> read(String responseAsString, Class<T> dataType, ObjectMapper mapper) throws IOException {
          JavaType innerType = constructType(dataType, mapper.getTypeFactory());
          JavaType restResponseType = mapper.getTypeFactory().constructParametricType(RestResponse.class, innerType);
          return mapper.readValue(responseAsString, restResponseType);
      }
  
      private static <T> JavaType constructType(Class<T> dataType, TypeFactory typeFactory) {
          if (dataType == String.class || dataType == Boolean.TYPE || dataType == Integer.TYPE || dataType == Long.TYPE) {
              return typeFactory.constructSimpleType(dataType, null);
          }
          if (dataType.isArray()) {
              return typeFactory.constructArrayType(dataType.getComponentType());
          } else if (dataType.isEnum()) {
              return typeFactory.constructSimpleType(dataType, new JavaType[0]);
          } else if (Map.class.isAssignableFrom(dataType)) {
              return typeFactory.constructRawMapType((Class<? extends Map>) dataType);
          } else if (Collection.class.isAssignableFrom(dataType)) {
              return typeFactory.constructRawCollectionType((Class<? extends Collection>) dataType);
          }
  
          TypeVariable<Class<T>>[] types = dataType.getTypeParameters();
          JavaType[] javaTypes = new JavaType[types.length];
          for (int i = 0; i < javaTypes.length; i++) {
              javaTypes[i] = TypeFactory.unknownType();
          }
  
          return typeFactory.constructSimpleType(dataType, javaTypes);
      }
  
      /**
       * Parse a {@link RestResponse} without being interested in parameterized type
       *
       * @param responseAsString
       * @return
       * @throws IOException
       */
      public static RestResponse<?> read(String responseAsString) throws IOException {
          return read(responseAsString, createRestMapper());
      }
  
      /**
       * Parse a {@link RestResponse} without being interested in parameterized type
       *
       * @param responseAsString
       * @param mapper the {@link ObjectMapper} to use
       * @return
       * @throws IOException
       */
      public static RestResponse<?> read(String responseAsString, ObjectMapper mapper) throws IOException {
          return mapper.readValue(responseAsString, RestResponse.class);
      }
  
      /**
       * Deserialize json text to object
       *
       * @param objectText
       * @param objectClass
       * @return
       * @throws IOException
       */
      public static <T> T readObject(String objectText, Class<T> objectClass) throws IOException {
          return getNewObjectMapper().readValue(objectText, objectClass);
      }
  
      /**
       * Deserialize json stream to object
       *
       * @param jsonStream
       * @param objectClass
       * @return
       * @throws IOException
       */
      public static <T> T readObject(InputStream jsonStream, Class<T> objectClass) throws IOException {
          return getNewObjectMapper().readValue(jsonStream, objectClass);
      }
  
      /**
       * Deserialize json text to object
       *
       * @param objectText
       * @return
       * @throws IOException
       */
      public static <T> T readObject(String objectText) throws IOException {
          TypeReference<T> typeRef = new TypeReference<T>() {
          };
          return getNewObjectMapper().readValue(objectText, typeRef);
      }
  
      /**
       * Serialize the given object in a JSon String.
       *
       * @param obj The object to serialize.
       * @return The JSon serialization of the given object.
       * @throws JsonProcessingException In case of a failure in serialization.
       */
      public static String toString(Object obj) throws JsonProcessingException {
          return getNewObjectMapper().writeValueAsString(obj);
      }
  
      /**
       * Convert a map or list to an object
       * 
       * @param raw a map or a list
       * @param targetType the target class
       * @param <T> the target
       * @return
       */
      public static <T> T toObject(Object raw, Class<T> targetType) {
          return getNewObjectMapper().convertValue(raw, targetType);
      }
  
      /**
       * Serialize the given object in a JSon String (including null map entries).
       *
       * @param obj
       *            The object to serialize.
       * @return The JSon serialization of the given object.
       * @throws JsonProcessingException
       *             In case of a failure in serialization.
       */
      public static String toVerboseString(Object obj) throws JsonProcessingException {
          return getNewObjectMapper(true).writeValueAsString(obj);
      }
  
      /**
       * Deserialize the given json string to a map
       *
       * @param json json text
       * @return map object
       * @throws IOException
       */
      public static Map<String, Object> toMap(String json) throws IOException {
          ObjectMapper mapper = getNewObjectMapper();
          JavaType mapStringObjectType = mapper.getTypeFactory().constructParametricType(HashMap.class, String.class, Object.class);
          return mapper.readValue(json, mapStringObjectType);
      }
  
      /**
       * Deserialize the given json string to a map
       *
       * @param json
       * @param keyTypeClass
       * @param valueTypeClass
       * @return
       * @throws IOException
       */
      public static <K, V> Map<K, V> toMap(String json, Class<K> keyTypeClass, Class<V> valueTypeClass) throws IOException {
          return toMap(json, keyTypeClass, valueTypeClass, getNewObjectMapper());
      }
  
      /**
       * Deserialize the given json string to a map
       *
       * @param json
       * @param keyTypeClass
       * @param valueTypeClass
       * @param mapper the {@link ObjectMapper} to use
       * @return
       * @throws IOException
       */
      public static <K, V> Map<K, V> toMap(String json, Class<K> keyTypeClass, Class<V> valueTypeClass, ObjectMapper mapper) throws IOException {
          JavaType mapStringObjectType = mapper.getTypeFactory().constructParametricType(HashMap.class, keyTypeClass, valueTypeClass);
          return mapper.readValue(json, mapStringObjectType);
      }
  
      public static <V> V[] toArray(String json, Class<V> valueTypeClass) throws IOException {
          return toArray(json, valueTypeClass, createRestMapper());
      }
  
      public static <V> V[] toArray(String json, Class<V> valueTypeClass, ObjectMapper mapper) throws IOException {
          JavaType arrayStringObjectType = mapper.getTypeFactory().constructArrayType(valueTypeClass);
          return mapper.readValue(json, arrayStringObjectType);
      }
  
      /**
       * Deserialize the given json string to a list
       *
       * @param json json text
       * @return list object
       * @throws IOException
       */
      public static <T> List<T> toList(String json, Class<T> clazz) throws IOException {
          return toList(json, clazz, getNewObjectMapper());
      }
  
      /**
       * Deserialize the given json string to a list
       *
       * @param json json text
       * @param mapper the {@link ObjectMapper} to use
       * @return list object
       * @throws IOException
       */
      public static <T> List<T> toList(String json, Class<T> clazz, ObjectMapper mapper) throws IOException {
          JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, clazz);
          return mapper.readValue(json, type);
      }
  
      public static <T> List<T> toList(String json, Class<T> elementClass, Class<?> elementGenericClass) throws IOException {
          return toList(json, elementClass, elementGenericClass, getNewObjectMapper());
      }
  
      public static <T> List<T> toList(String json, Class<T> elementClass, Class<?> elementGenericClass, ObjectMapper mapper) throws IOException {
          JavaType elementType = mapper.getTypeFactory().constructParametricType(elementClass, elementGenericClass);
          JavaType listType = mapper.getTypeFactory().constructCollectionType(List.class, elementType);
          return mapper.readValue(json, listType);
      }
  }