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&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         * 同一カラム番号に対する、行1と行2の値の大小を比較します。
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)に対する、行1と行2の値の大小を比較します。
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}