    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}