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&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         * 同一カラム番号に対する、行1と行2の値の大小を比較します。
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         *
108         * @param   row1        比較元の行番号
109         * @param   row2        比較先の行番号
110         * @param   column      比較するカラム番号
111         *
112         * @return      比較結果[負/0/正]
113         */
114        private int compareRowsByColumn( final int row1, final int row2, final int column ) {
115
116                final String s1 = super.getValue(row1, column);
117                final String s2 = super.getValue(row2, column);
118
119                if( isNumberType ) {
120                        // 3.5.6.3 (2004/07/12) 数字型で ゼロ文字列時の処理
121                        if( s1.isEmpty() || s2.isEmpty() ) {
122                                return s1.length() - s2.length() ;
123                        }
124
125                        final double d1 = StringUtil.parseDouble( s1 );
126                        final double d2 = StringUtil.parseDouble( s2 );
127
128                        // 注意:引き算をすると、桁あふれする可能性があるため、比較する。
129                        if(      d1 < d2 ) { return -1; }
130                        else if( d1 > d2 ) { return 1;  }
131                        else {                           return 0;  }
132                }
133                else {
134                        return s1.compareTo(s2);
135                }
136        }
137
138        /**
139         * 内部指定のカラム(sortingColumn)に対する、行1と行2の値の大小を比較します。
140         * 比較処理は、compareRowsByColumn( int,int,int ) を使用します。
141         * ascending フラグ[true:昇順/false:降順] にしたがって、結果を反転します。
142         *
143         * ascending == true の時        ascending == false の時
144         * row1の値 &lt; row2の値 : 負            正
145         * row1の値 &gt; row2の値 : 正            負
146         * row1の値 == row2の値 : 0             0
147         *
148         * @param       row1    比較元の行番号
149         * @param       row2    比較先の行番号
150         *
151         * @return      比較結果[負/0/正]
152         * @see     #compareRowsByColumn( int,int,int )
153         */
154        private int compare( final int row1, final int row2 ) {
155                final int result = compareRowsByColumn(row1, row2, sortingColumn);
156                // 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
157                // 条件反転注意
158                return result == 0 ? 0 : ascending ? result : -result;
159
160        }
161
162        /**
163         * ソートする内部データが不整合を起こしているかチェックします。
164         * 内部行番号と、テーブルオブジェクトの件数を比較します。
165         *
166         * @og.rev 3.5.6.3 (2004/07/12) チェックエラー時にアベンドせずに再設定する。
167         */
168        private void checkModel() {
169                if( indexes.length != super.getRowCount() ) {
170                        final String errMsg = "内部行番号と、テーブルオブジェクトの件数が不一致です。 " + CR
171                                        + "Index Length=[" + indexes.length + "] , Table Row Count=[" + super.getRowCount() + "]";
172                        LogWriter.log( errMsg );
173                        reallocateIndexes();
174                }
175        }
176
177        /**
178         * ソート処理のトップメソッドです。
179         *
180         */
181        private void  sort() {
182                checkModel();
183
184                reallocateIndexes();
185                shuttlesort(indexes.clone(), indexes, 0, indexes.length);
186
187                final int rowCount = indexes.length;
188
189                final List<String[]>            newData          = new ArrayList<>( rowCount );
190                final List<DBRowHeader> newRowHeader = new ArrayList<>( rowCount );
191
192                for( int row=0; row<rowCount; row++ ) {
193                        newData.add( row,data.get( indexes[row] ) );
194                        newRowHeader.add( row,rowHeader.get( indexes[row] ) );
195                }
196                data      = newData;
197                rowHeader = newRowHeader;
198        }
199
200        /**
201         * シャトルソートを行います。
202         *
203         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
204         *
205         * @param       from    ソート元配列
206         * @param       to              ソート先配列
207         * @param       low             範囲(下位)
208         * @param       high    範囲(上位)
209         */
210        private void shuttlesort( final int[] from, final int[] to, final int low, final int high ) {
211                if( high - low < 2 ) {
212                        return;
213                }
214                final int middle = (low + high) >>> 1;  // widely publicized the bug pattern.
215                shuttlesort(to, from, low, middle);
216                shuttlesort(to, from, middle, high);
217
218                if( high - low >= 4 && compare(from[middle-1], from[middle]) <= 0 ) {
219                        // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
220                        System.arraycopy( from,low,to,low,high-low );           // 6.3.6.0 (2015/08/16)
221                        return;
222                }
223
224                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid declaring a variable if it is unreferenced before a possible exit point.
225                int pp = low;
226                int qq = middle;
227                for( int i=low; i<high; i++ ) {
228                        if( qq >= high || (pp < middle && compare(from[pp], from[qq]) <= 0) ) {
229                                to[i] = from[pp++];
230                        }
231                        else {
232                                to[i] = from[qq++];
233                        }
234                }
235        }
236
237        /**
238         * カラム毎ソートのトップメソッドです。
239         * デフォルトで、昇順ソートを行います。
240         * 最後にソートしたカラムと同一のカラムが指定された場合、昇順と降順を
241         * 反転させて、再度ソートを行います。(シャトルソート)
242         *
243         * @param column    カラム番号
244         */
245        public void sortByColumn( final int column ) {
246                if( lastColumNo == column ) {
247                        ascending = !ascending ;
248                }
249                else {
250                        ascending = true;
251                }
252                sortByColumn( column,ascending );
253        }
254
255        /**
256         * カラム毎ソートのトップメソッドです。
257         * ascending フラグ[true:昇順/false:降順]を指定します。
258         *
259         * @og.rev 3.5.6.3 (2004/07/12) isNumberType 属性を設定する。
260         * @og.rev 4.0.0.0 (2005/01/31) getColumnClassName 廃止。DBColumから取得する。
261         * @og.rev 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
262         * @og.rev 6.4.6.0 (2016/05/27) isNumber , isDate 追加。
263         *
264         * @param column    カラム番号
265         * @param ascending  ソートの方向[true:昇順/false:降順]
266         */
267        public void sortByColumn( final int column, final boolean ascending ) {
268                this.ascending = ascending;
269                sortingColumn = column;
270                // 6.4.4.2 (2016/04/01) contains 判定を行う新しいメソッドを使用します。
271                isNumberType = getDBColumn(sortingColumn).isNumberType();                       // 6.4.6.0 (2016/05/27)
272                sort();
273                lastColumNo = column;
274        }
275
276        /**
277         * ソートの方向(昇順:true/降順:false)を取得します。
278         *
279         * @return  ソートの方向 [true:昇順/false:降順]
280         */
281        public boolean isAscending() {
282                return ascending;
283        }
284}