    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.db;
    017
    018import org.opengion.fukurou.system.LogWriter;
    019import org.opengion.fukurou.util.StringUtil;
    020import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.1.0.0 (2014/12/26)
    021
    022import java.util.List;
    023import java.util.ArrayList;
    024
    025/**
    026 * DBTableModelを継承した TableModelのソート機能の実装クラスです。
    027 *
    028 * ViewFormのヘッダーリンクをクリックすると、その項目について再ソートします。
    029 * これは、データベースではなく、メモリのDBTableModelにソート用のModelを
    030 * 用意し、そのModelの行番号のみをソートし、行変換を行います。
    031 * ソートを利用するかどうかは、システムパラメータ の、VIEW_USE_TABLE_SORTER 属性で
    032 * 指定します。(内部 システムパラメータ では、false 設定)
    033 * ヘッダー部に表示するリンクは、command=VIEW&amp;h_sortColumns=XXXXX で、カラム名を指定します。
    034 * ※ h_sortColumns 部は、HybsSystemにて定義しますので一般のJSPでは使用しないで下さい。
    035 *
    036 * DBTableModel インターフェースは、データベースの検索結果(Resultset)をラップする
    037 * インターフェースとして使用して下さい。
    038 *
    039 * @og.rev 3.5.4.7 (2004/02/06) 新規登録
    040 * @og.group テーブル管理
    041 *
    042 * @version  4.0
    043 * @author   Kazuhiko Hasegawa
    044 * @since    JDK5.0,
    045 */
    046public class DBTableModelSorter extends DBTableModelImpl {
    047        private int[]           indexes                 ;
    048        private int                     sortingColumn   ;
    049        private boolean         ascending               = true;
    050        private int                     lastColumNo             = -1;
    051        private boolean         isNumberType    ;               // 3.5.6.3 (2004/07/12)
    052
    053        /**
    054         * デフォルトコンストラクター
    055         *
    056         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    057         */
    058        public DBTableModelSorter() { super(); }                // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    059
    060        /**
    061         * DBTableModel を設定し、このオブジェクトを初期化します。
    062         *
    063         * @param   model DBTableModelオブジェクト
    064         */
    065        public void setModel( final DBTableModel model ) {
    066                final DBTableModelImpl impl = (DBTableModelImpl)model;
    067                dbColumns               = impl.dbColumns;
    068                names                   = impl.names;
    069                data                    = impl.data;
    070                rowHeader               = impl.rowHeader;
    071                columnMap               = impl.columnMap;
    072                overflow                = impl.overflow;
    073                numberOfColumns = impl.numberOfColumns;
    074
    075                // 3.5.5.5 (2004/04/23) 整合性キー(オブジェクトの作成時刻)追加
    076                consistencyKey  = impl.consistencyKey;
    077
    078                lastColumNo = -1;
    079                reallocateIndexes();
    080        }
    081
    082        /**
    083         * 行番号インデックスを初期化します。
    084         * 行番号をそのまま、順番に設定します。
    085         *
    086         */
    087        private void  reallocateIndexes() {
    088                final int rowCount = super.getRowCount();
    089                indexes = new int[rowCount];
    090
    091                for( int row=0; row<rowCount; row++ ) {
    092                        indexes[row] = row;
    093                }
    094        }
    095
    096        /**
    097         * 同一カラム番号に対する、行１と行２の値の大小を比較します。
    098         * 比較時に、そのカラムが、NUMBERタイプの場合は、Double に変換後、数字として
    099         * 比較します。それ以外の場合は、文字列の比較( row1の値.compareTo(s2) )の
    100         * 値を返します。
    101         *
    102         * row1の値 &lt; row2の値 : 負
    103         * row1の値 &gt; row2の値 : 正
    104         * row1の値 == row2の値 : 0
    105         *
    106         * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を使用する。
    107         * @og.rev 7.0.4.0 (2019/05/31) ← 7.0.1.9 (2019/02/04) 数値タイプでwritableControl機能使用時の対応
    108         *
    109         * @param   row1        比較元の行番号
    110         * @param   row2        比較先の行番号
    111         * @param   column      比較するカラム番号
    112         *
    113         * @return      比較結果[負/0/正]
    114         */
    115        private int compareRowsByColumn( final int row1, final int row2, final int column ) {
    116
    117                // 7.0.1.9 (2019/02/04) 数値タイプでwritableControl機能使用時の対応
    118//              final String s1 = super.getValue(row1, column);
    119//              final String s2 = super.getValue(row2, column);
    120                String s1 = super.getValue(row1, column);
    121                String s2 = super.getValue(row2, column);
    122
    123                if( isNumberType ) {
    124                        // 3.5.6.3 (2004/07/12) 数字型で ゼロ文字列時の処理
    125                        if( s1.isEmpty() || s2.isEmpty() ) {
    126                                return s1.length() - s2.length() ;
    127                        }
    128
    129                        if( s1.charAt(0) == '_' ) { s1 = s1.substring(1); }             // 7.0.1.9 (2019/02/04) 先頭に '_' があれば削除
    130                        if( s2.charAt(0) == '_' ) { s2 = s2.substring(1); }             // 7.0.1.9 (2019/02/04) 先頭に '_' があれば削除
    131
    132                        final double d1 = StringUtil.parseDouble( s1 );
    133                        final double d2 = StringUtil.parseDouble( s2 );
    134
    135                        // 注意：引き算をすると、桁あふれする可能性があるため、比較する。
    136                        if(      d1 < d2 ) { return -1; }
    137                        else if( d1 > d2 ) { return 1;  }
    138                        else {                           return 0;  }
    139                }
    140                else {
    141                        return s1.compareTo(s2);
    142                }
    143        }
    144
    145        /**
    146         * 内部指定のカラム(sortingColumn)に対する、行１と行２の値の大小を比較します。
    147         * 比較処理は、compareRowsByColumn( int,int,int ) を使用します。
    148         * ascending フラグ[true:昇順/false:降順] にしたがって、結果を反転します。
    149         *
    150         * ascending == true の時        ascending == false の時
    151         * row1の値 &lt; row2の値 : 負            正
    152         * row1の値 &gt; row2の値 : 正            負
    153         * row1の値 == row2の値 : 0             0
    154         *
    155         * @param       row1    比較元の行番号
    156         * @param       row2    比較先の行番号
    157         *
    158         * @return      比較結果[負/0/正]
    159         * @see     #compareRowsByColumn( int,int,int )
    160         */
    161        private int compare( final int row1, final int row2 ) {
    162                final int result = compareRowsByColumn(row1, row2, sortingColumn);
    163                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
    164                // 条件反転注意
    165                return result == 0 ? 0 : ascending ? result : -result;
    166        }
    167
    168        /**
    169         * ソートする内部データが不整合を起こしているかチェックします。
    170         * 内部行番号と、テーブルオブジェクトの件数を比較します。
    171         *
    172         * @og.rev 3.5.6.3 (2004/07/12) チェックエラー時にアベンドせずに再設定する。
    173         */
    174        private void checkModel() {
    175                if( indexes.length != super.getRowCount() ) {
    176                        final String errMsg = "内部行番号と、テーブルオブジェクトの件数が不一致です。 " + CR
    177                                        + "Index Length=[" + indexes.length + "] , Table Row Count=[" + super.getRowCount() + "]";
    178                        LogWriter.log( errMsg );
    179                        reallocateIndexes();
    180                }
    181        }
    182
    183        /**
    184         * ソート処理のトップメソッドです。
    185         *
    186         */
    187        private void  sort() {
    188                checkModel();
    189
    190                reallocateIndexes();
    191                shuttlesort(indexes.clone(), indexes, 0, indexes.length);
    192
    193                final int rowCount = indexes.length;
    194
    195                final List<String[]>    newData          = new ArrayList<>( rowCount );
    196                final List<DBRowHeader> newRowHeader = new ArrayList<>( rowCount );
    197
    198                for( int row=0; row<rowCount; row++ ) {
    199                        newData.add( row,data.get( indexes[row] ) );
    200                        newRowHeader.add( row,rowHeader.get( indexes[row] ) );
    201                }
    202                data      = newData;
    203                rowHeader = newRowHeader;
    204        }
    205
    206        /**
    207         * シャトルソートを行います。
    208         *
    209         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
    210         *
    211         * @param       from    ソート元配列
    212         * @param       to              ソート先配列
    213         * @param       low             範囲(下位)
    214         * @param       high    範囲(上位)
    215         */
    216        private void shuttlesort( final int[] from, final int[] to, final int low, final int high ) {
    217                if( high - low < 2 ) {
    218                        return;
    219                }
    220                final int middle = (low + high) >>> 1;  // widely publicized the bug pattern.
    221                shuttlesort(to, from, low, middle);
    222                shuttlesort(to, from, middle, high);
    223
    224                if( high - low >= 4 && compare(from[middle-1], from[middle]) <= 0 ) {
    225                        // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
    226                        System.arraycopy( from,low,to,low,high-low );           // 6.3.6.0 (2015/08/16)
    227                        return;
    228                }
    229
    230                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
    231                int pp = low;
    232                int qq = middle;
    233                for( int i=low; i<high; i++ ) {
    234                        if( qq >= high || pp < middle && compare( from[pp], from[qq] ) <= 0 ) {         // 6.9.7.0 (2018/05/14) PMD Useless parentheses.
    235                                to[i] = from[pp++];
    236                        }
    237                        else {
    238                                to[i] = from[qq++];
    239                        }
    240                }
    241        }
    242
    243        /**
    244         * カラム毎ソートのトップメソッドです。
    245         * デフォルトで、昇順ソートを行います。
    246         * 最後にソートしたカラムと同一のカラムが指定された場合、昇順と降順を
    247         * 反転させて、再度ソートを行います。(シャトルソート)
    248         *
    249         * @param column    カラム番号
    250         */
    251        public void sortByColumn( final int column ) {
    252                if( lastColumNo == column ) {
    253                        ascending = !ascending ;
    254                }
    255                else {
    256                        ascending = true;
    257                }
    258                sortByColumn( column,ascending );
    259        }
    260
    261        /**
    262         * カラム毎ソートのトップメソッドです。
    263         * ascending フラグ[true:昇順/false:降順]を指定します。
    264         *
    265         * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を設定する。
    266         * @og.rev 4.0.0.0 (2005/01/31) getColumnClassName 廃止。DBColumから取得する。
    267         * @og.rev 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
    268         * @og.rev 6.4.6.0 (2016/05/27) isNumber , isDate 追加。
    269         *
    270         * @param column    カラム番号
    271         * @param ascending  ソートの方向[true:昇順/false:降順]
    272         */
    273        public void sortByColumn( final int column, final boolean ascending ) {
    274                this.ascending = ascending;
    275                sortingColumn = column;
    276                // 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
    277                isNumberType = getDBColumn(sortingColumn).isNumberType();                       // 6.4.6.0 (2016/05/27)
    278                sort();
    279                lastColumNo = column;
    280        }
    281
    282        /**
    283         * ソートの方向(昇順:true/降順:false)を取得します。
    284         *
    285         * @return  ソートの方向 [true:昇順/false:降順]
    286         */
    287        public boolean isAscending() {
    288                return ascending;
    289        }
    290}