/**
   * Registers a simple formatter.
   *
   * @param clazz class handled by this formatter
   * @param formatter the formatter to register
   */
  public static <T> void register(final Class<T> clazz, final SimpleFormatter<T> formatter) {
    conversion.addFormatterForFieldType(
        clazz,
        new org.springframework.format.Formatter<T>() {

          public T parse(String text, Locale locale) throws java.text.ParseException {
            return formatter.parse(text, locale);
          }

          public String print(T t, Locale locale) {
            return formatter.print(t, locale);
          }

          public String toString() {
            return formatter.toString();
          }
        });
  }
  /** Converter for String -> Option and Option -> String */
  private static <T> void registerOption() {
    conversion.addConverter(
        new GenericConverter() {

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            if (sourceType.getObjectType().equals(String.class)) {
              // From String to Option
              Object element =
                  conversion.convert(source, sourceType, targetType.getElementTypeDescriptor());
              if (element == null) {
                return new None();
              } else {
                return new Some<Object>(element);
              }
            } else if (targetType.getObjectType().equals(String.class)) {
              // Fromt Option to String
              if (source == null) return "";

              Option<?> opt = (Option) source;
              if (!opt.isDefined()) {
                return "";
              } else {
                return conversion.convert(
                    source, sourceType.getElementTypeDescriptor(), targetType);
              }
            }
            return null;
          }

          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<ConvertiblePair> result = new HashSet<ConvertiblePair>();
            result.add(new ConvertiblePair(Option.class, String.class));
            result.add(new ConvertiblePair(String.class, Option.class));
            return result;
          }
        });
  }
  /**
   * Registers an annotation-based formatter.
   *
   * @param clazz class handled by this formatter
   * @param formatter the formatter to register
   */
  @SuppressWarnings("unchecked")
  public static <A extends Annotation, T> void register(
      final Class<T> clazz, final AnnotationFormatter<A, T> formatter) {
    final Class<? extends Annotation> annotationType =
        (Class<? extends Annotation>)
            GenericTypeResolver.resolveTypeArguments(
                formatter.getClass(), AnnotationFormatter.class)[0];

    conversion.addConverter(
        new ConditionalGenericConverter() {
          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<GenericConverter.ConvertiblePair> types =
                new HashSet<GenericConverter.ConvertiblePair>();
            types.add(new GenericConverter.ConvertiblePair(clazz, String.class));
            return types;
          }

          public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
            return (sourceType.getAnnotation(annotationType) != null);
          }

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            final A a = (A) sourceType.getAnnotation(annotationType);
            Locale locale = LocaleContextHolder.getLocale();
            try {
              return formatter.print(a, (T) source, locale);
            } catch (Exception ex) {
              throw new ConversionFailedException(sourceType, targetType, source, ex);
            }
          }

          public String toString() {
            return "@"
                + annotationType.getName()
                + " "
                + clazz.getName()
                + " -> "
                + String.class.getName()
                + ": "
                + formatter;
          }
        });

    conversion.addConverter(
        new ConditionalGenericConverter() {
          public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
            Set<GenericConverter.ConvertiblePair> types =
                new HashSet<GenericConverter.ConvertiblePair>();
            types.add(new GenericConverter.ConvertiblePair(String.class, clazz));
            return types;
          }

          public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
            return (targetType.getAnnotation(annotationType) != null);
          }

          public Object convert(
              Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
            final A a = (A) targetType.getAnnotation(annotationType);
            Locale locale = LocaleContextHolder.getLocale();
            try {
              return formatter.parse(a, (String) source, locale);
            } catch (Exception ex) {
              throw new ConversionFailedException(sourceType, targetType, source, ex);
            }
          }

          public String toString() {
            return String.class.getName()
                + " -> @"
                + annotationType.getName()
                + " "
                + clazz.getName()
                + ": "
                + formatter;
          }
        });
  }