/**
 * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */

package com.opengamma.language.convert;

import static com.opengamma.language.convert.TypeMap.MINOR_LOSS;
import static com.opengamma.language.convert.TypeMap.ZERO_LOSS;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.opengamma.language.definition.JavaTypeInfo;
import com.opengamma.language.invoke.AbstractTypeConverter;

/**
 * Converts enums to/from strings.  
 */
public final class EnumConverter extends AbstractTypeConverter {

  /**
   * Default instance.
   */
  public static final EnumConverter INSTANCE = new EnumConverter();

  @SuppressWarnings("unchecked")
  private static final JavaTypeInfo<Enum> ENUM = JavaTypeInfo.builder(Enum.class).get();
  private static final JavaTypeInfo<String> STRING = JavaTypeInfo.builder(String.class).get();
  private static final Map<JavaTypeInfo<?>, Integer> TO_STRING = TypeMap.of(ZERO_LOSS, ENUM);
  private static final Map<JavaTypeInfo<?>, Integer> TO_ENUM = TypeMap.of(MINOR_LOSS, STRING);

  private final ConcurrentMap<Class<?>, Map<String, Enum<?>>> _enumValues = new ConcurrentHashMap<Class<?>, Map<String, Enum<?>>>();

  protected EnumConverter() {
  }

  @Override
  public synchronized boolean canConvertTo(final JavaTypeInfo<?> targetType) {
    return targetType.getRawClass().isEnum() || (targetType.getRawClass() == String.class);
  }

  @SuppressWarnings("unchecked")
  @Override
  public void convertValue(final ValueConversionContext conversionContext, final Object value, final JavaTypeInfo<?> type) {
    if (type.getRawClass() == String.class) {
      conversionContext.setResult(value.toString());
    } else {
      final String str = (String) value;
      final Class<? extends Enum> enumClass = (Class<? extends Enum>) type.getRawClass();
      Enum<?> enumValue;
      try {
        enumValue = Enum.valueOf(enumClass, str);
      } catch (IllegalArgumentException e) {
        Map<String, Enum<?>> cache = _enumValues.get(type.getRawClass());
        if (cache == null) {
          cache = new HashMap<String, Enum<?>>();
          for (Enum<?> enumEntry : enumClass.getEnumConstants()) {
            cacheAlternativeEnumValue(cache, enumEntry);
          }
          _enumValues.putIfAbsent(type.getRawClass(), cache);
        }
        enumValue = cache.get(getAlternativeString(str));
        if (enumValue == null) {
          conversionContext.setFail();
          return;
        }
      }
      conversionContext.setResult(enumValue);
    }
  }

  /**
   * Converts the raw string so that it may match one of the alternative enum values. The default implementation here converts
   * it to lower case to match the behavior of {@link #cacheAlternativeEnumValue}.
   * 
   * @param str original string
   * @return the alternative form of the string
   */
  protected String getAlternativeString(final String str) {
    return str.trim().toLowerCase();
  }

  /**
   * Inserts alternative string representations of the enum value into a cache. The default implementation here converts it to
   * lower case to match the behavior of {@link #getAlternativeString} and inserts a form with underscores converted to spaces.
   * 
   * @param cache cache to update, mapping alternative strings to enum values
   * @param enumEntry the enum value to cache
   */
  protected void cacheAlternativeEnumValue(final Map<String, Enum<?>> cache, final Enum<?> enumEntry) {
    final String name = enumEntry.name().toLowerCase();
    cache.put(name, enumEntry);
    if (name.indexOf('_') >= 0) {
      final String withSpaces = name.replace('_', ' ').trim();
      cache.put(withSpaces, enumEntry);
    }
  }

  @Override
  public Map<JavaTypeInfo<?>, Integer> getConversionsTo(final JavaTypeInfo<?> targetType) {
    if (targetType.getRawClass() == String.class) {
      return TO_STRING;
    } else {
      return TO_ENUM;
    }
  }

}

@@ -887,32 +887,171 @@
 m.class).get();%0A
+  @SuppressWarnings(%22unchecked%22)%0A  private static final JavaTypeInfo%3CEnum%3E ENUM_NULL = JavaTypeInfo.builder(Enum.class).allowNull().get();%0A
   private static
@@ -1121,32 +1121,144 @@
 g.class).get();%0A
+  private static final JavaTypeInfo%3CString%3E STRING_NULL = JavaTypeInfo.builder(String.class).allowNull().get();%0A
   private static
@@ -1395,44 +1395,253 @@
  TO_
-ENUM = TypeMap.of(MINOR_LOSS, STRING
+STRING_NULL = TypeMap.of(ZERO_LOSS, ENUM_NULL);%0A  private static final Map%3CJavaTypeInfo%3C?%3E, Integer%3E TO_ENUM = TypeMap.of(MINOR_LOSS, STRING);%0A  private static final Map%3CJavaTypeInfo%3C?%3E, Integer%3E TO_ENUM_NULL = TypeMap.of(MINOR_LOSS, STRING_NULL
 );%0A%0A
@@ -4446,44 +4446,130 @@
 urn 
-TO_STRING;%0A    %7D else %7B%0A      return
+targetType.isAllowNull() ? TO_STRING_NULL : TO_STRING;%0A    %7D else %7B%0A      return targetType.isAllowNull() ? TO_ENUM_NULL :
  TO_

Allow NULL conversions when translating enums to/from Strings in OG-Language