    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.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
    019import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.1 (2016/02/12) refactoring
    020import java.util.Locale;
    021
    022import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    023
    024/**
    025 * Attributes.java は、String 型キーにString型値を Map するクラスです。
    026 *
    027 * HTMLのPOST/GET等の受け渡しや、String型の引数が多い場合に効果があります。
    028 * 特に、getAttributes( String[] param ) による属性リスト作成は、
    029 * HTMLタグの属性定義を行う上で，非常に便利に利用できます。
    030 *
    031 * ※ 6.1.1.0 (2015/01/17)
    032 *    StringBuilder と同様、set メソッド , add メソッドの戻り値に、自分自身を戻します。
    033 *    これにより、連結処理できるようにします。
    034 *
    035 * この実装は同期化されません。
    036 *
    037 * @version  4.0
    038 * @author   Kazuhiko Hasegawa
    039 * @since    JDK5.0,
    040 */
    041public final class Attributes {
    042        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
    043        private final ConcurrentMap<String,String> attMap ;             // Map系変数は、Mapと判る様に命名する。
    044
    045        /**
    046         * デフォルトコンストラクター
    047         *
    048         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    049         */
    050        public Attributes() {
    051                attMap = new ConcurrentHashMap<>();
    052        }
    053
    054        /**
    055         * Attributesオブジェクト を与えて新しく作成するコンストラクター
    056         *
    057         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    058         *
    059         * @param att Attributesオブジェクト
    060         */
    061        public Attributes( final Attributes att ) {
    062                attMap = new ConcurrentHashMap<>( att.attMap );                 // AttributesのattMapは、ConcurrentHashMap なので、not null保障
    063        }
    064
    065        /**
    066         * マップからマッピングをすべて削除します 。
    067         *
    068         */
    069        public void clear()  {
    070                attMap.clear() ;
    071        }
    072
    073        /**
    074         * マップが指定のキーをマップする値を返します。
    075         * マップがこのキーのマッピングを保持していない場合は null
    076         * を返します。戻り値の null は、マップがキーのマッピングを
    077         * 保持していないことを示すとはかぎりません。つまり、マップが
    078         * 明示的にキーを null にマップすることもあります。
    079         *
    080         * @param    key 関連付けられた値が返されるキー(大文字小文字は同値)
    081         *
    082         * @return   マップが、指定されたキーにマッピングしている値。
    083         *           このキーに対するマッピングがマップにない場合は null
    084         */
    085        public String get( final String key ) {
    086                return attMap.get( key.toLowerCase( Locale.JAPAN ) ) ;
    087        }
    088
    089        /**
    090         * 指定された値と指定されたキーをこのマップに関連付けます
    091         * 指定されたキーに、null を関連付けることはできません。
    092         * (もちろん、""：ゼロストリング は登録できます。)
    093         * なぜなら、getAttribute( String[] keys ) 等で値が null の
    094         * キーは、取得できない為です。
    095         * また、すでに何らかの値がセットされている所に､null をセットした
    096         * 場合は､前の値をなにも変更しません。
    097         * 通常、値をクリアしたい場合は､ remove( String key ) を利用してください。
    098         *
    099         * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
    100         *
    101         * @param    key 指定される値が関連付けられるキー(大文字小文字は同値)
    102         * @param    value 指定されるキーに関連付けられる値
    103         *
    104         * @return      自分自身
    105         * @og.rtnNotNull
    106         */
    107        public Attributes set( final String key,final String value ) {
    108                if( value != null ) {
    109                        attMap.put( key.toLowerCase( Locale.JAPAN ),value ) ;
    110                }
    111                return this ;           // 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
    112        }
    113
    114        /**
    115         * 指定された値と指定されたキーをこのマップに関連付けます
    116         * set( String key,String value ) との違いは､value が null
    117         * の場合に、def を代わりにセットすることです。
    118         * ただし、value が null で、def も null の場合は､
    119         * なにもセットされません。
    120         *
    121         * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
    122         *
    123         * @param    key 指定される値が関連付けられるキー(大文字小文字は同値)
    124         * @param    value 指定されるキーに関連付けられる値
    125         * @param    def value が null の場合にキーに関連付けられる値
    126         *
    127         * @return      自分自身
    128         * @og.rtnNotNull
    129         */
    130        public Attributes set( final String key,final String value,final String def ) {
    131                if( value != null )    { attMap.put( key.toLowerCase( Locale.JAPAN ),value ) ; }
    132                else if( def != null ) { attMap.put( key.toLowerCase( Locale.JAPAN ),def )   ; }
    133
    134                return this ;           // 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
    135        }
    136
    137        /**
    138         * Attributes 属性を、既存の属性に上書き追加します。
    139         *
    140         * 引数 att が null の場合は，何もしません。
    141         * 内部の Map に直接書き込みますので、すでに同一キーの属性が存在している場合は，
    142         * 上書きで置き換えます。
    143         * つまり、初期値を設定する場合は、最初に呼び出します。引数で設定された最新の値を
    144         * 使用する場合は、最後に設定します。
    145         * ただし、#add(String,String) は、既存のキーに値を追記していきますので、これらより
    146         * 後に設定しないと、上書きされてしまいますので、ご注意ください。
    147         *
    148         * 従来の、#addAttributes( Attributes ) の代替えメソッドです。
    149         *
    150         * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
    151         *
    152         * @param att Attributes属性
    153         *
    154         * @return      自分自身
    155         * @og.rtnNotNull
    156         */
    157        public Attributes set( final Attributes att ) {
    158                if( att != null && !att.attMap.isEmpty() ) {
    159                        attMap.putAll( att.attMap );                            // AttributesのattMapは、ConcurrentHashMap なので、not null保障
    160                }
    161
    162                return this ;           // 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
    163        }
    164
    165        /**
    166         * 指定された値と指定されたキーをこのマップに追加します
    167         *
    168         * マップ自身のキーは、ユニークである為、既存の値に対して、
    169         * 新しく値を追加します。
    170         * 追加する方法は、値の文字列の結合です。このメソッドでは、
    171         * デフォルトのスペースで結合します。
    172         *
    173         * 値が null または、すでにそのキーに同一の値が関連付けられている場合は、
    174         * 何もしません。
    175         *
    176         * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
    177         *
    178         * @param    key   指定される値が関連付けられるキー(大文字小文字は同値)
    179         * @param    value 指定されるキーの値に、追加される値
    180         *
    181         * @return      自分自身
    182         * @og.rtnNotNull
    183         */
    184        public Attributes add( final String key,final String value ) {
    185                return add( key,value," " ) ;           // 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
    186        }
    187
    188        /**
    189         * 指定された値と指定されたキーをこのマップに追加します
    190         *
    191         * class属性や、style属性など、同一キーに対して、複数の値をつなげる場合に
    192         * 使用します。
    193         *
    194         * マップ自身のキーは、ユニークである為、既存の値に対して、
    195         * 新しく値を追加します。
    196         * 追加する方法は、値の文字列の結合です。このメソッドでは、
    197         * 引数 sepa で文字列を結合します。
    198         *
    199         * 値が null または、sepa が null または、すでにそのキーに
    200         * 同一の値が関連付けられている場合は、何もしません。
    201         *
    202         * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
    203         *
    204         * @param    key   指定される値が関連付けられるキー(大文字小文字は同値)
    205         * @param    value 指定されるキーの値に、追加される値
    206         * @param    sepa  値を連結するときの文字列
    207         *
    208         * @return      自分自身
    209         * @og.rtnNotNull
    210         */
    211        public Attributes add( final String key,final String value,final String sepa ) {
    212                if( value != null && sepa != null ) {
    213                        final String lkey = key.toLowerCase( Locale.JAPAN );
    214
    215                        String temp = attMap.get( lkey );
    216                        // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    217                        if( temp == null ) {
    218                                attMap.put( lkey,value );
    219                        }
    220                        else {
    221                                temp = temp.trim();
    222                                if( temp.indexOf( value ) < 0 ||                        // 存在しない または、
    223                                          ! temp.equals( value ) &&                             // 一致しない
    224                                          ! temp.startsWith( value + sepa ) &&  // 先頭にない
    225                                          ! temp.endsWith( sepa + value ) &&    // 最終にない
    226                                          temp.indexOf( sepa + value + sepa ) < 0 ) {   // 途中にない                        // 6.9.7.0 (2018/05/14) PMD
    227                                                if( temp.endsWith( sepa ) ) {
    228                                                        attMap.put( lkey,temp + value );
    229                                                }
    230                                                else {
    231                                                        attMap.put( lkey,temp + sepa + value );
    232                                                }
    233                                }
    234                        }
    235                }
    236
    237                return this ;           // 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
    238        }
    239
    240        /**
    241         * このキーにマッピングがある場合に、そのマッピングをマップから削除します。
    242         *
    243         * @param    key マッピングがマップから削除されるキー(大文字小文字は同値)
    244         *
    245         * @return   このキーにマッピングがある場合に、そのマッピングをマップから削除します
    246         *           指定されたキーに関連した以前の値。key にマッピングがなかった場合は null。
    247         */
    248        public String remove( final String key ) {
    249                return attMap.remove( key.toLowerCase( Locale.JAPAN ) );
    250        }
    251
    252        /**
    253         * マップ内のキーと値のマッピングの数を返します。
    254         *
    255         * @return   インタフェース Map 内の size
    256         */
    257        public int size() {
    258                return attMap.size() ;
    259        }
    260
    261        /**
    262         * マップに含まれているキーと属性のペアを タグの属性リストの形式で返します。
    263         * key1="value1" key2="value2" key3="value3" .... の形式で、value が null の
    264         * 場合は，key そのもののペアを出力しません。
    265         * value が空文字列 "" の場合は，key="" で出力します。
    266         *
    267         * 引数には，key として出力したい値を配列文字列で渡します。
    268         * これは、拡張性に乏しい(すべて出せば，属性項目の追加に対応できる。)
    269         * 方法ですが、タグ毎に異なる属性のみを管理するには，厳格に出力
    270         * タグ属性を定義したいという思いから，導入しました。
    271         *
    272         * @og.rev 6.0.4.0 (2014/11/28) 内部処理見直し。値の取得は、Mapから直接取得する。
    273         * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
    274         *
    275         * @param    keys 指定 key の文字列配列(可変長引数)(大文字小文字は同値)
    276         *
    277         * @return   キーと属性のペアをタグの属性リストの形式で返します
    278         * @og.rtnNotNull
    279         */
    280//      public String getAttribute( final String[] keys ) {
    281        public String getAttribute( final String... keys ) {
    282                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    283
    284                if( keys != null ) {
    285                        for( final String key : keys ) {
    286                                if( key != null ) {
    287                                        final String value = attMap.get( key.toLowerCase( Locale.JAPAN ) );     // 6.0.4.0 (2014/11/28) Mapから直接取得する。
    288                                        if( value != null ) {
    289                                                // 6.0.2.5 (2014/10/31) char を append する。
    290                                                buf.append( key ).append("=\"").append( value ).append("\" ");
    291                                        }
    292                                }
    293                        }
    294                }
    295
    296                return buf.toString();
    297        }
    298
    299        /**
    300         * マップに含まれているキーと属性のペアを タグの属性リストの形式ですべて返します。
    301         * なお、value が null の場合は，key そのもののペアを出力しません。
    302         * value が空文字列 "" の場合は，key="" で出力します。
    303         *
    304         * @og.rev 6.0.4.0 (2014/11/28) 内部処理見直し。値の取得は、Mapから直接取得する。
    305         * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
    306         *
    307         * @return   キーと属性のペアをタグの属性リストの形式で返します
    308         * @og.rtnNotNull
    309         */
    310        public String getAttribute() {
    311                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    312
    313                // 内部に持っているすべてのデータを出力する。
    314                // よって、toLowerCase も、null チェックも不要。
    315                attMap.forEach( (k,v) -> buf.append( k ).append("=\"").append( v ).append("\" ") );
    316
    317                return buf.toString();
    318        }
    319
    320        /**
    321         * マップに含まれているキーと属性のペアを タグの属性リストの形式ですべて返します。
    322         * なお、value が nullや、空文字列("") の場合は，key そのもののペアを出力しません。
    323         * この文字列は、key:value; 形式で、文字列を作成します。value に、ダブルコーテーション
    324         * は付けません。
    325         *
    326         * @og.rev 7.0.1.0 (2018/10/15) 新規作成
    327         *
    328         * @return   キーと属性のペアをCSS形式で返します
    329         * @og.rtnNotNull
    330         */
    331        public String getCssFormat() {
    332                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    333
    334                // 内部に持っているすべてのデータを出力する。
    335                // よって、toLowerCase も、null チェックも不要。
    336                attMap.forEach( (k,v) -> { if( StringUtil.isNotNull(k) ) { buf.append( k ).append(':').append( v ).append("; "); } } );
    337
    338                return buf.toString();
    339        }
    340
    341        /**
    342         * 指定の文字列を分解して、Mapを作成します。
    343         * 『;』 で分解して、ここのｷｰと値を、『=』で分解します。
    344         * 引数の値は、そのまま適用されますので、クオーテーション等は、付けないと想定しています。
    345         *
    346         * @og.rev 7.4.2.1 (2021/05/08)
    347         *
    348         * @param  params キー=値; キー=値 形式の文字列
    349         * @return   キーと属性に分解したMap
    350         * @og.rtnNotNull
    351         */
    352        public ConcurrentMap<String,String> getParamMap( final String params ) {
    353                final ConcurrentMap<String,String> optMap = new ConcurrentHashMap<>();
    354                if( params != null ) {
    355                        final String[] prms = params.split( ";" );              // 各項目は、『;』 で分解する。
    356                        for( final String prm : prms ) {
    357                                final String[] keyval = prm.split( "=" );               // ｷｰと値は、『=』 で分解する。
    358                                if( keyval.length >= 2 ) {
    359                                        final String val = keyval[1].replaceAll( "'","" ).replaceAll( "\"","" );
    360                                        optMap.put( keyval[0].trim() , val );
    361                                }
    362                        }
    363                }
    364                return optMap ;
    365        }
    366
    367        /**
    368         * このオブジェクトの文字列表現を返します。
    369         * 基本的にデバッグ目的に使用します。
    370         *
    371         * @return      オブジェクトの文字列表現
    372         * @og.rtnNotNull
    373         */
    374        @Override
    375        public String toString() {
    376                return getAttribute() ;
    377        }
    378}