/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.util;

import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;

/**
 * ReplaceString.java は、複数の文字列を一括置換する場合に使用するクラスです。
 *
 * add メソッドで、開始アドレス、終了アドレス、置換文字列を指定し、
 * 最後に、replaceAll で、変換を行います。
 * 通常、異なる文字列を一括で変換する場合、逆順に変換アドレスを求めて、
 * 後ろから順に置換していかないと、前から処理すると処理ごとにアドレスが
 * 変更になり一から再計算することになります。これは、登録時は、どのような
 * 順序でもよく、replaceAll 時に、内部に登録指定ある変換文字列の開始アドレスより
 * 自動的に逆順で置換するため、複数の置換個所があっても、まとめて処理できます。
 * ただし、複数の置換個所がある場合、重複要素があれば、エラーになります。
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class ReplaceString {
	private final Set<ReplaceData> set = new TreeSet<ReplaceData>();

	/**
	 * 開始アドレス、終了アドレス、置換文字列を指定し置換対象を追加します。
	 * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、
	 * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの
	 * 後ろから、置換を始める為の、初期データを登録します。
	 * 登録順は、置換順とは無関係に設定可能です。
	 *
	 * @param	start	置換開始アドレス
	 * @param	end  	置換終了アドレス
	 * @param	newStr	置換文字列
	 */
	public void add( final int start, final int end, final String newStr ) {
		set.add( new ReplaceData( start, end, newStr ) );
	}

	/**
	 * 置換元文字列を指定して、置換処理を実行します。
	 * add メソッドで指定した文字列を実際に置換処理します。
	 *
	 * @param	target	置換元文字列
	 *
	 * @return	置換後文字列
	 */
	public String replaceAll( final String target ) {
		Iterator<ReplaceData> ite = set.iterator();
		StringBuilder buf = new StringBuilder( target );
		while( ite.hasNext() ) {
			ReplaceData data = ite.next();
			buf = data.replace( buf );
		}
		return buf.toString();
	}

	/**
	 * 置換文字列を管理する内部クラス
	 *
	 * @version  4.0
	 * @author	 Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static class ReplaceData implements Comparable<ReplaceData> {
		private final int start     ;
		private final int end       ;
		private final String newStr ;
		private final int hCode  ;

		/**
		 * 開始アドレス、終了アドレス、置換文字列を指定します。
		 * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、
		 * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの
		 * 後ろから、置換を始める為の、初期データを登録します。
		 * 登録順は、置換順とは無関係に設定可能です。
		 *
		 * @param	start	置換開始アドレス
		 * @param	end  	置換終了アドレス
		 * @param	newStr	置換文字列
		 */
		public ReplaceData( final int start, final int end, final String newStr ) {
			this.start  = start;
			this.end    = end;
			this.newStr = newStr ;
			hCode    = ( newStr + start + "_" + end ).hashCode();
		}

		/**
		 * 置換処理を実行します。
		 *
		 * @param   buf StringBuilder 入力文字列
		 * @return	出力文字列
		 */
		public StringBuilder replace( final StringBuilder buf ) {
			return buf.replace( start,end,newStr );
		}

		/**
		 * 指定のReplaceDataの開始/終了が重なっているかどうかを判定します。
		 *                                                        return
		 *                                   | o.E   S | E   o.S |
		 * ①            S----E              |    ＞   | 【＜】  | false
		 *                       o.S----o.E  |         |         |
		 * ②            S----E              |    ＞   |   ≧    | true
		 *                 o.S----o.E        |         |         |
		 * ③            S----E              |    ≧   |   ＞    | true
		 *         o.S----o.E                |         |         |
		 * ④            S----E              |  【＜】 |   ＞    | false
		 *   o.S----o.E                      |         |         |
		 *
		 * @og.rev 5.7.2.1 (2014/01/17) 判定結果の true/false が反転していたので、修正
		 *
		 * @param   other ReplaceData 入力文字列
		 * @return	オーバーラップしているかどうか(true:不正/false:正常)
		 */
		public boolean isOverlap( final ReplaceData other ) {
//			return  ! ( ( other == null ) || ( other.end < start ) || ( end < other.start ) );
//			return  ( ( other == null ) || ( other.end < start ) || ( end < other.start ) );
			return  ( other == null ) || ! ( ( other.end < start ) || ( end < other.start ) );
		}

		/**
		 * 自然比較メソッド
		 * インタフェース Comparable の 実装です。
		 * 登録された開始アドレスの降順になるように比較します。
		 * なお、比較対照オブジェクトとオーバーラップしている場合は、
		 * 比較できないとして、IllegalArgumentException を発行します。
		 *
		 * @og.rev 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加
		 *
		 * @param   other 比較対象のObject
		 * @return  開始アドレスの降順(自分のアドレスが小さい場合は、＋)
		 * @throws	IllegalArgumentException  引数オブジェクトがオーバーラップしている場合
		 */
		public int compareTo( final ReplaceData other ) {
			if( other == null ) {
				String errMsg = "引数に null は設定できません。" ;
				throw new IllegalArgumentException( errMsg );
			}

			// 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加
			if( other.hCode == hCode ) { return 0; }

			if( isOverlap( other) ) {
				String errMsg = "比較対照オブジェクトとオーバーラップしています。"
							+ " this =[" + start + "],[" + end + "],[" + newStr + "]"
							+ " other=[" + other.start + "],[" + other.end + "],[" + other.newStr + "]" ;
				throw new IllegalArgumentException( errMsg );
			}
			return other.start - start;		// 開始順の降順
		}

		/**
		 * このオブジェクトと他のオブジェクトが等しいかどうかを示します。
		 * インタフェース Comparable の 実装に関連して、再定義しています。
		 *
		 * @param   other 比較対象の参照オブジェクト
		 * @return  obj 引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false
		 *
		 */
		public boolean equals( final Object object ) {
			if( object instanceof ReplaceData ) {
				ReplaceData other = (ReplaceData)object ;
				return start == other.start &&
						end == other.end    &&
						newStr.equals( other.newStr ) ;
			}
			return false ;
		}

		/**
		 * オブジェクトのハッシュコード値を返します。
		 * このメソッドは、java.util.Hashtable によって提供されるような
		 * ハッシュテーブルで使用するために用意されています。
		 * equals( Object ) メソッドをオーバーライトした場合は、hashCode() メソッドも
		 * 必ず 記述する必要があります。
		 *
		 *
		 * @return  このオブジェクトのハッシュコード値
		 *
		 */
		public int hashCode() {
			return hCode ;
		}
	}
}
