    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.hayabusa.html;
    017
    018import org.opengion.hayabusa.db.DBTableModel;
    019import org.opengion.hayabusa.db.DBColumn;
    020import org.opengion.fukurou.util.StringUtil;
    021import org.opengion.fukurou.util.Attributes;
    022import org.opengion.fukurou.model.Formatter;
    023import static org.opengion.fukurou.system.HybsConst.BUFFER_LARGE;               // 6.1.0.0 (2014/12/26) refactoring
    024
    025import java.util.concurrent.ConcurrentMap;                                                              // 6.4.3.3 (2016/03/04)
    026import java.util.concurrent.ConcurrentHashMap;                                                  // 6.4.3.1 (2016/02/12) refactoring
    027import java.util.List;
    028import java.util.ArrayList;
    029import java.util.Arrays ;
    030
    031/**
    032 * ViewMarker インターフェース の実装オブジェクトです。
    033 * これを共通のスーパークラスとして 各種表示フォーム(例：HTML表示等)に使います。
    034 *
    035 * このクラスは、setter/getterメソッドのデフォルト実装を提供しています。
    036 * 各種表示フォームに対応したサブクラス上で、 create() をオーバーライドして下さい。
    037 *
    038 * @og.group 画面表示
    039 *
    040 * @version  4.0
    041 * @author   Kazuhiko Hasegawa
    042 * @since    JDK5.0,
    043 */
    044public class ViewMarker_MARKER implements ViewMarker {
    045
    046        private static final int        MARK_NULL   = -1;       // マーカー未設定
    047        private static final int        MARK_TRUE   = 1;        // マーカー作成
    048        private static final int        MARK_FALSE  = 0;        // マーカー作成せず
    049
    050        private List<Attributes>                markData        ;               // 4.0.0 (2005/08/31)
    051        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
    052        private final ConcurrentMap<Integer,Formatter>  formMap         = new ConcurrentHashMap<>();    // 6.4.3.1 (2016/02/12)
    053        private DBTableModel            table                   ;
    054        private int[]                           markCmlNo               ;
    055        private int[]                           isMark                  ;
    056        // 3.5.2.0 (2003/10/20)
    057        private String[]                        markKey                 ;
    058        private String[]                        markLists               ;
    059        private String[]                        instrVals               ;               // 3.8.8.1 (2007/01/06)
    060        private int[]                           markListNo              ;
    061        private boolean[]                       useFmtDeco              ;               // 5.6.3.0 (2013/04/01) [$XXXX],[#XXXX]機能を有効にするかどうか(true:有効)
    062
    063        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。 */
    064        private final ConcurrentMap<Integer,List<Integer>>      clmMap  = new ConcurrentHashMap<>();    // 6.4.3.1 (2016/02/12)
    065
    066        /**
    067         * デフォルトコンストラクター
    068         *
    069         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    070         */
    071        public ViewMarker_MARKER() { super(); }         // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    072
    073        /**
    074         * 内容をクリア(初期化)します。
    075         *
    076         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
    077         * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
    078         * @og.rev 3.5.6.1 (2004/06/25) formMap属性を追加
    079         * @og.rev 3.8.8.1 (2007/01/06) instrVals属性を追加
    080         * @og.rev 5.6.3.0 (2013/04/01) useFmtDeco属性を追加
    081         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
    082         *
    083         */
    084        @Override       // ViewMarker
    085        public void clear() {
    086                markData        = null;         // 4.0.0 (2005/08/31)
    087                formMap.clear();                // 6.4.3.3 (2016/03/04)
    088                table           = null;
    089                isMark          = null;
    090                markKey         = null;
    091                markLists       = null;
    092                instrVals       = null;         // 3.8.8.1 (2007/01/06)
    093                markListNo      = null;
    094                clmMap.clear();                 // 6.4.3.3 (2016/03/04)
    095                useFmtDeco      = null;         // 5.6.3.0 (2013/04/01) [$XXXX],[#XXXX]機能を有効にするかどうか(true:有効)
    096        }
    097
    098        /**
    099         * カラムに対するマーカーアトリビュートをセットします。
    100         *
    101         * @og.rev 3.1.0.0 (2003/03/20) Hashtable を使用している箇所で、非同期でも構わない箇所を、HashMap に置換え。
    102         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
    103         *
    104         * @param       attri   アトリビュート
    105         */
    106        @Override       // ViewMarker
    107        public void addAttribute( final Attributes attri ) {
    108                if( markData == null ) { markData = new ArrayList<>(); }
    109                markData.add( attri );
    110        }
    111
    112        /**
    113         * このマーカーが、初期化されているかどうかを判定します。
    114         *
    115         * 使用できる状態の場合は、true , 初期化が出来ていない場合は、false を返します。
    116         *
    117         * @og.rev 6.7.2.0 (2017/01/16) caseKey,caseVal等で未使用のときの対応。
    118         *
    119         * @return      初期化状況 [true:初期化済み/false:未初期化]
    120         */
    121        @Override       // ViewMarker
    122        public boolean isUsable() {
    123//              return markData != null && markData.size() > 0 ;                // 本当は、ゼロということは無いはず。
    124                return markData != null && !markData.isEmpty() ;                // 本当は、ゼロということは無いはず。    // 6.9.7.0 (2018/05/14) PMD
    125        }
    126
    127        /**
    128         * 内部に DBTableModel をセットします。
    129         *
    130         * @og.rev 3.1.1.0 (2003/03/28) 同期メソッド(synchronized付き)を非同期に変更する。
    131         * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
    132         * @og.rev 3.5.6.1 (2004/06/25) DBTableModel の再設定に対応。
    133         * @og.rev 3.8.8.1 (2007/01/06) instrVals属性を追加
    134         * @og.rev 5.6.3.0 (2013/04/01) useFmtDeco属性を追加
    135         * @og.rev 6.4.3.3 (2016/03/04) Map#computeIfAbsent で対応する。
    136         * @og.rev 6.4.3.4 (2016/03/11) Formatterに新しいコンストラクターを追加する。
    137         * @og.rev 6.7.6.0 (2017/03/17) strictCheck 追加。
    138         *
    139         * @param  tbl DBTableModelオブジェクト
    140         */
    141        @Override       // ViewMarker
    142        public void setDBTableModel( final DBTableModel tbl ) {
    143                table = tbl;
    144                final int count = markData.size();                      // 4.0.0 (2005/08/31)
    145
    146                isMark          = new int[count];
    147                markKey         = new String[count];
    148                markCmlNo       = new int[count];
    149                markLists       = new String[count];
    150                instrVals       = new String[count];
    151                markListNo      = new int[count];
    152                useFmtDeco      = new boolean[count];   // 5.6.3.0 (2013/04/01) [$XXXX],[#XXXX]機能を有効にするかどうか(true:有効)
    153
    154                Arrays.fill( isMark,MARK_FALSE );       // マーカーの表示可否
    155                Arrays.fill( markCmlNo,-1 );            // マーカーの可否を判断するカラム番号
    156                Arrays.fill( useFmtDeco,false );        // [$XXXX],[#XXXX]機能を無効にする。(互換性のため)
    157
    158                for( int intKey=0; intKey<count; intKey++ ) {
    159                        final Attributes attri = markData.get( intKey );
    160
    161                        final String column = attri.get( "column" );
    162
    163                        // 6.7.6.0 (2017/03/17) カラムのDBTableModel存在チェック。初期値が true なので、attri に無い場合も、true になる。
    164                        final String strChk = attri.get( "strictCheck" );
    165                        final boolean strictCheck = ! "false".equalsIgnoreCase( strChk );
    166
    167                        // 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
    168                        final int clm = table.getColumnNo( column,strictCheck );        // 6.7.6.0 (2017/03/17) strictCheck で、true の場合は、clm番号が見つからないときは、Exception発生
    169
    170                        if( clm < 0 ) { continue; }             // 6.7.6.0 (2017/03/17) 存在しない場合、以下の処理を行わない。＝ clmMap に、カラムが登録されない。
    171
    172                        // 6.4.3.3 (2016/03/04) Map#computeIfAbsent で対応する。
    173                        // Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
    174                        clmMap.computeIfAbsent( clm,k -> new ArrayList<>() ).add( intKey );
    175
    176                        final String body = attri.get( "body" );
    177                        final Formatter formatter = new Formatter( table,body );                // 6.4.3.4 (2016/03/11)
    178                        // 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
    179                        formMap.put( intKey, formatter );
    180
    181                        makeOnMarkFormat( intKey,attri );
    182
    183                        useFmtDeco[intKey] = "true".equalsIgnoreCase( attri.get( "useFormatDeco" ) );   // 5.6.3.0 (2013/04/01)
    184                }
    185        }
    186
    187        /**
    188         * 指定の行列に対するマーカー文字列を返します。
    189         * この値は、すでにマーカー文字列処理されている為、RendererValue で
    190         * 変換する必要はありません。
    191         * 引数の value はそのカラムの値として利用されます。この値は、修飾済みの
    192         * 値を与えることが可能です。
    193         *
    194         * @og.rev 3.5.6.1 (2004/06/25) formMap属性を使用します。
    195         * @og.rev 3.8.8.1 (2007/01/06) instrVals属性を追加
    196         * @og.rev 5.3.9.0 (2011/09/01) カラム名の先頭に'$'を付加した場合に、URLEncodeされた値を返すように対応
    197         * @og.rev 5.6.3.0 (2013/04/01) useFmtDeco属性を追加([$XXXX],[#XXXX]機能を有効にするかどうか)
    198         * @og.rev 6.2.4.0 (2015/05/15) useFmtDeco属性とは無関係に、[!XXXX](値)を対応します。
    199         * @og.rev 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    200         *
    201         * @param   row 指定の行
    202         * @param   clm 指定の列
    203         * @param   value カラムの値(マーカー文字列処理済)
    204         *
    205         * @return  row行、colum列 のマーカー文字列
    206         */
    207        @Override       // ViewMarker
    208        public String getMarkerString( final int row,final int clm,final String value ) {
    209                final int intKey = isOnMark(row,clm) ;
    210                if( intKey < 0 ) { return value; }
    211
    212                final Formatter formatter = formMap.get( intKey );
    213                final int[]    clmNo  = formatter.getClmNos();
    214                final String[] format = formatter.getFormat();
    215                final char[]   types  = formatter.getType();
    216
    217                final StringBuilder buf = new StringBuilder( BUFFER_LARGE );
    218                int j=0;
    219                String val ;
    220                for( ; j<clmNo.length; j++ ) {
    221                        // 6.2.4.0 (2015/05/15) [!XXXX](値)を対応
    222                        if( clm == clmNo[j] && types[j] != '!' ) {
    223                                val = value;                                                            // 一致する場合の valueは、通常レンデラー
    224                        }
    225                        else {
    226                                val = formatter.getValue(row,clmNo[j]);         // 生の値
    227                        }
    228
    229                        // 5.6.3.0 (2013/04/01) useFmtDeco属性を追加(trueの場合は、[$XXXX],[#XXXX]機能を有効にする)
    230                        if( useFmtDeco[intKey] ) {
    231                                final DBColumn dbClm = table.getDBColumn( clmNo[j] );
    232                                final String   prm   = formatter.getClmParam(j);        // 6.8.3.1 (2017/12/01)
    233                                if( types[j] == '$' ) {                 // レンデラー
    234                                        // 6.8.3.1 (2017/12/01) [$XXXX param] で、RendererValueのパラメータを動的に変更できます。
    235                                        val = formatter.getValue(row,clmNo[j]);                 // ※ 互換性の関係で、useFmtDecoで、'$' のときのみ、生の値を再取得します。
    236                                        val = prm == null || prm.isEmpty()
    237                                                                        ? dbClm.getRendererValue( row,val )
    238                                                                        : dbClm.getRendererValue( row,val,prm );
    239                                }
    240                                else if( types[j] == '#' ) {    // ラベル
    241                                        if( prm == null || prm.isEmpty() ) {
    242                                                val = dbClm.getLabel();
    243                                        }
    244                                        else {
    245                                                // 6.8.3.1 (2017/12/01) [#XXXX param] で、RendererValueのパラメータを動的に変更できます。
    246                                                switch( prm.charAt(0) ) {               // 先頭の文字(文字列のswitchでもよい)
    247                                                        case 'L': val = dbClm.getLabel();                       break;
    248                                                        case 'S': val = dbClm.getShortLabel();          break;
    249                                                        case 'T': val = dbClm.getLongLabel();           break;
    250                                                        case 'D': val = dbClm.getDescription();         break;
    251                                                        default : val = dbClm.getLabel();                       break;
    252                                                }
    253                                        }
    254                                }
    255                        }
    256                        // false が以前と同じ処理（互換処理）ただし、view などのフォーマット処理とは異なる。
    257                        else {
    258                                // 5.3.9.0 (2011/09/01) カラム名の先頭に'$'を付加した場合URLEncodeされた値を返すように対応
    259                                if( types[j] == '$' ) {
    260                                        val = StringUtil.urlEncode( val );
    261                                }
    262                        }
    263
    264                        buf.append( format[j] ).append( val );
    265                }
    266                if( j < format.length ) { buf.append( format[j] ); }
    267                String rtn = StringUtil.replace( buf.toString(),"{I}",String.valueOf( row ) );
    268
    269                // 3.8.8.1 (2007/01/06) instrVals属性を追加
    270                if( instrVals[intKey] != null ) {
    271                        final String[] vals = StringUtil.csv2Array( instrVals[intKey],' ' );
    272                        for( int i=0; i<vals.length; i++ ) {
    273                                final String css = "<span class=\"instr" + i + "\">" + vals[i] + "</span>";
    274                                rtn = StringUtil.replace( rtn,vals[i],css );
    275                        }
    276                }
    277                return rtn ;
    278        }
    279
    280        /**
    281         * マーカーを作成する/作成しないの指定カラム番号を求めます。
    282         * また、int[列番号] isMark を初期化します。
    283         *
    284         * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
    285         * @og.rev 3.8.8.1 (2007/01/06) instrVals属性を追加
    286         *
    287         * @param       intKey  カラムキーの番号
    288         * @param       attri   アトリビュート
    289         */
    290        private void makeOnMarkFormat( final int intKey,final Attributes attri ) {
    291                final String onMark   = attri.get( "onMark" );
    292                final String markList = attri.get( "markList" );
    293                instrVals[intKey] = attri.get( "instrVals" );   // 3.8.8.1 (2007/01/06)
    294
    295                // 3.5.6.0 (2004/06/18) nullポインタの参照外しバグの対応
    296                // このロジックで値が設定済みであれば、以下の処理は不要である。
    297                isMark[intKey] = MARK_NULL;
    298                if( onMark == null || onMark.isEmpty() ||
    299                        markList == null || markList.isEmpty() ) {
    300                                isMark[intKey] = MARK_FALSE;
    301                                return ;        // 3.5.6.0 (2004/06/18)
    302                }
    303                else if( onMark.charAt(0) != '[' && markList.charAt(0) != '[' ) {
    304                        isMark[intKey] = markList.indexOf( onMark ) >= 0 ? MARK_TRUE : MARK_FALSE;
    305                        return ;        // 3.5.6.0 (2004/06/18)
    306                }
    307
    308                if( onMark.charAt(0) == '[' ) {
    309                        markCmlNo[intKey] = table.getColumnNo( onMark.substring( 1,onMark.length()-1 ));
    310                }
    311                else {
    312                        markCmlNo[intKey]  = -1;
    313                        markKey[intKey]    = onMark ;
    314                }
    315
    316                if( markList.charAt(0) == '[' ) {
    317                        markListNo[intKey] = table.getColumnNo( markList.substring( 1,markList.length()-1 ));
    318                }
    319                else {
    320                        markListNo[intKey] = -1;
    321                        markLists[intKey] = markList;
    322                }
    323        }
    324
    325        /**
    326         * マーカーを作成するかどうかを判断します。
    327         * int[列番号] isMark には、 未設定 FALSE TRUE の状態を持っており、
    328         * 列でマーカーを作成する状態が固定の場合(例えば、onMark属性がデフォルト "true" の場合)
    329         * カラムに関係なく、同じ値を返すときに、使用します。
    330         *
    331         * @og.rev 3.5.2.0 (2003/10/20) markLists,markListNo,markKey属性を追加
    332         * @og.rev 3.5.4.0 (2003/11/25) onMark ,markList が null(またはゼロストリング)の場合は、false とする。
    333         * @og.rev 4.0.0.0 (2005/08/31) 同一カラムの複数登録を許可します。
    334         * @og.rev 6.7.2.0 (2017/01/16) markListの先頭が、"?" の場合、正規表現で判定します。
    335         *
    336         * @param       row     列番号
    337         * @param       clm     カラムキーの名称
    338         *
    339         * @return      処理するリスト番号、-1 の場合は、該当なし
    340         */
    341        private int isOnMark( final int row,final int clm ) {
    342                final List<Integer> list = clmMap.get( clm );
    343                if( list == null ) { return -1; }
    344
    345                for( int i=0; i<list.size(); i++ ) {
    346                        final int intKey = list.get( i );
    347                        if( isMark[intKey] != MARK_NULL ) {
    348                                if( isMark[intKey] == MARK_TRUE ) { return intKey; }
    349                                else { continue; }
    350                        }
    351
    352                        final String onMark ;
    353                        if( markCmlNo[intKey] < 0 ) { onMark = markKey[intKey] ; }
    354                        else { onMark = table.getValue( row,markCmlNo[intKey] ); }
    355
    356                        // 3.5.4.0 (2003/11/25) 追加
    357                        if( onMark == null || onMark.isEmpty() ) { continue; }
    358
    359                        final String markList ;
    360                        if( markListNo[intKey] < 0 ) { markList = markLists[intKey] ; }
    361                        else { markList = table.getValue( row,markListNo[intKey] ); }
    362
    363                        // 3.5.4.0 (2003/11/25) 修正
    364                        if( markList == null || markList.isEmpty() ) { continue; }
    365
    366                        // 6.7.2.0 (2017/01/16) markListの先頭が、"?" の場合、正規表現で判定します。
    367                        if( markList.charAt(0) == '?' ) {
    368                                if( onMark.matches( markList.substring(1) ) ) {
    369                                        return intKey;
    370                                }
    371                        }
    372                        else if( markList.indexOf( onMark ) >= 0 ) { return intKey; }
    373                }
    374                return -1;
    375        }
    376
    377        /**
    378         * マーカーされたカラム番号の配列を返します。
    379         *
    380         * これは特殊処理で、Edit機能で、カラム列をキャッシュしているときに、
    381         * JSPのソース等の変更時に、変更が反映されない対応を行う場合、
    382         * 通常の ViewFormのサブクラスから、Edit専用の ViewForm_HTMLSeqClmTable で
    383         * 制御する場合、ViewMarkerのEditMarkerでは、通常非表示（検索の場合）ですが
    384         * Editのポップアップ画面に、表示されてしまうのを避けるため、noDisplay に
    385         * 強制的にするカラム番号が必要です。
    386         * あくまで、暫定処置です。Edit機能を改修するときに、この機能は削除します。
    387         *
    388         * ※ この処理は、EditMarkerでのみ有効にします。
    389         *
    390         * @og.rev 6.0.3.0 (2014/11/13) Edit機能で、JSPソース変更時の対応
    391         *
    392         * @return  マーカーされたカラム番号の配列
    393         */
    394        @Override       // ViewMarker
    395        public int[] getColumnNos() {
    396                final int[] rtn = new int[clmMap.size()];
    397                int i=0;
    398                for( final Integer obj : clmMap.keySet() ) {
    399                        rtn[i++] = obj.intValue();
    400                }
    401
    402                return rtn;
    403        }
    404}