JSONIC 0.9.5 予定

いろいろ変えようと思った挙句結局ほとんど変わってないのだけど、今のところ下記の変更を入れる予定。

  • Parse時のDate/Calendar型への自動変換機能を実装
  • Parse時にjava.sql.Date/java.sql.Time/java.sql.Timestampへの変換に対応
  • Parse時にjava.util.TimeZoneの変換に対応
  • Parse時に「'」で囲まれた文字列はエスケープ処理しないよう変更(JavaScriptの仕様とは違ってUNIX shell的な取り扱いとなる)
  • maxDepthのデフォルト値を32階層に変更
  • JSONRPCServletのContainerを簡単に差し替えられるようにするとともに、Seasar2用コンテナを用意した
  • JSONRPCServletのconfigでencodingを指定できるようになった。
  • ありがたみが薄いためinvokeメソッドを廃止
  • 各種JavaScriptライブラリのJSON対応が整ってきたため、JavaScript版のJSON.encode/JSON.decodeを削除(てゆうかprototype.jsを使えってことですな)

日付の変換対応が目玉と言えば目玉かな? 使い物にならないDateFormat.parseの使用を基本的にはやめて、入力文字列から動的にSimpleDateFormatのパターンを作って使用するように変更することにしました。このサイトの情報+IEFirefoxのDate.toString()/toGMTString()のフォーマットを参考にしてるので、世間一般でコンピュータに読み込ませる前提で書かれたフォーマットであれば大抵自動的に変換できるはず。

あと、JSONRPCServletにRestfullっぽい機能を追加する予定。某Restfull本を読んだ感じとしては、RESTっちゅうのは必ずしもRPCと排他的なものじゃないなという結論に至ったのでREST/RPCの2-wayで扱えるような仕組みにするつもり(銀行のトランザクションの例が書いてあったが、さすがにあの例は無茶でしょ)。

なお、日付自動変換のコードは、単独でも役に立つかもしれないので切り出して掲載しておきます。かなりアドホックなのでもっと良い変換方法があるに違いなく、もし気づいたら教えてたもれ。

private static final Pattern TIMEZONE_PATTERN = Pattern.compile("(?:GMT|UTC)([+-][0-9]{2})([0-9]{2})");
private Long parseDate(String value) throws ParseException {
  value = value.trim();
  if (value.length() == 0) {
    return null;
  }
  value = TIMEZONE_PATTERN.matcher(value).replaceFirst("GMT$1:$2");
  
  DateFormat format = null;
  if (Character.isDigit(value.charAt(0))) {
    StringBuilder sb = new StringBuilder(value.length() * 2);

    String types = "yMdHmsSZ";
    // 0: year, 1:month, 2: day, 3: hour, 4: minute, 5: sec, 6:msec, 7: timezone
    int pos = (value.length() > 2 && value.charAt(2) == ':') ? 3 : 0;
    boolean before = true;
    int count = 0;
    for (int i = 0; i < value.length(); i++) {
      char c = value.charAt(i);
      if ((pos == 4 || pos == 5 || pos == 6) 
          && (c == '+' || c == '-')
          && (i + 1 < value.length())
          && (Character.isDigit(value.charAt(i+1)))) {
        
        if (!before) sb.append('\'');
        pos = 7;
        count = 0;
        before = true;
        continue;
      } else if (pos == 7 && c == ':'
          && (i + 1 < value.length())
          && (Character.isDigit(value.charAt(i+1)))) {
        value = value.substring(0, i) + value.substring(i+1);
        continue;
      }
      
      boolean digit = (Character.isDigit(c) && pos < 8);
      if (before != digit) {
        sb.append('\'');
        if (digit) {
          count = 0;
          pos++;
        }
      }
      
      if (digit) {
        char type = types.charAt(pos);
        if (count == ((type == 'y' || type == 'Z') ? 4 : (type == 'S') ? 3 : 2)) {
          count = 0;
          pos++;
          type = types.charAt(pos);
        }
        if (type != 'Z' || count == 0) sb.append(type);
        count++;
      } else {
        sb.append((c == '\'') ? "''" : c);
      }
      before = digit;
    }
    if (!before) sb.append('\'');
    
    format = new SimpleDateFormat(sb.toString(), Locale.ENGLISH);
  } else if (value.length() > 18) {
    if (value.charAt(3) == ',') {
      String pattern = "EEE, dd MMM yyyy HH:mm:ss Z";
      format = new SimpleDateFormat(
          (value.length() < pattern.length()) ? pattern.substring(0, value.length()) : pattern, Locale.ENGLISH);
    } else if (value.charAt(13) == ':') {
      format = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH);
    } else if (value.charAt(18) == ':') {
      String pattern = "EEE MMM dd yyyy HH:mm:ss Z";
      format = new SimpleDateFormat(
          (value.length() < pattern.length()) ? pattern.substring(0, value.length()) : pattern, Locale.ENGLISH);
    } else {
      format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
    }
  } else {
    format = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
  }
  
  return format.parse(value).getTime();
}