    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 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.List;
    021import java.util.ArrayList;
    022import java.util.Arrays;
    023
    024import org.opengion.hayabusa.common.HybsSystemException;                        // 6.4.3.3 (2016/03/04)
    025import org.opengion.fukurou.util.StringUtil ;
    026import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.1.0.0 (2014/12/26) refactoring
    027
    028/**
    029 * String[] 型キーにカラム列の連想記憶を用いた、クロス集計データを管理するクラスです。
    030 *
    031 * クロス集計では、カラム列が、データとして与えられる為、このクラス内部で、
    032 * 一旦カラム列の連想記憶(Map)データを作成し、実際の行データ登録時にデータを
    033 * 設定しています。
    034 * 取り出すときは、一気に取り出すことを考慮して、配列(ArrayList)データに
    035 * 共有しているオブジェクトを取り出します。
    036 *
    037 * この実装は同期化されません。
    038 *
    039 * @og.rev 3.5.4.0 (2003/11/25) 新規作成
    040 * @og.group 画面表示
    041 *
    042 * @version  4.0
    043 * @author   Kazuhiko Hasegawa
    044 * @since    JDK5.0,
    045 */
    046public final class CrossMap {
    047        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
    048        private final ConcurrentMap<String,String[]> rowMap = new ConcurrentHashMap<>() ;
    049        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
    050        private final ConcurrentMap<String,Integer>  clmMap = new ConcurrentHashMap<>() ;
    051        private final List<String[]> list = new ArrayList<>();
    052        private final int headCount;
    053        private final int sumCount;
    054        private final int totalCols;
    055
    056        /**
    057         * カラム部(クロス集計部)を与えて新しく作成するコンストラクター
    058         *
    059         * クロス集計を行うカラム部のみセットします。
    060         * 行のクロス集計部のヘッダーキーは、引数の配列の順番で、設定されます。
    061         * この行のヘッダー部となるデータは、addData 時にセットします。
    062         *
    063         * @param       clmData         クロス集計部のカラム名配列
    064         * @param       headCount       HEADカラムの数
    065         * @param       sumCount        合計カラムの数
    066         */
    067        public CrossMap( final String[] clmData, final int headCount, final int sumCount ) {
    068                if( headCount <= 0 ) {
    069                        final String errMsg = "headCount は、ROWカラムを含むため、最低１以上必要です。";
    070                        throw new IllegalArgumentException( errMsg );
    071                }
    072
    073                this.headCount = headCount;
    074                this.sumCount  = sumCount;
    075                final int clmNum = clmData.length;
    076                totalCols = headCount + clmNum * sumCount;
    077                for( int i=0; i<clmNum; i++ ) {
    078                        clmMap.put( clmData[i],Integer.valueOf( i ) );
    079                }
    080        }
    081
    082        /**
    083         * クロス集計の元となる検索結果の行データを登録します。
    084         *
    085         * クロス集計を行うカラム部のみ,セットします。
    086         * 行のヘッダー部となるデータは、rowKeys と headCount で指定します。
    087         * 行のクロス集計部のヘッダーキーは、clmKey で指定し、内部で、列カラムとして
    088         * 割り当てられます。割り当て順(カラム順)は、コンストラクタでの clmData の
    089         * 配列の順番です。
    090         *
    091         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
    092         * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
    093         *
    094         * @param       rowKeys 行データの配列(可変長引数)( 0～headCount の値が行のキーとなります。)
    095         */
    096        public void add( final String... rowKeys ) {
    097                if( rowKeys.length < headCount + 1 + sumCount ) {
    098                        final String errMsg = "指定の rowKeys の個数が不正です。 rowKeys には、clmKey と data が必要です。"
    099                                                + " rowKeys=" + StringUtil.array2csv( rowKeys ) ;       // 5.1.8.0 (2010/07/01) errMsg 修正
    100                        throw new ArrayIndexOutOfBoundsException( errMsg );
    101                }
    102
    103                // 3.5.6.6 (2004/08/23) 修正
    104//              final String clmKey = rowKeys[headCount];               // カラム列のクロス集計部のキー(ヘッダー)
    105                String[] data = new String[sumCount];                   // クロス集計表の値
    106                for( int i=0; i<sumCount; i++ ) {
    107                        data[i] = rowKeys[headCount+1+i];
    108                }
    109
    110                final String rowKey ;
    111                // 3.5.6.6 (2004/08/23) 修正
    112                if( headCount == 1 ) { rowKey = rowKeys[0]; }
    113                else {
    114                        final StringBuilder rKey = new StringBuilder( BUFFER_MIDDLE );
    115                        for( int i=0; i<headCount; i++ ) {
    116                                rKey.append( rowKeys[i] ).append( '_' );                        // 6.0.2.5 (2014/10/31) char を append する。
    117                        }
    118                        rowKey = rKey.toString();
    119                }
    120
    121                // 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限
    122                if( rowKey == null ) {
    123                        final String errMsg = "指定の rowKey が、null です。";
    124                        throw new HybsSystemException( errMsg );
    125                }
    126
    127                String[] clmData = rowMap.get( rowKey );
    128                if( clmData == null ) {
    129                        // 行データ+クロス行データ
    130                        clmData = new String[totalCols];
    131                        Arrays.fill( clmData,"" );
    132                        // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
    133                        System.arraycopy( rowKeys,0,clmData,0,headCount );              // 6.3.6.0 (2015/08/16)
    134                        list.add( clmData );    // 生成順にArrayList にセーブします。
    135                }
    136
    137                final String clmKey = rowKeys[headCount];               // カラム列のクロス集計部のキー(ヘッダー) 6.9.7.0 (2018/05/14) PMD before a possible exit point.
    138                for( int i=0; i<sumCount; i++ ) {
    139                        final int no = headCount + clmMap.get( clmKey ).intValue()*sumCount+i;  // 列番号
    140                        clmData[no] = data[i];
    141                }
    142                rowMap.put( rowKey,clmData );   // ArrayList と同じオブジェクトを検索用のMapにもセットする。
    143        }
    144
    145        /**
    146         * クロス集計結果の指定行の列データを返します。
    147         *
    148         * @param       row 指定の行番号( 0 .. getSize()-1 )
    149         *
    150         * @return      列データ配列
    151         */
    152        public String[] get( final int row ) {
    153                return list.get( row );
    154        }
    155
    156        /**
    157         * クロス集計結果の行数を返します。
    158         *
    159         * @return   行数を返します。
    160         */
    161        public int getSize() {
    162                return list.size();
    163        }
    164}