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の値 < row2の値 : 負 103 * row1の値 > 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の値 < row2の値 : 負 正 145 * row1の値 > 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}