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