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