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