    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.fukurou.db;
    017
    018import java.io.UnsupportedEncodingException;
    019import java.util.Calendar ;                                                     // 7.0.5.0 (2019/09/16)
    020
    021import org.opengion.fukurou.util.StringUtil;            // 6.9.8.0 (2018/05/28)
    022import org.opengion.fukurou.util.HybsDateUtil;          // 7.0.5.0 (2019/09/16)
    023
    024import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 7.2.9.5 (2020/11/28)
    025
    026/**
    027 * JavaDB(derby) や､hsqldb に対する､Javaの拡張組込み関数です｡
    028 *
    029 * staticﾒｿｯﾄﾞとして､関数を定義します｡引数や返り値は､各ﾃﾞｰﾀﾍﾞｰｽの
    030 * 定義に準拠します｡
    031 *
    032 * <pre>
    033 * ① JavaDB の場合
    034 * 【概要】
    035 *     実行するﾃﾞｰﾀﾍﾞｰｽから見えるところに､ﾌｧｲﾙを配置する必要があります｡
    036 *     java8 までなら､Javaのｴｸｽﾃﾝｼｮﾝ(JAVA_HOME\)jre\lib\ext などですが､
    037 *     java9以降は､CLASSPATH に設定します｡
    038 *     openGionでは､bin/const.bat で､OG_CLASSPATH 環境変数にﾊﾟｽを通して､
    039 *     使用しています｡
    040 *     標準の Java staticﾒｿｯﾄﾞを FUNCTION 定義することも出来ます｡
    041 * 【設定】
    042 *     JavaDBに FUNCTION を定義します｡(ｻﾝﾌﾟﾙ)
    043 *      DROP FUNCTION TO_CHAR;
    044 *
    045 *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
    046 *      RETURNS VARCHAR(20)
    047 *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    048 *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    049 *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    050 *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
    051 *
    052 * ② HSQLDB の場合
    053 * 【概要】
    054 *
    055 * </pre>
    056 *
    057 * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    058 * @og.group 拡張組込み関数
    059 *
    060 * @version  1.1.0
    061 * @author       Kazuhiko Hasegawa
    062 * @since    JDK8.0,
    063 */
    064public final class Functions {
    065    private static final String ENCODE = "UTF-8";
    066
    067        /**
    068         *      ﾃﾞﾌｫﾙﾄｺﾝｽﾄﾗｸﾀｰをprivateにして､
    069         *      ｵﾌﾞｼﾞｪｸﾄの生成をさせないようにする｡
    070         *
    071         * @og.rev 6.9.7.0 (2018/05/14) 新規作成
    072         */
    073        private Functions() {}
    074
    075        /**
    076         * 数値を文字列に変換します｡
    077         *
    078         * この関数は､引数の double が､小数点を含まない場合は､
    079         * 小数点以下を出しません｡
    080         * JavaDBの場合､数字と文字列の連結が出来ないため､文字列変換関数を用意します｡
    081         *
    082         *      DROP FUNCTION TO_CHAR;
    083         *
    084         *      CREATE FUNCTION TO_CHAR ( VAL DOUBLE )
    085         *      RETURNS VARCHAR(20)
    086         *      DETERMINISTIC           -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    087         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    088         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    089         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.toChar' ;
    090         *
    091         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
    092         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    093         * @og.rev 6.9.8.0 (2018/05/28) FindBugs:浮動小数点の等価性のためのﾃｽﾄ
    094         *
    095         * @param       val     文字列に変換すべき数値
    096         * @return      変換した文字列
    097         */
    098        public static String toChar( final double val ) {
    099                //  6.9.8.0 (2018/05/28) FindBugs の警告を避ける方法が､見つかりませんでした｡
    100                return val == (int)val ? String.valueOf( (int)val ) : String.valueOf( val );
    101
    102//              final int intVal = (int)val;
    103//              return ((double)intVal) == val ? String.valueOf( intVal ) : String.valueOf( val );
    104        }
    105
    106        /**
    107         * 特殊な文字列の連結を行います｡
    108         *
    109         * これは､第1引数の数字と､第2､第3､第4の文字列をｽﾍﾟｰｽで連結した文字列を返します｡
    110         * 引数の個数が､可変に出来ないため､完全に決め打ちです｡
    111         *
    112         *      DROP FUNCTION JOIN2;
    113         *
    114         *      CREATE FUNCTION JOIN2 ( INTEGER , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
    115         *      RETURNS VARCHAR(4000)
    116         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    117         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    118         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    119         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join2' ;
    120         *
    121         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
    122         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    123         *
    124         * @param       no              第1引数の数字
    125         * @param       arg2    第2引数
    126         * @param       arg3    第3引数
    127         * @param       arg4    第4引数
    128         * @return      連結したした文字列
    129         */
    130        public static String join2( final int no , final String arg2 , final String arg3 , final String arg4 ) {
    131                return new StringBuilder( BUFFER_MIDDLE )
    132                                .append( no ).append( ' ' )
    133                                .append( arg2 == null ? "" : arg2 ).append( ' ' )
    134                                .append( arg3 == null ? "" : arg3 ).append( ' ' )
    135                                .append( arg4 == null ? "" : arg4 )
    136                                .toString() ;
    137        }
    138
    139        /**
    140         * 連結文字列を指定した､文字列の連結を行います｡
    141         *
    142         * これは､第1引数の連結文字列を使用して､第2､第3､第4の文字列を連結します｡
    143         * 引数の個数が､可変に出来ないため､第2､第3､第4の文字列が､null か､ｾﾞﾛ文字列の
    144         * 場合は､連結せずにﾊﾟｽします｡
    145         *
    146         *      DROP FUNCTION JOIN3;
    147         *
    148         *      CREATE FUNCTION JOIN3 ( VARCHAR(10) , VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
    149         *      RETURNS VARCHAR(4000)
    150         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    151         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    152         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    153         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.join3' ;
    154         *
    155         * @og.rev 8.0.2.0 (2021/11/30) 新規作成
    156         *
    157         * @param       sep             連結時の文字列
    158         * @param       arg2    第2引数
    159         * @param       arg3    第3引数
    160         * @param       arg4    第4引数
    161         * @return      連結したした文字列
    162         */
    163        public static String join3( final String sep , final String arg2 , final String arg3 , final String arg4 ) {
    164                final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) ;
    165
    166                boolean useSep = false;         // 連結文字列を使うかどうか。直前の文字列が null 等の場合は、連結しません。
    167                if( arg2 != null && !arg2.isEmpty() ) {
    168                        buf.append( arg2 );
    169                        useSep = true;
    170                }
    171
    172                if( arg3 != null && !arg3.isEmpty() ) {
    173                        if( useSep ) { buf.append( sep ); }
    174                        buf.append( arg3 );
    175                        useSep = true;
    176                }
    177
    178                if( arg4 != null && !arg4.isEmpty() ) {
    179                        if( useSep ) { buf.append( sep ); }
    180                        buf.append( arg4 );
    181                }
    182
    183                return buf.toString() ;
    184        }
    185
    186        /**
    187         * 対象の文字列の部分文字列を置換します｡
    188         *
    189         * ただし､source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､
    190         * 処理を実行せず､source をそのまま返します｡
    191         *
    192         *      DROP FUNCTION REPLACE;
    193         *
    194         *      CREATE FUNCTION REPLACE ( VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
    195         *      RETURNS VARCHAR(4000)
    196         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    197         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    198         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    199         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.replace' ;
    200         *
    201         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
    202         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    203         * @og.rev 6.9.8.0 (2018/05/28) source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､source を返す｡
    204         *
    205         * @param       source 対象の文字列
    206         * @param       target 置換したい文字列
    207         * @param       replacement 置換する文字列
    208         * @return      置換した文字列｡
    209         */
    210        public static String replace( final String source , final String target , final String replacement ) {
    211                // 6.9.8.0 (2018/05/28) source､target､replacement のどれかが､null(ｾﾞﾛ文字列)の場合は､source を返す｡
    212                if( StringUtil.isEmpty( source , target , replacement ) ) {             // 一つでも､null の場合､true
    213                        return source;
    214                }
    215                else {
    216                        return source.replace( target,replacement );
    217                }
    218
    219//              if( source != null && target != null || !target.isEmpty() && replacement != null && !replacement.isEmpty() ) {
    220//                      return source.replace( target,replacement );
    221//              }
    222//              else {
    223//                      return source;
    224//              }
    225        }
    226
    227//      /**
    228//       * この文字列内にあるすべてのoldCharをnewCharに置換した結果生成される文字列を返します｡
    229//       *
    230//       * String#replace( char , char )を､繰り返します｡
    231//       *
    232//       * @og.rev 6.7.9.0 (2017/04/28) 新規作成
    233//       *
    234//       * @param       source 対象の文字列
    235//       * @param       target 以前の文字の集合
    236//       * @param       replacement 置換する文字の集合
    237//       * @return      置換した文字列｡
    238//       */
    239//      public static String translate( final String source , final String target , final String replacement ) {
    240//              String rtn = source ;
    241//
    242//              if( source != null && target != null && replacement != null && target.length() == replacement.length() ) {
    243//                      for( int i=0; i<target.length(); i++ ) {
    244//                              rtn = rtn.replace( target.charAt(i) , replacement.charAt(i) );
    245//                      }
    246//              }
    247//
    248//              return rtn;
    249//      }
    250
    251        /**
    252         * substr関数のﾊﾞｲﾄ数版
    253         * 過去に､hsqldb 用に作成したJava関数です｡
    254         *
    255         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    256         * ※ 現在未使用
    257         *
    258         * @param       value            変換する文字列
    259         * @param       start            変換開始ｱﾄﾞﾚｽ
    260         * @param       length           変換ﾊﾞｲﾄ数
    261         * @return      変換後文字列
    262         * @throws      UnsupportedEncodingException    文字のｴﾝｺｰﾃﾞｨﾝｸﾞがｻﾎﾟｰﾄされていません｡
    263         */
    264        public static String substrb( final String value, final int start, final int length ) throws UnsupportedEncodingException {
    265                String rtn = null;
    266                final byte[] byteValue = makeByte( value );
    267                if( byteValue != null ) {
    268                        rtn = new String( byteValue,start-1,length,ENCODE );
    269                }
    270                return rtn;
    271        }
    272
    273        /**
    274         * length関数のﾊﾞｲﾄ数版
    275         * 過去に､hsqldb 用に作成したJava関数です｡
    276         *
    277         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    278         * ※ 現在未使用
    279         *
    280         * @param       value           ﾊﾞｲﾄ数をｶｳﾝﾄする文字列
    281         * @return      ﾊﾞｲﾄ数
    282         * @throws      UnsupportedEncodingException    文字のｴﾝｺｰﾃﾞｨﾝｸﾞがｻﾎﾟｰﾄされていません｡
    283         */
    284        public static int lengthb( final String value ) throws UnsupportedEncodingException {
    285                return makeByte( value ).length;
    286        }
    287
    288        /**
    289         * 指定の文字列をﾊﾞｲﾄｺｰﾄﾞに変換します｡
    290         * 引数の文字列が null の場合は､return は､byte[0] を返します｡
    291         *
    292         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にﾊﾟｯｹｰｼﾞ変更
    293         *
    294         * @param       value    変換するｽﾄﾘﾝｸﾞ値
    295         * @param       encode   変換する文字ｴﾝｺｰﾄﾞ
    296         * @return      変換後文字列
    297         */
    298        private static byte[] makeByte( final String value ) throws UnsupportedEncodingException {
    299                byte[] rtnByte = new byte[0];
    300                if( value != null ) {
    301                        rtnByte = value.getBytes( ENCODE );
    302                }
    303                return rtnByte;
    304        }
    305
    306        /**
    307         * 日時文字列(yyyyMMddHHmmss)の引数1,2に対して､差分の範囲判定を行います｡
    308         *
    309         * 範囲判定を行う引数 sec1､sec2､sec3 引数には､それぞれ(秒)ﾚﾍﾞﾙの値を指定します｡
    310         * 上記の引数の値が､0 の場合は､判定を回避します｡
    311         *
    312         * sec1引数と比べて､小さければ 0 を､大きければ 1 を返します｡
    313         * sec2引数と比べて､小さければ sec1引数の結果を､大きければ 2 を返します｡
    314         * sec3引数と比べて､小さければ sec2引数の結果を､大きければ 3 を返します｡
    315         *
    316         * 通常､sec1､sec2､sec3 と､順番に大きな値にしておきます｡
    317         * 小さいと判定された時点で､判定処理は終了します｡
    318         *
    319         * date2 - date1 &lt;= sec1 ⇒ 0
    320         *               &gt;  sec1 ⇒ 1
    321         *               &gt;  sec2 ⇒ 2
    322         *               &gt;  sec3 ⇒ 3
    323         *
    324         * date1,date2 のどちらかが､null,ｾﾞﾛ文字列の場合は､判定しません｡(return "0")
    325         *
    326         * @og.rev 7.0.5.0 (2019/09/16) 新規作成
    327         * ※ 現在未使用
    328         *
    329         * @param       date1   前比較日時文字列(yyyyMMddHHmmss)
    330         * @param       date2   後比較日時文字列(yyyyMMddHHmmss)
    331         * @param       sec1    比較する秒
    332         * @param       sec2    比較する秒
    333         * @param       sec3    比較する秒
    334         * @return      判定結果
    335         */
    336        public static String checkDelay( final String date1 , final String date2 , final double sec1 , final double sec2 , final double sec3 ) {
    337                if( StringUtil.isNull( date1,date2 ) ) { return "0"; }          // どちらかが､null,ｾﾞﾛ文字列の場合は､判定しません｡
    338
    339                final Calendar cal1 = HybsDateUtil.getCalendar( date1 );
    340                final Calendar cal2 = HybsDateUtil.getCalendar( date2 );
    341
    342                final double diff = ( cal2.getTimeInMillis() - cal1.getTimeInMillis() )/1000.0;                 // 秒に変換しておきます｡
    343
    344                String rtn = "0";
    345                if( sec1 > 0.0 ) {
    346                        if( diff <= sec1 ) { return rtn; }
    347                        else { rtn = "1"; }
    348                }
    349
    350                if( sec2 > 0.0 ) {
    351                        if( diff <= sec2 ) { return rtn; }
    352                        else { rtn = "2"; }
    353                }
    354
    355                if( sec3 > 0.0 ) {
    356                        if( diff <= sec3 ) { return rtn; }
    357                        else { rtn = "3"; }
    358                }
    359
    360                return rtn;
    361        }
    362
    363        /**
    364         * 部分文字列の出現位置を検索します｡
    365         * 第1引数の検索元の文字列と､第2引数の部分文字列を指定します｡
    366         * 戻り値は､Javaの indexOf とは異なり､+1 された値が返ります｡
    367         *
    368         *      DROP FUNCTION INSTR;
    369     *
    370         *      CREATE FUNCTION INSTR ( VARCHAR(1000) , VARCHAR(100) )
    371         *      RETURNS INTEGER
    372         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    373         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    374         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    375         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.instr' ;
    376     *
    377         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
    378         *
    379         * @param       value    検索元の文字列
    380         * @param       sub              部分文字列
    381         * @return      文字列が見つかった位置(最初が1 ､見つからなければ 0 )
    382         */
    383        public static int instr( final String value,final String sub ) {
    384                if( value == null || value.isEmpty() ) {
    385                        return 0 ;
    386                }
    387                else {
    388                        return value.indexOf( sub ) + 1 ;
    389                }
    390        }
    391
    392        /**
    393         * 文字列を連結します｡
    394         *
    395         * 文字列が､NULL の場合は､ｾﾞﾛ文字列として扱います｡
    396         * 引数がどちらも NULL の場合は､ｾﾞﾛ文字列が戻されます｡
    397         * これは､ORACLEとの互換性を考慮した関数です｡
    398         * ORACLE は､NULLとｾﾞﾛ文字列を同等にNULLとして扱うため､
    399         * COALESCE( VAL1,'' ) で､VAL1がNULLか､ｾﾞﾛ文字列の場合は､NULLが返されます｡
    400         * NULLを含むWHERE条件で､WHERE COALESCE( VAL1,'' ) = COALESCE( VAL2,'' )
    401         * としても､一致しないため､WHERE COALESCE( VAL1,'x' ) = COALESCE( VAL2,'x' )
    402         * とする必要がありますが､VAL1が､実際の 'x' で､VAL2がNULLの場合もﾏｯﾁします｡
    403         * 一方､Derbyでは､NULLとｾﾞﾛ文字列を区別するため､COALESCE( VAL1,'x' ) の
    404         * VAL1の値が､NULLとｾﾞﾛ文字列で､結果が変わってしまいます｡
    405         * また､|| による文字列連結も､ORACLEとDerbyでは結果が変わるため､
    406         * CONCAT関数を作成して､ORACLEと同じSQL文での比較ができるようにしておきます｡
    407         *
    408         *      DROP FUNCTION CONCAT;
    409     *
    410         *      CREATE FUNCTION CONCAT ( VARCHAR(1000) , VARCHAR(1000) )
    411         *      RETURNS VARCHAR(2000)
    412         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    413         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    414         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    415         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.concat' ;
    416     *
    417         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
    418         *
    419         * @param       val1    文字列1
    420         * @param       val2    文字列2
    421         * @return      文字列1と文字列2を連結した文字列
    422         */
    423        public static String concat( final String val1,final String val2 ) {
    424                return new StringBuilder( BUFFER_MIDDLE )
    425                                .append( val1 == null ? "" : val1 )
    426                                .append( val2 == null ? "" : val2 )
    427                                .toString() ;
    428        }
    429
    430        /**
    431         * NULL文字列の判定変換を行います｡
    432         *
    433         * 文字列が､NULL(または空文字列)は､文字列2を､NULLでない場合は､文字列1を返します｡
    434         * Derbyでは､NULLと空文字列が区別されますが､ORACLE等では､区別されないため､
    435         * この関数でも区別しません｡
    436         * 空文字列の場合は､NULLと同じになります｡
    437         *
    438         *      DROP FUNCTION NVL2;
    439     *
    440         *      CREATE FUNCTION NVL2 ( VARCHAR(1000) , VARCHAR(1000) , VARCHAR(1000) )
    441         *      RETURNS VARCHAR(1000)
    442         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    443         *      PARAMETER STYLE JAVA    -- 戻り値のﾀｲﾌﾟ
    444         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    445         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.nvl2' ;
    446     *
    447         * @og.rev 7.3.1.3 (2021/03/09) 新規追加
    448         *
    449         * @param       inval   判定する文字列
    450         * @param       val1    invalがNULL(または空文字列)でない場合に返す文字列1
    451         * @param       val2    invalがNULL(または空文字列)の場合に返す文字列2
    452         * @return      文字列がNULLの場合は､val2 を､そうでない場合は､val1 を返す｡
    453         */
    454        public static String nvl2( final String inval,final String val1,final String val2 ) {
    455                return inval == null || inval.isEmpty() ? val2 : val1 ;
    456        }
    457}