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