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.List;
019import java.util.ArrayList;
020import java.util.Locale;
021import java.text.Normalizer;
022
023/**
024 * HybsContains.java は、指定の AND OR 形式の文字列が含まれるかどうかを判定するクラスです。
025 *
026 * AND OR 形式の文字列 とは、「AAA BBB」は、AAA とBBB のAND形式、「CCC OR DDD」は、
027 * CCC と DDD のOR形式になります。
028 * 優先順位を付ける"(" などは使えません。常に、OR で分解後、スペース分解で、AND因子を求めます。
029 *
030 * 例)
031 *   AAA BBB OR CCC ⇒「AAA BBB」OR 「CCC」
032 *   AAA OR BBB CCC ⇒「AAA」OR 「BBB CCC」
033 *
034 * 判定方法は、ノーマル、大文字小文字を区別しない、全角半角を正規化するを指定します。
035 *
036 * @version     8.5
037 * @author      Kazuhiko Hasegawa
038 * @since       JDK17.0,
039 */
040public final class HybsContains {
041        // List成分が、OR因子、文字列配列が、AND因子
042        private final List<String[]> andOrList = new ArrayList<>();
043        private final boolean isIgnoreCase ;            // true で大文字小文字を区別しない
044        private final boolean isNormalize ;                     // true でNormalize変換を使用する
045
046        /**
047         * コンストラクター
048         *
049         * AND OR 形式の文字列 を指定します。「OR」は、大文字固定で前後に半角スペースを入れます。
050         * AND の連結は、スペースで区切ります。OR の分割には、String#split を使いますが、ANDの
051         * 分割は、CSVTokenizer を使用するため、"xxx yyy"などで一連の文字列として処理できます。
052         * スペースで分割するため、ダブルクオートで括っても 前後のスペースは削除されます。
053         *
054         * @og.rev 8.5.0.0 (2023/04/21)
055         *
056         * @param       andOrStr        AND OR 形式の文字列
057         */
058        public HybsContains( final String andOrStr ) {
059                this( andOrStr,false,false );
060        }
061
062        /**
063         * コンストラクター
064         *
065         * @og.rev 8.5.0.0 (2023/04/21)
066         *
067         * @param       andOrStr                AND OR 形式の文字列
068         * @param       isIgnoreCase    true で大文字小文字を区別しない
069         * @param       isNormalize             true でNormalize変換を使用する
070         */
071        public HybsContains( final String andOrStr, final boolean isIgnoreCase, final boolean isNormalize ) {
072                this.isIgnoreCase = isIgnoreCase;
073                this.isNormalize  = isNormalize;
074
075                String base = andOrStr ;
076                if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
077                if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }
078
079                for( final String andStr : base.split( " OR " ) ) {                             // OR で分割
080                        andOrList.add( StringUtil.csv2ArrayOnly( andStr,' ' ) );        // スペースで分割(文字列配列)をListに追加
081                }
082        }
083
084        /**
085         * 指定の文字列に、コンストラクタで指定したAND OR文字列が含まれるか判定します。
086         * 注意点としては、通常の String#contains() とは、引数が逆です。
087         * つまり、このメソッドの引数がベースとなって判定します。
088         * (通常の String#contains() は、引数が判定される部分文字列です)
089         *
090         * @og.rev 8.5.0.0 (2023/04/21)
091         *
092         * @param       value   判定のベースとなる文字列
093         * @return      AND OR文字列が含まれる場合は、true
094         */
095        public boolean contains( final String value ) {
096                String base = value ;
097                if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
098                if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }
099
100                for( final String[] orAry : andOrList ) {                       // [A B C] [D]
101                        boolean andFlag = true;
102                        for( final String andStr : orAry ) {                    // [A B C] の配列を順番に処理
103                                if( !base.contains( andStr ) ) {
104                                        andFlag = false;
105                                        break;                                                                  // and 判定なので、含まないと即終了
106                                }
107                        }
108                        if( andFlag ) { return true; }                                  // or 判定なので、成立すれば、即完了
109                }
110                return false;
111        }
112
113        /**
114         * 指定の文字列に、コンストラクタで指定したAND OR文字列が含まれる場合、tag1 とtag2 で囲んだ。
115         * 文字列で置換した結果を返します。文字列が含まれない場合は、null を返します。
116         * このメソッドでは、各種置換後(大文字化や正規化)の文字列を返しますので、
117         * オリジナルの文字列と異なるのでご注意ください。
118         *
119         * @og.rev 8.5.0.0 (2023/04/21)
120         *
121         * @param       value   判定のベースとなる文字列
122         * @param       tag1    置換する場合の前文字列
123         * @param       tag2    置換する場合の後文字列
124         * @return      value   置換後の文字列(含まれない場合は、null)
125         */
126        public String changeValue( final String value, final String tag1, final String tag2 ) {
127                String base = value ;
128                if( isIgnoreCase ) { base = base.toUpperCase(Locale.JAPAN); }
129                if( isNormalize  ) { base = Normalizer.normalize( base,Normalizer.Form.NFKC ); }
130
131                final List<Integer[]> adrsList = new ArrayList<>();
132                boolean orFlag = false;                                                                                 // ORフラグは、初期値 false
133                for( final String[] orAry : andOrList ) {
134                        boolean andFlag = true;                                                                         // ANDフラグは、初期値 true (一つでもfalseになれば false)
135                        for( final String andStr : orAry ) {
136                                boolean cngFlag = false;                                                                // 変更フラグは、初期値 false
137                                int idx = base.lastIndexOf( andStr , base.length() );   // 後ろからbaseを検索
138                                while( idx >= 0 ) {
139                                        cngFlag = true;                                                                         // 変更フラグは、一つでも置換があれば、true
140                                        adrsList.add( new Integer[] { idx,idx + andStr.length() } );
141                                        idx = base.lastIndexOf( andStr , idx-1 );                       // 後ろからbaseを検索
142                                }
143                                andFlag = andFlag && cngFlag;                                                   // ANDフラグは、すべてが true でないと、true にならない。
144                        }
145                        orFlag = orFlag || andFlag ;                                                            // ORフラグは、一度でも true になれば、true
146                }
147
148                if( orFlag ) {
149                        final boolean useOriginal = base.length() == value.length() ;   // Normalize 変換時に、文字数が変わらなければ、オリジナルを置き換える。
150                        final StringBuilder buf = new StringBuilder( useOriginal ? value : base );
151
152                        adrsList.sort( (s1, s2) -> s2[0] - s1[0] );                                     // アドレスを降順に並べ替える
153                        for( final Integer[] adrs : adrsList ) {
154                                buf.insert( adrs[1] , tag2 );                                                   // 後ろから追加している。
155                                buf.insert( adrs[0] , tag1 );
156                        }
157
158                        return buf.toString();
159                }
160                return null;
161        }
162}