/**
  * Gets HTML5 parameter type for input field according to {@link Type} annotation.
  *
  * @return the type
  */
 @Override
 public Type getHtmlInputFieldType() {
   final Type ret;
   if (inputAnnotation == null || inputAnnotation.value() == Type.FROM_JAVA) {
     if (isArrayOrCollection() || isRequestBody()) {
       ret = null;
     } else if (DataType.isNumber(getParameterType())) {
       ret = Type.NUMBER;
     } else {
       ret = Type.TEXT;
     }
   } else {
     ret = inputAnnotation.value();
   }
   return ret;
 }
  private void writePossiblePropertyValues(
      JsonGenerator jgen,
      String currentVocab,
      ActionInputParameter actionInputParameter,
      @SuppressWarnings("unused") Object[] possiblePropertyValues)
      throws IOException {
    // Enable the following to list possible values.
    // Problem: how to express individuals only for certain hydra:options
    // not all hydra:options should be taken as uris, sometimes they might be just literals
    // how to make that clear to the client?
    // maybe we must write them out for options
    //        if (possiblePropertyValues.length > 0) {
    //            jgen.writeArrayFieldStart("hydra:option");
    //
    //            for (Object possibleValue : possiblePropertyValues) {
    //                // TODO: apply "hydra:option" : { "@type": "@vocab"} to context for enums
    //                writeScalarValue(jgen, possibleValue,
    // actionInputParameter.getParameterType());
    //            }
    //            jgen.writeEndArray();
    //        }

    if (actionInputParameter.isArrayOrCollection()) {
      jgen.writeBooleanField(
          getPropertyOrClassNameInVocab(
              currentVocab, "multipleValues", LdContextFactory.HTTP_SCHEMA_ORG, "schema:"),
          true);
    }

    //  valueRequired (hard to say, using @Access on Event is for all update requests - or make
    //     specific request beans for different
    //     purposes rather than always passing an instance of e.g. Event?)
    //       -> update is a different use case than create - or maybe have an
    // @Requires("eventStatus")
    //          annotation alongside requestBody to tell which attributes are required or writable,
    // and use Requires over
    //          bean structure, where ctor with least length of args is required and setters are
    // supported
    //          but optional? The bean structure does say what is writable for updates, but not what
    // is required for creation. Right now setters are supportedProperties. For creation we would
    // have to add constructor arguments as supportedProperties.
    //  (/) defaultValue (pre-filled value, e.g. list of selected items for option)
    //  valueName (for iri templates only)
    //  (/) readonlyValue (true for final public field or absence of setter, send fixed value like
    // hidden field?) -> use hydra:readable, hydra:writable
    //  (/) multipleValues
    //  (/) valueMinLength
    //  (/) valueMaxLength
    //  (/) valuePattern
    //  minValue (DateTime support)
    //  maxValue (DateTime support)
    //  (/) stepValue
    final Map<String, Object> inputConstraints = actionInputParameter.getInputConstraints();

    if (actionInputParameter.hasCallValue()) {
      if (actionInputParameter.isArrayOrCollection()) {
        Object[] callValues = actionInputParameter.getCallValues();
        Class<?> componentType = callValues.getClass().getComponentType();
        // only write defaultValue for array of scalars
        if (DataType.isSingleValueType(componentType)) {
          jgen.writeFieldName(
              getPropertyOrClassNameInVocab(
                  currentVocab, "defaultValue", LdContextFactory.HTTP_SCHEMA_ORG, "schema:"));
          jgen.writeStartArray();
          for (Object callValue : callValues) {
            writeScalarValue(jgen, callValue, componentType);
          }
          jgen.writeEndArray();
        }
      } else {
        jgen.writeFieldName(
            getPropertyOrClassNameInVocab(
                currentVocab, "defaultValue", LdContextFactory.HTTP_SCHEMA_ORG, "schema:"));

        writeScalarValue(
            jgen, actionInputParameter.getCallValue(), actionInputParameter.getParameterType());
      }
    }

    if (!inputConstraints.isEmpty()) {
      final List<String> keysToAppendValue = Arrays.asList(Input.MAX, Input.MIN, Input.STEP);
      for (String keyToAppendValue : keysToAppendValue) {
        final Object constraint = inputConstraints.get(keyToAppendValue);
        if (constraint != null) {
          jgen.writeFieldName(
              getPropertyOrClassNameInVocab(
                  currentVocab,
                  keyToAppendValue + "Value",
                  LdContextFactory.HTTP_SCHEMA_ORG,
                  "schema:"));
          jgen.writeNumber(constraint.toString());
        }
      }

      final List<String> keysToPrependValue =
          Arrays.asList(Input.MAX_LENGTH, Input.MIN_LENGTH, Input.PATTERN);
      for (String keyToPrependValue : keysToPrependValue) {
        final Object constraint = inputConstraints.get(keyToPrependValue);
        if (constraint != null) {
          jgen.writeFieldName(
              getPropertyOrClassNameInVocab(
                  currentVocab,
                  "value" + StringUtils.capitalize(keyToPrependValue),
                  LdContextFactory.HTTP_SCHEMA_ORG,
                  "schema:"));
          if (Input.PATTERN.equals(keyToPrependValue)) {
            jgen.writeString(constraint.toString());
          } else {
            jgen.writeNumber(constraint.toString());
          }
        }
      }
    }
  }
  private void recurseSupportedProperties(
      JsonGenerator jgen,
      String currentVocab,
      Class<?> beanType,
      ActionDescriptor actionDescriptor,
      ActionInputParameter actionInputParameter,
      Object currentCallValue)
      throws IntrospectionException, IOException {
    // TODO support Option provider by other method args?
    final BeanInfo beanInfo = Introspector.getBeanInfo(beanType);
    final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    // TODO collection and map

    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      final Method writeMethod = propertyDescriptor.getWriteMethod();
      if (writeMethod == null) {
        continue;
      }
      final Class<?> propertyType = propertyDescriptor.getPropertyType();
      // TODO: the property name must be a valid URI - need to check context for terms?
      String propertyName = getWritableExposedPropertyOrPropertyName(propertyDescriptor);
      if (DataType.isSingleValueType(propertyType)) {

        final Property property =
            new Property(
                beanType,
                propertyDescriptor.getReadMethod(),
                propertyDescriptor.getWriteMethod(),
                propertyDescriptor.getName());

        Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);

        MethodParameter methodParameter =
            new MethodParameter(propertyDescriptor.getWriteMethod(), 0);
        ActionInputParameter propertySetterInputParameter =
            new ActionInputParameter(methodParameter, propertyValue);
        final Object[] possiblePropertyValues =
            actionInputParameter.getPossibleValues(
                propertyDescriptor.getWriteMethod(), 0, actionDescriptor);

        writeSupportedProperty(
            jgen,
            currentVocab,
            propertySetterInputParameter,
            propertyName,
            property,
            possiblePropertyValues);
      } else {
        jgen.writeStartObject();
        jgen.writeStringField("hydra:property", propertyName);
        // TODO: is the property required -> for bean props we need the Access annotation to express
        // that
        jgen.writeObjectFieldStart(
            getPropertyOrClassNameInVocab(
                currentVocab, "rangeIncludes", LdContextFactory.HTTP_SCHEMA_ORG, "schema:"));
        Expose expose = AnnotationUtils.getAnnotation(propertyType, Expose.class);
        String subClass;
        if (expose != null) {
          subClass = expose.value();
        } else {
          subClass = propertyType.getSimpleName();
        }
        jgen.writeStringField(
            getPropertyOrClassNameInVocab(
                currentVocab, "subClassOf", "http://www.w3.org/2000/01/rdf-schema#", "rdfs:"),
            subClass);

        jgen.writeArrayFieldStart("hydra:supportedProperty");

        Object propertyValue = PropertyUtils.getPropertyValue(currentCallValue, propertyDescriptor);

        recurseSupportedProperties(
            jgen,
            currentVocab,
            propertyType,
            actionDescriptor,
            actionInputParameter,
            propertyValue);
        jgen.writeEndArray();

        jgen.writeEndObject();
        jgen.writeEndObject();
      }
    }
  }
 /**
  * Determines if action input parameter is an array or collection.
  *
  * @return true if array or collection
  */
 public boolean isArrayOrCollection() {
   if (arrayOrCollection == null) {
     arrayOrCollection = DataType.isArrayOrCollection(getParameterType());
   }
   return arrayOrCollection;
 }