001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import java.util.Set; 019import java.util.TreeSet; 020import java.util.Iterator; 021 022/** 023 * ReplaceString.java は、複数の文字列を一括置換する場合に使用するクラスです。 024 * 025 * add メソッドで、開始アドレス、終了アドレス、置換文字列を指定し、 026 * 最後に、replaceAll で、変換を行います。 027 * 通常、異なる文字列を一括で変換する場合、逆順に変換アドレスを求めて、 028 * 後ろから順に置換していかないと、前から処理すると処理ごとにアドレスが 029 * 変更になり一から再計算することになります。これは、登録時は、どのような 030 * 順序でもよく、replaceAll 時に、内部に登録指定ある変換文字列の開始アドレスより 031 * 自動的に逆順で置換するため、複数の置換個所があっても、まとめて処理できます。 032 * ただし、複数の置換個所がある場合、重複要素があれば、エラーになります。 033 * 034 * @version 4.0 035 * @author Kazuhiko Hasegawa 036 * @since JDK5.0, 037 */ 038public final class ReplaceString { 039 private final Set<ReplaceData> set = new TreeSet<ReplaceData>(); 040 041 /** 042 * 開始アドレス、終了アドレス、置換文字列を指定し置換対象を追加します。 043 * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、 044 * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの 045 * 後ろから、置換を始める為の、初期データを登録します。 046 * 登録順は、置換順とは無関係に設定可能です。 047 * 048 * @param start 置換開始アドレス 049 * @param end 置換終了アドレス 050 * @param newStr 置換文字列 051 */ 052 public void add( final int start, final int end, final String newStr ) { 053 set.add( new ReplaceData( start, end, newStr ) ); 054 } 055 056 /** 057 * 置換元文字列を指定して、置換処理を実行します。 058 * add メソッドで指定した文字列を実際に置換処理します。 059 * 060 * @param target 置換元文字列 061 * 062 * @return 置換後文字列 063 */ 064 public String replaceAll( final String target ) { 065 Iterator<ReplaceData> ite = set.iterator(); 066 StringBuilder buf = new StringBuilder( target ); 067 while( ite.hasNext() ) { 068 ReplaceData data = ite.next(); 069 buf = data.replace( buf ); 070 } 071 return buf.toString(); 072 } 073 074 /** 075 * 置換文字列を管理する内部クラス 076 * 077 * @version 4.0 078 * @author Kazuhiko Hasegawa 079 * @since JDK5.0, 080 */ 081 private static class ReplaceData implements Comparable<ReplaceData> { 082 private final int start ; 083 private final int end ; 084 private final String newStr ; 085 private int hCode = 0 ; 086 087 /** 088 * 開始アドレス、終了アドレス、置換文字列を指定します。 089 * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、 090 * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの 091 * 後ろから、置換を始める為の、初期データを登録します。 092 * 登録順は、置換順とは無関係に設定可能です。 093 * 094 * @param start 置換開始アドレス 095 * @param end 置換終了アドレス 096 * @param newStr 置換文字列 097 */ 098 public ReplaceData( final int start, final int end, final String newStr ) { 099 this.start = start; 100 this.end = end; 101 this.newStr = newStr ; 102 hCode = ( newStr + start + "_" + end ).hashCode(); 103 } 104 105 /** 106 * 置換処理を実行します。 107 * 108 * @param buf StringBuilder 入力文字列 109 * @return 出力文字列 110 */ 111 public StringBuilder replace( final StringBuilder buf ) { 112 return buf.replace( start,end,newStr ); 113 } 114 115 /** 116 * 指定のReplaceDataの開始/終了が重なっているかどうかを判定します。 117 * return 118 * | o.E S | E o.S | 119 * ① S----E | > | 【<】 | false 120 * o.S----o.E | | | 121 * ② S----E | > | ≧ | true 122 * o.S----o.E | | | 123 * ③ S----E | ≧ | > | true 124 * o.S----o.E | | | 125 * ④ S----E | 【<】 | > | false 126 * o.S----o.E | | | 127 * 128 * @og.rev 5.7.2.1 (2014/01/17) 判定結果の true/false が反転していたので、修正 129 * 130 * @param other ReplaceData 入力文字列 131 * @return オーバーラップしているかどうか(true:不正/false:正常) 132 */ 133 public boolean isOverlap( final ReplaceData other ) { 134// return ! ( ( other == null ) || ( other.end < start ) || ( end < other.start ) ); 135// return ( ( other == null ) || ( other.end < start ) || ( end < other.start ) ); 136 return ( other == null ) || ! ( ( other.end < start ) || ( end < other.start ) ); 137 } 138 139 /** 140 * 自然比較メソッド 141 * インタフェース Comparable の 実装です。 142 * 登録された開始アドレスの降順になるように比較します。 143 * なお、比較対照オブジェクトとオーバーラップしている場合は、 144 * 比較できないとして、IllegalArgumentException を発行します。 145 * 146 * @og.rev 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加 147 * 148 * @param other 比較対象のObject 149 * @return 開始アドレスの降順(自分のアドレスが小さい場合は、+) 150 * @throws IllegalArgumentException 引数オブジェクトがオーバーラップしている場合 151 */ 152 public int compareTo( final ReplaceData other ) { 153 if( other == null ) { 154 String errMsg = "引数に null は設定できません。" ; 155 throw new IllegalArgumentException( errMsg ); 156 } 157 158 // 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加 159 if( other.hCode == hCode ) { return 0; } 160 161 if( isOverlap( other) ) { 162 String errMsg = "比較対照オブジェクトとオーバーラップしています。" 163 + " this =[" + start + "],[" + end + "],[" + newStr + "]" 164 + " other=[" + other.start + "],[" + other.end + "],[" + other.newStr + "]" ; 165 throw new IllegalArgumentException( errMsg ); 166 } 167 return other.start - start; // 開始順の降順 168 } 169 170 /** 171 * このオブジェクトと他のオブジェクトが等しいかどうかを示します。 172 * インタフェース Comparable の 実装に関連して、再定義しています。 173 * 174 * @param other 比較対象の参照オブジェクト 175 * @return obj 引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false 176 */ 177 public boolean equals( final Object object ) { 178 if( object instanceof ReplaceData ) { 179 ReplaceData other = (ReplaceData)object ; 180 return start == other.start && 181 end == other.end && 182 newStr.equals( other.newStr ) ; 183 } 184 return false ; 185 } 186 187 /** 188 * オブジェクトのハッシュコード値を返します。 189 * このメソッドは、java.util.Hashtable によって提供されるような 190 * ハッシュテーブルで使用するために用意されています。 191 * equals( Object ) メソッドをオーバーライトした場合は、hashCode() メソッドも 192 * 必ず 記述する必要があります。 193 * 194 * @return このオブジェクトのハッシュコード値 195 */ 196 public int hashCode() { 197 if( hCode == 0 ) { hCode = ( newStr + start + "_" + end ).hashCode() ; } 198 return hCode ; 199 } 200 } 201}