public static <Type> JSONValue toJSON(
      Map<String, Type> value, AbstractJsonEncoderDecoder<Type> encoder, Style style) {
    if (value == null) {
      return JSONNull.getInstance();
    }

    switch (style) {
      case DEFAULT:
      case SIMPLE:
        {
          JSONObject rc = new JSONObject();
          for (Entry<String, Type> t : value.entrySet()) {
            rc.put(t.getKey(), encoder.encode(t.getValue()));
          }
          return rc;
        }
      case JETTISON_NATURAL:
        {
          JSONObject rc = new JSONObject();
          JSONArray entries = new JSONArray();
          int i = 0;
          for (Entry<String, Type> t : value.entrySet()) {
            JSONObject entry = new JSONObject();
            entry.put("key", new JSONString(t.getKey()));
            entry.put("value", encoder.encode(t.getValue()));
            entries.set(i++, entry);
          }
          rc.put("entry", entries);
          return rc;
        }
      default:
        throw new UnsupportedOperationException(
            "The encoding style is not yet suppored: " + style.name());
    }
  }
  public static <KeyType, ValueType> Map<KeyType, ValueType> toMap(
      JSONValue value,
      AbstractJsonEncoderDecoder<KeyType> keyEncoder,
      AbstractJsonEncoderDecoder<ValueType> valueEncoder,
      Style style) {
    if (value == null || value.isNull() != null) {
      return null;
    }

    switch (style) {
      case DEFAULT:
      case SIMPLE:
        {
          JSONObject object = value.isObject();
          if (object == null) {
            throw new DecodingException("Expected a json object, but was given: " + value);
          }

          HashMap<KeyType, ValueType> rc = new HashMap<KeyType, ValueType>(object.size() * 2);
          for (String key : object.keySet()) {
            rc.put(keyEncoder.decode(key), valueEncoder.decode(object.get(key)));
          }
          return rc;
        }
      case JETTISON_NATURAL:
        {
          JSONObject object = value.isObject();
          if (object == null) {
            throw new DecodingException("Expected a json object, but was given: " + value);
          }
          value = object.get("entry");
          if (value == null) {
            throw new DecodingException("Expected an entry array not found");
          }
          JSONArray entries = value.isArray();
          if (entries == null) {
            throw new DecodingException("Expected an entry array, but was given: " + value);
          }

          HashMap<KeyType, ValueType> rc = new HashMap<KeyType, ValueType>(object.size() * 2);
          for (int i = 0; i < entries.size(); i++) {
            JSONObject entry = entries.get(i).isObject();
            if (entry == null)
              throw new DecodingException("Expected an entry object, but was given: " + value);
            JSONValue key = entry.get("key");
            if (key == null) throw new DecodingException("Expected an entry key field not found");
            JSONString k = key.isString();
            if (k == null)
              throw new DecodingException(
                  "Expected an entry key to be a string, but was given: " + value);
            rc.put(keyEncoder.decode(k.stringValue()), valueEncoder.decode(entry.get("value")));
          }
          return rc;
        }
      default:
        throw new UnsupportedOperationException(
            "The encoding style is not yet supported: " + style.name());
    }
  }
  // TODO(sbeutel): new map method to handle other key values than String
  public static <KeyType, ValueType> JSONValue toJSON(
      Map<KeyType, ValueType> value,
      AbstractJsonEncoderDecoder<KeyType> keyEncoder,
      AbstractJsonEncoderDecoder<ValueType> valueEncoder,
      Style style) {
    if (value == null) {
      return JSONNull.getInstance();
    }

    switch (style) {
      case DEFAULT:
      case SIMPLE:
        {
          JSONObject rc = new JSONObject();

          for (Entry<KeyType, ValueType> t : value.entrySet()) {
            // TODO find a way to check only once
            JSONValue k = keyEncoder.encode(t.getKey());
            if (k.isString() != null) {
              rc.put(k.isString().stringValue(), valueEncoder.encode(t.getValue()));
            } else {
              rc.put(k.toString(), valueEncoder.encode(t.getValue()));
            }
          }
          return rc;
        }
      case JETTISON_NATURAL:
        {
          JSONObject rc = new JSONObject();
          JSONArray entries = new JSONArray();
          int i = 0;
          for (Entry<KeyType, ValueType> t : value.entrySet()) {
            JSONObject entry = new JSONObject();
            // TODO find a way to check only once
            JSONValue k = keyEncoder.encode(t.getKey());
            if (k.isString() != null) {
              entry.put("key", k);
            } else {
              entry.put("key", new JSONString(k.toString()));
            }
            entry.put("value", valueEncoder.encode(t.getValue()));
            entries.set(i++, entry);
          }
          rc.put("entry", entries);
          return rc;
        }
      default:
        throw new UnsupportedOperationException(
            "The encoding style is not yet supported: " + style.name());
    }
  }
  private String encodeDecodeExpression(
      JType type,
      String expression,
      Style style,
      String encoderMethod,
      String mapMethod,
      String setMethod,
      String listMethod)
      throws UnableToCompleteException {

    if (null != type.isEnum()) {
      if (encoderMethod.equals("encode")) {
        return encodeDecodeExpression(
            STRING_TYPE,
            expression + ".name()",
            style,
            encoderMethod,
            mapMethod,
            setMethod,
            listMethod);
      } else {
        return type.getQualifiedSourceName()
            + ".valueOf("
            + encodeDecodeExpression(
                STRING_TYPE, expression, style, encoderMethod, mapMethod, setMethod, listMethod)
            + ")";
      }
    }

    String encoderDecoder = getEncoderDecoder(type, logger);
    if (encoderDecoder != null) {
      return encoderDecoder + "." + encoderMethod + "(" + expression + ")";
    }

    JClassType clazz = type.isClassOrInterface();

    if (isCollectionType(clazz)) {
      JParameterizedType parameterizedType = type.isParameterized();
      if (parameterizedType == null || parameterizedType.getTypeArgs() == null) {
        error("Collection types must be parameterized.");
      }
      JClassType[] types = parameterizedType.getTypeArgs();

      if (clazz.isAssignableTo(MAP_TYPE)) {
        if (types.length != 2) {
          error("Map must define two and only two type parameters");
        }
        if (types[0] != STRING_TYPE) {
          error("Map's first type parameter must be of type String");
        }
        encoderDecoder = getEncoderDecoder(types[1], logger);
        if (encoderDecoder != null) {
          return mapMethod
              + "("
              + expression
              + ", "
              + encoderDecoder
              + ", "
              + JSON_CLASS
              + ".Style."
              + style.name()
              + ")";
        }
      } else if (clazz.isAssignableTo(SET_TYPE)) {
        if (types.length != 1) {
          error("Set must define one and only one type parameter");
        }
        encoderDecoder = getEncoderDecoder(types[0], logger);
        if (encoderDecoder != null) {
          return setMethod + "(" + expression + ", " + encoderDecoder + ")";
        }
      } else if (clazz.isAssignableTo(LIST_TYPE)) {
        if (types.length != 1) {
          error("List must define one and only one type parameter");
        }
        encoderDecoder = getEncoderDecoder(types[0], logger);
        info("type encoder for: " + types[0] + " is " + encoderDecoder);
        if (encoderDecoder != null) {
          return listMethod + "(" + expression + ", " + encoderDecoder + ")";
        }
      }
    }

    error("Do not know how to encode/decode " + type);
    return null;
  }