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<>();
040
041        /**
042         * デフォルトコンストラクター
043         *
044         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
045         */
046        public ReplaceString() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
047
048        /**
049         * 開始アドレス、終了アドレス、置換文字列を指定し置換対象を追加します。
050         * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、
051         * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの
052         * 後ろから、置換を始める為の、初期データを登録します。
053         * 登録順は、置換順とは無関係に設定可能です。
054         *
055         * @param       start   置換開始アドレス
056         * @param       end     置換終了アドレス
057         * @param       newStr  置換文字列
058         */
059        public void add( final int start, final int end, final String newStr ) {
060                set.add( new ReplaceData( start, end, newStr ) );
061        }
062
063        /**
064         * 置換元文字列を指定して、置換処理を実行します。
065         * add メソッドで指定した文字列を実際に置換処理します。
066         *
067         * @param       target  置換元文字列
068         *
069         * @return      置換後文字列
070         * @og.rtnNotNull
071         */
072        public String replaceAll( final String target ) {
073                final Iterator<ReplaceData> ite = set.iterator();
074                StringBuilder buf = new StringBuilder( target );
075                while( ite.hasNext() ) {
076                        final ReplaceData data = ite.next();
077                        buf = data.replace( buf );
078                }
079                return buf.toString();
080        }
081
082        /**
083         * 置換文字列を管理する内部クラス
084         *
085         * 6.4.1.1 (2016/01/16) PMD refactoring. Overridable method 'hashCode' called during object construction
086         *
087         * @version  4.0
088         * @author       Kazuhiko Hasegawa
089         * @since    JDK5.0,
090         */
091        private static final class ReplaceData implements Comparable<ReplaceData> {
092                private final int start     ;
093                private final int end       ;
094                private final String newStr ;
095                private final int hCode     ;
096
097                /**
098                 * 開始アドレス、終了アドレス、置換文字列を指定します。
099                 * 通常、文字列を置換すると、元のアドレスとずれるのを防ぐ為、
100                 * 後ろから、置換を行います。一括置換は、複数の文字列置換を、開始アドレスの
101                 * 後ろから、置換を始める為の、初期データを登録します。
102                 * 登録順は、置換順とは無関係に設定可能です。
103                 *
104                 * @param       start   置換開始アドレス
105                 * @param       end     置換終了アドレス
106                 * @param       newStr  置換文字列
107                 */
108                public ReplaceData( final int start, final int end, final String newStr ) {
109                        this.start  = start;
110                        this.end    = end;
111                        this.newStr = newStr ;
112                        hCode           = ( newStr + start + "_" + end ).hashCode();
113                }
114
115                /**
116                 * 置換処理を実行します。
117                 *
118                 * @param   buf StringBuilder 入力文字列
119                 * @return      出力文字列
120                 * @og.rtnNotNull
121                 */
122                public StringBuilder replace( final StringBuilder buf ) {
123                        return buf.replace( start,end,newStr );
124                }
125
126                /**
127                 * 指定のReplaceDataの開始/終了が重なっているかどうかを判定します。
128                 *                                                        return
129                 *                                   | o.E   S | E   o.S |
130                 * ①            S----E              |    >   | 【<】  | false
131                 *                       o.S----o.E  |         |         |
132                 * ②            S----E              |    >   |   ≧    | true
133                 *                 o.S----o.E        |         |         |
134                 * ③            S----E              |    ≧   |   >    | true
135                 *         o.S----o.E                |         |         |
136                 * ④            S----E              |  【<】 |   >    | false
137                 *   o.S----o.E                      |         |         |
138                 *
139                 * @og.rev 5.7.2.1 (2014/01/17) 判定結果の true/false が反転していたので、修正
140                 *
141                 * @param   other ReplaceData 入力文字列
142                 * @return      オーバーラップしているかどうか(true:不正/false:正常)
143                 */
144                public boolean isOverlap( final ReplaceData other ) {
145                        return  other == null || ! ( other.end < start || end < other.start );
146                }
147
148                /**
149                 * 自然比較メソッド
150                 * インタフェース Comparable の 実装です。
151                 * 登録された開始アドレスの降順になるように比較します。
152                 * なお、比較対照オブジェクトとオーバーラップしている場合は、
153                 * 比較できないとして、IllegalArgumentException を発行します。
154                 *
155                 * @og.rev 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加
156                 *
157                 * @param   other 比較対象のObject
158                 * @return  開始アドレスの降順(自分のアドレスが小さい場合は、+)
159                 * @throws      IllegalArgumentException  引数オブジェクトがオーバーラップしている場合
160                 */
161                @Override       // Comparable
162                public int compareTo( final ReplaceData other ) {
163                        if( other == null ) {
164                                final String errMsg = "引数に null は設定できません。" ;
165                                throw new IllegalArgumentException( errMsg );
166                        }
167
168                        // 8.5.5.1 (2024/02/29) オーバーラップ判定を先に持ってきます。
169                        if( isOverlap( other) ) {
170                                final String errMsg = "比較対照オブジェクトとオーバーラップしています。"
171                                                        + " this =[" + start       + "],[" + end       + "],[" + newStr       + "]"
172                                                        + " other=[" + other.start + "],[" + other.end + "],[" + other.newStr + "]" ;
173                                throw new IllegalArgumentException( errMsg );
174                        }
175
176                        // 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加
177                        return other.hCode == hCode
178                                        ? 0                                                     // 同一
179                                        : other.start - start;          // 開始順の降順
180
181                        // 5.7.4.0 (2014/03/07) 同一オブジェクトの判定を追加
182//                      if( other.hCode == hCode ) { return 0; }
183
184//                      if( isOverlap( other) ) {
185//                              final String errMsg = "比較対照オブジェクトとオーバーラップしています。"
186//                                                      + " this =[" + start       + "],[" + end       + "],[" + newStr       + "]"
187//                                                      + " other=[" + other.start + "],[" + other.end + "],[" + other.newStr + "]" ;
188//                              throw new IllegalArgumentException( errMsg );
189//                      }
190//                      return other.start - start;             // 開始順の降順
191                }
192
193                /**
194                 * このオブジェクトと他のオブジェクトが等しいかどうかを示します。
195                 * インタフェース Comparable の 実装に関連して、再定義しています。
196                 *
197                 * @param   object 比較対象の参照オブジェクト
198                 * @return  引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false
199                 */
200                @Override
201                public boolean equals( final Object object ) {
202                        if( object instanceof ReplaceData ) {
203                                final ReplaceData other = (ReplaceData)object ;
204                                return start == other.start &&
205                                                end  == other.end   &&
206                                                newStr.equals( other.newStr ) ;
207                        }
208                        return false ;
209                }
210
211                /**
212                 * オブジェクトのハッシュコード値を返します。
213                 * このメソッドは、java.util.Hashtable によって提供されるような
214                 * ハッシュテーブルで使用するために用意されています。
215                 * equals( Object ) メソッドをオーバーライトした場合は、hashCode() メソッドも
216                 * 必ず 記述する必要があります。
217                 *
218                 * @return  このオブジェクトのハッシュコード値
219                 */
220                @Override
221                public int hashCode() {
222                        return hCode ;
223                }
224        }
225}