content: package alien4cloud.json.deserializer;
  
  import java.beans.IntrospectionException;
  import java.beans.Introspector;
  import java.beans.PropertyDescriptor;
  import java.io.IOException;
  import java.lang.reflect.Modifier;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Set;
  
  import org.alien4cloud.tosca.model.definitions.PropertyConstraint;
  import org.alien4cloud.tosca.model.definitions.constraints.InRangeConstraint;
  import org.alien4cloud.tosca.exceptions.InvalidPropertyConstraintImplementationException;
  import alien4cloud.utils.TypeScanner;
  
  import com.fasterxml.jackson.core.JsonParser;
  import com.fasterxml.jackson.databind.DeserializationContext;
  import com.fasterxml.jackson.databind.JsonMappingException;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.PropertyNamingStrategy;
  import com.fasterxml.jackson.databind.PropertyNamingStrategy.PropertyNamingStrategyBase;
  import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  import com.fasterxml.jackson.databind.node.ObjectNode;
  import com.google.common.collect.Maps;
  
  public class PropertyConstraintDeserializer extends StdDeserializer<PropertyConstraint> {
  
      private static final String CONSTRAINT_PACKAGE = "org.alien4cloud.tosca.model.definitions.constraints";
  
      private static final long serialVersionUID = 1L;
  
      private Map<String, Class<? extends PropertyConstraint>> constraints;
  
      // It's a little bit scary to see something like that ?
      /**
       * A cache which map the naming strategy to the mapping of constraint key to constraint class. It's when a specific naming strategy is defined on our
       * desserialization config.
       */
      private Map<PropertyNamingStrategy, Map<String, Class<? extends PropertyConstraint>>> constraintsCache;
  
      @SuppressWarnings("unchecked")
      public PropertyConstraintDeserializer() throws IOException, ClassNotFoundException, IntrospectionException {
          super(PropertyConstraint.class);
          this.constraints = Maps.newHashMap();
          this.constraintsCache = Maps.newConcurrentMap();
  
          // Retrieve all classes which belong to this package
          Set<Class<?>> classes = TypeScanner.scanTypes(CONSTRAINT_PACKAGE, PropertyConstraint.class);
  
          for (Class<?> propClass : classes) {
              if (!Modifier.isAbstract(propClass.getModifiers())) {
                  // Use property as jackson use property
                  PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(propClass).getPropertyDescriptors();
                  String constraintName = null;
                  for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                      if (propertyDescriptor.getReadMethod() != null && propertyDescriptor.getWriteMethod() != null) {
                          constraintName = propertyDescriptor.getName();
                          break;
                      }
                  }
                  if (constraintName != null && !constraintName.isEmpty()) {
                      this.constraints.put(constraintName, (Class<? extends PropertyConstraint>) propClass);
                  } else {
                      // First field name is also constraint name
                      throw new InvalidPropertyConstraintImplementationException(
                              "A constraint validator must have a readable/writable property to inject configuration.");
                  }
              }
          }
      }
  
      @Override
      public PropertyConstraint deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
          ObjectMapper mapper = (ObjectMapper) jp.getCodec();
          ObjectNode node = mapper.readTree(jp);
          String constraintName = null;
          Iterator<String> fieldIterator = node.fieldNames();
          // First field is also the constraint name ?
          if (fieldIterator.hasNext()) {
              constraintName = fieldIterator.next();
          } else {
              throw JsonMappingException.from(jp, "Constraint definition must contain one field");
          }
          PropertyNamingStrategy namingStrategy = mapper.getDeserializationConfig().getPropertyNamingStrategy();
          Map<String, Class<? extends PropertyConstraint>> constraintsMapping = getTranslatedConstraintsMap(namingStrategy);
          if (!constraintsMapping.containsKey(constraintName)) {
              if ("rangeMinValue".equals(constraintName) || "rangeMaxValue".equals(constraintName)) {
                  return mapper.treeToValue(node, InRangeConstraint.class);
              } else {
                  throw JsonMappingException.from(jp, "Constraint not found [" + constraintName + "], expect one of [" + this.constraints.keySet() + "]");
              }
          }
          Class<? extends PropertyConstraint> constraintClass = constraintsMapping.get(constraintName);
          return mapper.treeToValue(node, constraintClass);
      }
  
      private Map<String, Class<? extends PropertyConstraint>> getTranslatedConstraintsMap(PropertyNamingStrategy namingStrategy) {
          if (namingStrategy instanceof PropertyNamingStrategyBase) {
              Map<String, Class<? extends PropertyConstraint>> cachedConstraints = constraintsCache.get(namingStrategy);
              if (cachedConstraints != null) {
                  return cachedConstraints;
              } else {
                  Map<String, Class<? extends PropertyConstraint>> translatedConstraints = Maps.newHashMap();
                  for (Map.Entry<String, Class<? extends PropertyConstraint>> entry : this.constraints.entrySet()) {
                      translatedConstraints.put(((PropertyNamingStrategyBase) namingStrategy).translate(entry.getKey()), entry.getValue());
                  }
                  constraintsCache.put(namingStrategy, translatedConstraints);
                  return translatedConstraints;
              }
          } else {
              return this.constraints;
          }
      }
  }
  