    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.hayabusa.common.HybsSystem;
    019import org.opengion.fukurou.util.LogWriter;
    020import org.opengion.fukurou.util.StringUtil;
    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 = false;           // 3.5.6.3 (2004/07/12)
    052
    053        /**
    054         * DBTableModel を設定し、このオブジェクトを初期化します。
    055         *
    056         * @param   model DBTableModelオブジェクト
    057         */
    058        public void setModel( final DBTableModel model ) {
    059                DBTableModelImpl impl = (DBTableModelImpl)model;
    060                dbColumns       = impl.dbColumns;
    061                names           = impl.names;
    062                data            = impl.data;
    063                rowHeader       = impl.rowHeader;
    064                columnMap       = impl.columnMap;
    065                overflow        = impl.overflow;
    066                numberOfColumns = impl.numberOfColumns;
    067
    068                // 3.5.5.5 (2004/04/23) 整合性キー(オブジェクトの作成時刻)追加
    069                consistencyKey  = impl.consistencyKey;
    070
    071                lastColumNo = -1;
    072                reallocateIndexes();
    073        }
    074
    075        /**
    076         * 行番号インデックスを初期化します。
    077         * 行番号をそのまま、順番に設定します。
    078         *
    079         */
    080        private void  reallocateIndexes() {
    081                int rowCount = super.getRowCount();
    082                indexes = new int[rowCount];
    083
    084                for(int row = 0; row < rowCount; row++) {
    085                        indexes[row] = row;
    086                }
    087        }
    088
    089        /**
    090         * 同一カラム番号に対する、行１と行２の値の大小を比較します。
    091         * 比較時に、そのカラムが、NUMBERタイプの場合は、Double に変換後、数字として
    092         * 比較します。それ以外の場合は、文字列の比較( row1の値.compareTo(s2) )の
    093         * 値を返します。
    094         *
    095         * row1の値 &lt; row2の値 : 負
    096         * row1の値 &gt; row2の値 : 正
    097         * row1の値 == row2の値 : 0
    098         *
    099         * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を使用する。
    100         *
    101         * @param   row1        比較元の行番号
    102         * @param   row2        比較先の行番号
    103         * @param   column      比較するカラム番号
    104         *
    105         * @return      比較結果[負/0/正]
    106         */
    107        private int compareRowsByColumn( final int row1, final int row2, final int column ) {
    108
    109                String s1 = super.getValue(row1, column);
    110                String s2 = super.getValue(row2, column);
    111
    112                if( isNumberType ) {
    113                        // 3.5.6.3 (2004/07/12) 数字型で ゼロ文字列時の処理
    114                        if( s1.length() == 0 || s2.length() == 0 ) {
    115                                return s1.length() - s2.length() ;
    116                        }
    117
    118                        double d1 = StringUtil.parseDouble( s1 );
    119                        double d2 = StringUtil.parseDouble( s2 );
    120
    121                        // 注意：引き算をすると、桁あふれする可能性があるため、比較する。
    122                        if(d1 < d2) {           return -1; }
    123                        else if(d1 > d2) {      return 1;  }
    124                        else {                          return 0;  }
    125                }
    126                else {
    127                        return s1.compareTo(s2);
    128                }
    129        }
    130
    131        /**
    132         * 内部指定のカラム(sortingColumn)に対する、行１と行２の値の大小を比較します。
    133         * 比較処理は、compareRowsByColumn( int,int,int ) を使用します。
    134         * ascending フラグ[true:昇順/false:降順] にしたがって、結果を反転します。
    135         *
    136         * ascending == true の時        ascending == false の時
    137         * row1の値 &lt; row2の値 : 負            正
    138         * row1の値 &gt; row2の値 : 正            負
    139         * row1の値 == row2の値 : 0             0
    140         *
    141         * @param       row1    比較元の行番号
    142         * @param       row2    比較先の行番号
    143         *
    144         * @return      比較結果[負/0/正]
    145         * @see     #compareRowsByColumn( int,int,int )
    146         */
    147        private int compare( final int row1, final int row2 ) {
    148                int result = compareRowsByColumn(row1, row2, sortingColumn);
    149
    150                if(result != 0) {
    151                        return ascending ? result : -result;
    152                }
    153                return 0;
    154        }
    155
    156        /**
    157         * ソートする内部データが不整合を起こしているかチェックします。
    158         * 内部行番号と、テーブルオブジェクトの件数を比較します。
    159         *
    160         * @og.rev 3.5.6.3 (2004/07/12) チェックエラー時にアベンドせずに再設定する。
    161         */
    162        private void checkModel() {
    163                if(indexes.length != super.getRowCount()) {
    164                        String errMsg = "内部行番号と、テーブルオブジェクトの件数が不一致です。 " + HybsSystem.CR
    165                                        + "Index Length=[" + indexes.length + "] , Table Row Count=[" + super.getRowCount() + "]";
    166                        LogWriter.log( errMsg );
    167                        reallocateIndexes();
    168                }
    169        }
    170
    171        /**
    172         * ソート処理のトップメソッドです。
    173         *
    174         */
    175        private void  sort() {
    176                checkModel();
    177
    178                reallocateIndexes();
    179                shuttlesort(indexes.clone(), indexes, 0, indexes.length);
    180
    181                int rowCount = indexes.length;
    182
    183                List<String[]>          newData          = new ArrayList<String[]>( rowCount );
    184                List<DBRowHeader>       newRowHeader = new ArrayList<DBRowHeader>( rowCount );
    185
    186                for( int row=0; row<rowCount; row++ ) {
    187                        newData.add( row,data.get( indexes[row] ) );
    188                        newRowHeader.add( row,rowHeader.get( indexes[row] ) );
    189                }
    190                data      = newData;
    191                rowHeader = newRowHeader;
    192        }
    193
    194        /**
    195         * シャトルソートを行います。
    196         *
    197         * @param       from    ソート元配列
    198         * @param       to              ソート先配列
    199         * @param       low             範囲(下位)
    200         * @param       high    範囲(上位)
    201         */
    202        private void shuttlesort( final int[] from, final int[] to, final int low, final int high ) {
    203                if(high - low < 2) {
    204                        return;
    205                }
    206                int middle = (low + high) >>> 1;        // widely publicized the bug pattern.
    207                shuttlesort(to, from, low, middle);
    208                shuttlesort(to, from, middle, high);
    209
    210                int pp = low;
    211                int qq = middle;
    212
    213                if(high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
    214                        for(int i = low; i < high; i++) {
    215                                to[i] = from[i];
    216                        }
    217                        return;
    218                }
    219
    220                for(int i = low; i < high; i++) {
    221                        if( qq >= high || (pp < middle && compare(from[pp], from[qq]) <= 0) ) {
    222                                to[i] = from[pp++];
    223                        }
    224                        else {
    225                                to[i] = from[qq++];
    226                        }
    227                }
    228        }
    229
    230        /**
    231         * カラム毎ソートのトップメソッドです。
    232         * デフォルトで、昇順ソートを行います。
    233         * 最後にソートしたカラムと同一のカラムが指定された場合、昇順と降順を
    234         * 反転させて、再度ソートを行います。(シャトルソート)
    235         *
    236         * @param column    カラム番号
    237         */
    238        public void sortByColumn( final int column ) {
    239                if( lastColumNo == column ) {
    240                        ascending = !ascending ;
    241                }
    242                else {
    243                        ascending = true;
    244                }
    245                sortByColumn( column,ascending );
    246        }
    247
    248        /**
    249         * カラム毎ソートのトップメソッドです。
    250         * ascending フラグ[true:昇順/false:降順]を指定します。
    251         *
    252         * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を設定する。
    253         * @og.rev 4.0.0.0 (2005/01/31) getColumnClassName 廃止。DBColumから取得する。
    254         *
    255         * @param column    カラム番号
    256         * @param ascending  ソートの方向[true:昇順/false:降順]
    257         */
    258        public void sortByColumn( final int column, final boolean ascending ) {
    259                this.ascending = ascending;
    260                sortingColumn = column;
    261                isNumberType = "NUMBER".equals( getDBColumn(sortingColumn).getClassName() );
    262                sort();
    263                lastColumNo = column;
    264        }
    265
    266        /**
    267         * ソートの方向(昇順:true/降順:false)を取得します。
    268         *
    269         * @return  ソートの方向 [true:昇順/false:降順]
    270         */
    271        public boolean isAscending() {
    272                return ascending;
    273        }
    274}