ByteArrayInputStream を使って文字列 String を作る

未知の byte配列のデータ、制御文字が入っているかもしれない byte配列のデータ、
ただし制御文字以外は ASCIIコードだけであるとします。

これをそのまま文字列として print してしまうと制御文字が含まれてしまうので制御文字を取り除いた
String を求めたい課題があります。

まだ、Java8 ですが、ByteArrayInputStream で以下のようすることができます。

byte[]  data;
/* data に、制御文字を含む ASCIIコードデータが入るものとします。*/

ByteArrayInputStream inst = new ByteArrayInputStream(data.getBytes());
int[] iary = Stream.generate(inst::read).limit(inst.available())
             .filter(b->0x20 <= b && b <= 0x7f)
             .mapToInt(i->i).toArray();
String str = new String(iary, 0, iary.length);

int[] を一旦作成するというのが、気に入りません。そこで考えたのが、、

ByteArrayInputStream inst = new ByteArrayInputStream(data.getBytes());
String str = Stream.generate(inst::read).limit(inst.available())
.filter(b->0x20 <= b && b <= 0x7f)
.map(i->String.valueOf((char)i.intValue()))
.collect(Collectors.joining());

これで、String str を作ってしまいます。

Handsontable で 安易なテストツールを作る。

Handsontable で、任意にキーと値(value)を入力して、それをJSONテキストにするのと、任意の入力URLに送信するもの。

用意するもの。

Handsontable は、コミュニティ版で充分、https://handsontable.com/

JSONjQueryプラグイン
GitHub - Krinkle/jquery-json: [Discontinued] Use native JSON.stringify and JSON.parse instead, or json2.js from https://github.com/douglascrockford/JSON-js

handsontable のCSSと上を読むようにしたHTML

<div id="example"><!-- ここに表が展開される。 --></div>

<!-- JSON文字列生成するボタンと生成した文字列表示 -->
<div style="margin-top: 40px">
<button id="to_json" type="button">to JSON</button>
<textarea id="json_text" rows="4" cols="80"></textarea>
</div>

<!-- 送信するボタンと受信した結果をJSONにして表示するもの -->
<div style="margin-top: 40px">
<input id="target_url" type="text" placeholder="http://" style="width: 1000px">
<button id="send" type="button">to JSON</button>
<textarea id="res_text" rows="4" cols="80"></textarea>

このHTMLを前提とした jQuery ソース

var handson = new Handsontable(document.getElementById("example"), {
   data: [[null, null],],
   minSpareRows: 1,
   colHeaders: ['Key', 'Value'],
   contextMenu: { items:{
         'row_above': { name: '1行挿入' },
         'remove_row': { name: '行削除'  },
         "hsep3": "---------",
         'undo': { name: '戻る' },
      }
   },
});
$('#to_json').click(function(){
   var map = {};
   $(handson.getData()).filter(function(i, e){
      if (!handson.isEmptyRow(i)) return e;
   }).each(function(i, e){
      map[e[0]] = e[1];
   });

   $('#json_text').html($.toJSON(map));

});

$('#send').click(function(){
   var map = {};
   $(handson.getData()).filter(function(i, e){
      if (e[0] != null) return e;
   }).each(function(i, e){
      map[e[0]] = e[1];
   });

   $.ajax({
      type: 'POST',
      timeout: 10000,
      url: $('#target_url').val(),
      data: map,
      dataType: 'json',
      cache: false,
   }).done(function(e){
      $('#res_text').html($.toJSON(e));
   }).fail(function(e){
      $('#res_text').html($.toJSON(e));
   }).always(function(e){
   });
});

Handsontable の生成として、セレクタ参照に .handsontable() を付けて

$('#example').handsontable({ ...

ではなく、document.getElementById から、new Handsontable で生成することにして、

var handson = new Handsontable(document.getElementById("example"), {
   :
handson.getData()

で抽出するようにしている。

Handsontableの生成として、tutorial に書いてある Load and save では、

var
    $$ = function(id) {
      return document.getElementById(id);
    },
    container = $$('example1'),
    exampleConsole = $$('example1console'),
    autosave = $$('autosave'),
    load = $$('load'),
    save = $$('save'),
    autosaveNotification,
    hot;

  hot = new Handsontable(container, {
   :
  (省略)

と書かれており、手数が多くて嫌だ。

Handsontable コンテキストメニューで削除の丁寧な補助は書かなくてもよい

久々に Handsontable に触れる。
3年程前、2014 年のバージョンの Handsontable のコンテキストメニューは、行削除、列削除を組み込んだ時、
最後の1行、1列は、削除できないように、以下のように disabled キーの関数コーディングをする必要があった。

var hot = new Handsontable(document.getElementById("example"), {
   data: [["Key", "Value" ],],
   minSpareRows: 1,
   colHeaders: false,
   contextMenu: { items:{
         'row_above': { name: '1行挿入' },
         'col_left':  { name: '1列挿入' },
         'remove_row': { name: '行削除' , disabled: function(){ return hot.countRows() < 2; } },
         'remove_col': { name: '列削除' , disabled: function(){ return hot.countCols() < 1; } },
         "hsep": "---------",
         'undo': { name: '戻る' },
      }
   },
});

この記述のまま、バージョンがこの時に比べてかなり新しい、最近のHandsontable を使用すると
コンテキストメニューを開いた時に、

Uncaught ReferenceError: container is not defined

で、コンテキストメニューがきちんと描画されない。
もう、この disabled: function(){ ... } は書かなくても 最後の1行、1列は、削除できないようになっている。

だから、以下の記述で良い。

$('#example').handsontable({
   data: [["Key", "Value" ],],
   minSpareRows: 1,
   colHeaders: false,
   contextMenu: { items:{
         'row_above': { name: '1行挿入' },
         'col_left':  { name: '1列挿入' },
         'remove_row': { name: '行削除'  },
         'remove_col': { name: '列削除'  },
         "hsep": "---------",
         'undo': { name: '戻る' },
      }
   },
});

mybatis logbackが出なくなった場合の対処

mybatis 、logback を使っていて、logback のバージョン上げたのか何かのきっかけ、
依存関係かなにか?指定しているアペンダ悪い?で mybatis のデバッグレベルのログが出なくなってしまった。
実行するSQL文が見れてデバッグに便利だった

logaback.xml で書くロガー設定で、mybatis のSQLMapperのネームスペースで指定すると
強制的に出すことができる。

mybatis の SQLMap XMLで、

  <mapper namespace="jp.xxx.sql.SimpleMapper">

としていた場合、

logback.xml で以下のようにすれば、この SQLMapper で実行するSQL文をデバッグログとして出力できる。

<logger name="jp.xxx.sql.SimpleMapper" additivity="true">
   <level value="debug" />
   <appender-ref ref="FILE" />
</logger>

additivity 属性が問題で、additivity="false" は、親のロガーで設定されたアペンダを継承しない。
という指定で、以前の変更までまで出ていたSQL文を出したいので、true を指定する。

フェードイン、フェードアウトするメッセージ(改編)

昔、フェードイン、フェードアウトするメッセージの jQuery を書いた。
見直すと中央寄せが中途半端でできていない。書き直す。

CSSソース

@CHARSET "UTF-8";
/**
 * fadeinmsg.css
 */
div#fadeMessageModal{
   display: none;
   position: fixed;
   width: 100%;
   height: 100%;
   z-index: 300001!important;
}
div#fadeMessageModal div.background{
   position: fixed;
   width: 100%;
   height: 100%;
   background-color: #000000;
   opacity: 0.25;
   filter: alpha(opacity=25);
   -ms-filter: "alpha(opacity=25)";
   z-index: 300001;
}
div#fadeMessageModal div.container{
   width: auto;
   box-shadow:0px 0px 40px;
   background-color: #fafad2;
   padding: 12px 20px;
   border-radius: 16px;
   position: absolute;
   top: 50%;
   left: 50%;
   -webkit-transform: translateY(-50%) translateX(-50%);
   transform: translateY(-50%) translateX(-50%);
   font-size: 24px;
   color: #000000;
   margin: auto auto;
   z-index: 300001;
   white-space: nowrap;
}

以下が抜けてた。

-webkit-transform: translateY(-50%) translateX(-50%);
transform: translateY(-50%) translateX(-50%);

jQuery のソース

/**
 * fadeinmsg.js
 *
 * showFadeMessage(message , [options])
 *
 * @param message メッセージ文字列
 * @param option オプション(フェードイン時間等)
 * options = {  fadein   : フェードイン時間、ミリ秒
 *            , interval : 表示時間、フェードアウト開始までの時間、ミリ秒
 *            , fadeout  : フェードアウト時間、ミリ秒
 *            , fontsize : 文字サイズ、font-size
 *           }
 */
var showFadeMessage = function(msg, options){
   var intime   = 3000;
   var interval = 2000;
   var outtime  = 3000;
   var fontoption  = "";
   if (!$.isEmptyObject(options)){
      if (options['fadein'] != undefined){
         intime = options['fadein'];
      }
      if (options['interval'] != undefined){
         interval = options['interval'];
      }
      if (options['fadeout'] != undefined){
         outtime = options['fadeout'];
      }
      if (options['fontsize'] != undefined){
         fontoption = 'style="font-size: ' + options['fontsize'] + ';"';
      }
   }
   $('body').prepend('<div id="fadeMessageModal"><div class="background"></div><div id="fadeMessage" class="container" ' + fontoption + '></div></div>');
   $('#fadeMessage').html(msg);
   $('#fadeMessageModal').fadeIn(intime).delay(interval).fadeOut(outtime, function(){
      $('#fadeMessageModal').remove();
   });
};

使うサンプル

<script type="text/javascript">
$(function(){
   $("button").click(function(){
     showFadeMessage('<strong style="color:#ff0000;">警告!</strong>メッセージ<br/>ABCD', {fadein:100, interval:10000, fadeout:2000});
   });
});
</script>

f:id:posturan:20170916225907j:plain

Optional の filter を連結して使う

Webアプリでのフォーム入力に限らす、Javaでは変数への入力実行の後の妥当性チェックを記述していると
どうしてもコードが長くなる。
コードが長いと、タイプミスの確率も上がるし、なにしろ読むのが辛い。長くても何回も if文を書きたくない。

java.util.Optional を単純に使うので連結を考えてみる。入力規則外を例外捕捉で処理する考え方。。。

例)日付の入力、"/" 区切りの日付文字列

String datestring = "2017/09/31";

try{
   LocalDate dt =   Optional.ofNullable(datestring)
   .filter(e->Pattern.compile("^\\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])$").matcher(e).matches())
   .filter(e->{
      DateFormat format = new SimpleDateFormat("yyyy/MM/dd");
      format.setLenient(false);
      try{
         format.parse(e);
         return true;
      }catch(ParseException ex){
         throw new RuntimeException(ex);
      }
   })
   .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
   .orElseThrow(()->new IllegalArgumentException("Format Error"));

   // 正常時の処理

}catch(Exception ex){
   if(ex instanceof IllegalArgumentException){
      // 書式エラー、null の場合を捕捉
   }
   if (ex.getCause() instanceof ParseException){
      // 存在できない日付の場合を捕捉
   }
   ex.printStackTrace();
}

古いJavajava.text.DateFormat 、 java.text.SimpleDateFormat を使って
ありえない日付のチェックをしている。ここをスマートに書きたいのだがどうにもならない。
上のコードは、null も、nullでない書式エラーも、IllegalArgumentException で捕捉する。
setLenient を実行した format で parse して発生する ParseException ラムダで飛び越えられないので
RuntimeExceptionでラップする。

せめて、ありえない日付のチェックを外にメソッドにする。

public static boolean checkDate(String strDate, String pattern){
   DateFormat format = new SimpleDateFormat(pattern);
   format.setLenient(false);
   try{
      format.parse(strDate);
      return true;
   }catch(ParseException e){
      return false;
   }
}

書き直す。

String datestring = "2017/09/31";

try{
   LocalDate dt =   Optional.ofNullable(datestring)
   .filter(e->Pattern.compile("^\\d{4}/(0[1-9]|1[012])/(0[1-9]|[12][0-9]|3[01])$").matcher(e).matches())
   .filter(e->checkDate(e, "yyyy/MM/dd"))
   .map(e->LocalDate.parse(e, DateTimeFormatter.ofPattern("yyyy/MM/dd")))
   .orElseThrow(()->new IllegalArgumentException("Format Error"));

   // 正常時の処理

}catch(Exception ex){
   if(ex instanceof IllegalArgumentException){
      // 書式エラー、null の場合を捕捉
   }
   if (ex.getCause() instanceof ParseException){
      // 存在できない日付の場合を捕捉
   }
   ex.printStackTrace();
}

少しは綺麗になる。