memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

ControlP5のsetTextでjava.lang.StringIndexOutOfBoundsException: String index out of range: -1が出る件について

Processingはプログラミングでアニメーションやインタラクティブなコンテンツを(Javaだけで作り上げるよりは)手軽に作成することができるプログラミング環境です。

ビジュアルな命令(というか関数というかメソッド)はいろいろと用意されていますが、単純なものが多く、ボタンやテキスト入力エリアなどのいわゆるGUIコンポーネントは用意されていません。

そのため、sojamoさんがcontrolP5というライブラリを開発して公開してくれています。たぶん、お世話になった方も多いと思います。

私もその1人なのですが、ある研究用のアプリを作っていて表記のエラーが出ました。この調査と対応についてまとめたのがこのエントリーの趣旨になります。

さて。

現象としてはcontrolP5のテキストエリア部品(Textarea)のクラスのインスタンスに対して、setTextで表示する文字列を与えたとき、たまに表記のエラーが発生します。例えば、以下のコードでこの例外が発生します。つまり、空白しか含まない行(空白の個数に関係ない)が1つでもあると発生するわけです。

  myTextarea.setText("\n  \n \n");

この件については、これまでも報告があるようで、以下のようなものが見つかりました。

Control P5 setText() problem - arrayIndexOutofBounds error! - Processing Forum

java - ControlP5 Textarea space character issue - Stack Overflow


上記の最初では未解決、次の件ではreplaceAllを使っていますが、実はこれはエラーが出なくなるだけで、改行が複数回あるとすべてが1つの改行コードに置き換わるため、オリジナルとは異なる結果になってしまいます。わかりにくいとは思いますが。。。

Github上のコードが公開されていましたので、抜粋が以下になります。

	private void calculateHeight( PGraphics theGraphics , Label theLabel ) {
		txt.clear( );
		String myString = theLabel.getTextFormatted( );
		List< String > paragraphs = Arrays.asList( myString.split( "\n" ) );
		// does not recognize linebreaks at the end of theString.
		myString = "";
		for ( String p : paragraphs ) {
			List< String > words = Arrays.asList( p.split( "\\s" ) );  //1行に空白文字しかないときwordsは要素数0個のListになる
			for ( String w : words ) {
				if ( theGraphics.textWidth( myString + w ) < width ) {
					myString += w + " ";
				} else {
					txt.add( myString.substring( 0 , PApplet.max( 0 , myString.length( ) - 1 ) ) );
					myString = w + " ";
				}
			}
			txt.add( myString.substring( 0 , myString.length( ) - 1 ) ); //その結果、myString.length( )が0となりsubstringで-1の位置にアクセスして例外発生する
			myString = "";
		}

調査過程は省略しますが、一応、上記のコード内にコメントで書きました。この処理自体が何をしているのか微妙ですが、恐らく、テキストエリアの幅に入らない単語を自動的に改良するワードラップ処理をしているのではないかと思われます。

対処方法はいくつか考えられますが、その上のsubstringではmax関数で処理しているのと同様に、この行でもmaxで0未満の位置を指定しないようにすればよいものと思われます。そもそも、1行に空白文字しか含まれていない場合、つまりwordsのsizeが0だったときはfor文を実行しないようにした方がよいとも思われます。

こんなこと書いていないでコード書いてプルリク送ればいいんですが。。。面倒なので。。。時間があればやります。とりあえず対処療法を。空白しか含まれない行があればどうせ空白は表示しても意味がありませんので改行コードのみの行にする関数をかまします。

Processingのテストコードを以下に書きます。このfunc関数をかませばエラーがでなくなり、表示も(改行個数も)オリジナルと同様になるはずです。はい。

void setup() {
  String s = "\naa\n \n  \n c  \n  b";
  println(func(s));
}

String func(String src) {
  String tmp = "";
  String[] srcs = src.split("\n");
  for (String s : srcs) {
    tmp += ((s.split("\\s")).length == 0) ? "\n" : s + "\n";
  }
  return tmp;
}