    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}