    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         * これは、第１引数の数字と、第２、第３、第４の文字列をスペースで連結した文字列を返します。
    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              第１引数の数字
    125         * @param       arg2     第２引数
    126         * @param       arg3    第３引数
    127         * @param       arg4    第４引数
    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 ).append( ' ' )
    136                                .toString() ;
    137        }
    138
    139        /**
    140         * 対象の文字列の部分文字列を置換します。
    141         *
    142         * ただし、source、target、replacement のどれかが、null(ゼロ文字列)の場合は、
    143         * 処理を実行せず、source をそのまま返します。
    144         *
    145         *      DROP FUNCTION REPLACE;
    146         *
    147         *      CREATE FUNCTION REPLACE ( VARCHAR(2000) , VARCHAR(2000) , VARCHAR(2000) )
    148         *      RETURNS VARCHAR(4000)
    149         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    150         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
    151         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    152         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.replace' ;
    153         *
    154         * @og.rev 6.7.3.0 (2017/01/27) 新規作成
    155         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.javadb → org.opengion.fukurou.db にパッケージ変更
    156         * @og.rev 6.9.8.0 (2018/05/28) source、target、replacement のどれかが、null(ゼロ文字列)の場合は、source を返す。
    157         *
    158         * @param       source 対象の文字列
    159         * @param       target 置換したい文字列
    160         * @param       replacement 置換する文字列
    161         * @return      置換した文字列。
    162         */
    163        public static String replace( final String source , final String target , final String replacement ) {
    164                // 6.9.8.0 (2018/05/28) source、target、replacement のどれかが、null(ゼロ文字列)の場合は、source を返す。
    165                if( StringUtil.isEmpty( source , target , replacement ) ) {             // 一つでも、null の場合、true
    166                        return source;
    167                }
    168                else {
    169                        return source.replace( target,replacement );
    170                }
    171
    172//              if( source != null && target != null || !target.isEmpty() && replacement != null && !replacement.isEmpty() ) {
    173//                      return source.replace( target,replacement );
    174//              }
    175//              else {
    176//                      return source;
    177//              }
    178        }
    179
    180//      /**
    181//       * この文字列内にあるすべてのoldCharをnewCharに置換した結果生成される文字列を返します。
    182//       *
    183//       * String#replace( char , char )を、繰り返します。
    184//       *
    185//       * @og.rev 6.7.9.0 (2017/04/28) 新規作成
    186//       *
    187//       * @param       source 対象の文字列
    188//       * @param       target 以前の文字の集合
    189//       * @param       replacement 置換する文字の集合
    190//       * @return      置換した文字列。
    191//       */
    192//      public static String translate( final String source , final String target , final String replacement ) {
    193//              String rtn = source ;
    194//
    195//              if( source != null && target != null && replacement != null && target.length() == replacement.length() ) {
    196//                      for( int i=0; i<target.length(); i++ ) {
    197//                              rtn = rtn.replace( target.charAt(i) , replacement.charAt(i) );
    198//                      }
    199//              }
    200//
    201//              return rtn;
    202//      }
    203
    204        /**
    205         * substr関数のバイト数版
    206         * 過去に、hsqldb 用に作成したJava関数です。
    207         *
    208         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
    209         * ※ 現在未使用
    210         *
    211         * @param       value            変換する文字列
    212         * @param       start            変換開始アドレス
    213         * @param       length           変換バイト数
    214         * @return      変換後文字列
    215         * @throws      UnsupportedEncodingException    文字のエンコーディングがサポートされていません。
    216         */
    217        public static String substrb( final String value, final int start, final int length ) throws UnsupportedEncodingException {
    218                String rtn = null;
    219                final byte[] byteValue = makeByte( value );
    220                if( byteValue != null ) {
    221                        rtn = new String( byteValue,start-1,length,ENCODE );
    222                }
    223                return rtn;
    224        }
    225
    226        /**
    227         * length関数のバイト数版
    228         * 過去に、hsqldb 用に作成したJava関数です。
    229         *
    230         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
    231         * ※ 現在未使用
    232         *
    233         * @param       value           バイト数をカウントする文字列
    234         * @return      バイト数
    235         * @throws      UnsupportedEncodingException    文字のエンコーディングがサポートされていません。
    236         */
    237        public static int lengthb( final String value ) throws UnsupportedEncodingException {
    238                return makeByte( value ).length;
    239        }
    240
    241        /**
    242         * 指定の文字列をバイトコードに変換します。
    243         * 引数の文字列が null の場合は、return は、byte[0] を返します。
    244         *
    245         * @og.rev 6.8.5.1 (2018/01/15) org.opengion.hsqldb → org.opengion.fukurou.db にパッケージ変更
    246         *
    247         * @param       value    変換するストリング値
    248         * @param       encode   変換する文字エンコード
    249         * @return      変換後文字列
    250         */
    251        private static byte[] makeByte( final String value ) throws UnsupportedEncodingException {
    252                byte[] rtnByte = new byte[0];
    253                if( value != null ) {
    254                        rtnByte = value.getBytes( ENCODE );
    255                }
    256                return rtnByte;
    257        }
    258
    259        /**
    260         * 日時文字列(yyyyMMddHHmmss)の引数1,2に対して、差分の範囲判定を行います。
    261         *
    262         * 範囲判定を行う引数 sec1、sec2、sec3 引数には、それぞれ（秒）レベルの値を指定します。
    263         * 上記の引数の値が、0 の場合は、判定を回避します。
    264         *
    265         * sec1引数と比べて、小さければ 0 を、大きければ 1 を返します。
    266         * sec2引数と比べて、小さければ sec1引数の結果を、大きければ 2 を返します。
    267         * sec3引数と比べて、小さければ sec2引数の結果を、大きければ 3 を返します。
    268         *
    269         * 通常、sec1、sec2、sec3 と、順番に大きな値にしておきます。
    270         * 小さいと判定された時点で、判定処理は終了します。
    271         *
    272         * date2 - date1 ＜= sec1 ⇒ 0
    273         *               ＞  sec1 ⇒ 1
    274         *               ＞  sec2 ⇒ 2
    275         *               ＞  sec3 ⇒ 3
    276         *
    277         * date1,date2 のどちらかが、null,ゼロ文字列の場合は、判定しません。(return "0")
    278         *
    279         * @og.rev 7.0.5.0 (2019/09/16) 新規作成
    280         * ※ 現在未使用
    281         *
    282         * @param       date1   前比較日時文字列(yyyyMMddHHmmss)
    283         * @param       date2   後比較日時文字列(yyyyMMddHHmmss)
    284         * @param       sec1    比較する秒
    285         * @param       sec2    比較する秒
    286         * @param       sec3    比較する秒
    287         * @return      判定結果
    288         */
    289        public static String checkDelay( final String date1 , final String date2 , final double sec1 , final double sec2 , final double sec3 ) {
    290                if( StringUtil.isNull( date1,date2 ) ) { return "0"; }          // どちらかが、null,ゼロ文字列の場合は、判定しません。
    291
    292                final Calendar cal1 = HybsDateUtil.getCalendar( date1 );
    293                final Calendar cal2 = HybsDateUtil.getCalendar( date2 );
    294
    295                final double diff = ( cal2.getTimeInMillis() - cal1.getTimeInMillis() )/1000.0;                 // 秒に変換しておきます。
    296
    297                String rtn = "0";
    298                if( sec1 > 0.0 ) {
    299                        if( diff <= sec1 ) { return rtn; }
    300                        else { rtn = "1"; }
    301                }
    302
    303                if( sec2 > 0.0 ) {
    304                        if( diff <= sec2 ) { return rtn; }
    305                        else { rtn = "2"; }
    306                }
    307
    308                if( sec3 > 0.0 ) {
    309                        if( diff <= sec3 ) { return rtn; }
    310                        else { rtn = "3"; }
    311                }
    312
    313                return rtn;
    314        }
    315
    316        /**
    317         * 部分文字列の出現位置を検索します。
    318         * 第１引数の検索元の文字列と、第２引数の部分文字列を指定します。
    319         * 戻り値は、Javaの indexOf とは異なり、＋１ された値が返ります。
    320         *
    321         *      DROP FUNCTION INSTR;
    322     *
    323         *      CREATE FUNCTION INSTR ( VARCHAR(1000) , VARCHAR(100) )
    324         *      RETURNS INTEGER
    325         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    326         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
    327         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    328         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.instr' ;
    329     *
    330         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
    331         *
    332         * @param       value    検索元の文字列
    333         * @param       sub              部分文字列
    334         * @return      文字列が見つかった位置(最初が1 、見つからなければ 0 )
    335         */
    336        public static int instr( final String value,final String sub ) {
    337                if( value == null || value.isEmpty() ) {
    338                        return 0 ;
    339                }
    340                else {
    341                        return value.indexOf( sub ) + 1 ;
    342                }
    343        }
    344
    345        /**
    346         * 文字列を連結します。
    347         *
    348         * 文字列が、NULL の場合は、ゼロ文字列として扱います。
    349         * 引数がどちらも NULL の場合は、ゼロ文字列が戻されます。
    350         * これは、ORACLEとの互換性を考慮した関数です。
    351         * ORACLE は、NULLとゼロ文字列を同等にNULLとして扱うため、
    352         * COALESCE( VAL1,'' ) で、VAL1がNULLか、ゼロ文字列の場合は、NULLが返されます。
    353         * NULLを含むWHERE条件で、WHERE COALESCE( VAL1,'' ) = COALESCE( VAL2,'' )
    354         * としても、一致しないため、WHERE COALESCE( VAL1,'x' ) = COALESCE( VAL2,'x' )
    355         * とする必要がありますが、VAL1が、実際の 'x' で、VAL2がNULLの場合もマッチします。
    356         * 一方、Derbyでは、NULLとゼロ文字列を区別するため、COALESCE( VAL1,'x' ) の
    357         * VAL1の値が、NULLとゼロ文字列で、結果が変わってしまいます。
    358         * また、|| による文字列連結も、ORACLEとDerbyでは結果が変わるため、
    359         * CONCAT関数を作成して、ORACLEと同じSQL文での比較ができるようにしておきます。
    360         *
    361         *      DROP FUNCTION CONCAT;
    362     *
    363         *      CREATE FUNCTION CONCAT ( VARCHAR(1000) , VARCHAR(1000) )
    364         *      RETURNS VARCHAR(2000)
    365         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    366         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
    367         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    368         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.concat' ;
    369     *
    370         * @og.rev 7.3.0.0 (2021/01/06) 新規追加
    371         *
    372         * @param       val1    文字列1
    373         * @param       val2    文字列2
    374         * @return      文字列1と文字列2を連結した文字列
    375         */
    376        public static String concat( final String val1,final String val2 ) {
    377                return new StringBuilder( BUFFER_MIDDLE )
    378                                .append( val1 == null ? "" : val1 )
    379                                .append( val2 == null ? "" : val2 )
    380                                .toString() ;
    381        }
    382
    383        /**
    384         * NULL文字列の判定変換を行います。
    385         *
    386         * 文字列が、NULL(または空文字列)は、文字列2を、NULLでない場合は、文字列1を返します。
    387         * Derbyでは、NULLと空文字列が区別されますが、ORACLE等では、区別されないため、
    388         * この関数でも区別しません。
    389         * 空文字列の場合は、NULLと同じになります。
    390         *
    391         *      DROP FUNCTION NVL2;
    392     *
    393         *      CREATE FUNCTION NVL2 ( VARCHAR(1000) , VARCHAR(1000) , VARCHAR(1000) )
    394         *      RETURNS VARCHAR(1000)
    395         *      DETERMINISTIC                   -- 引数が同じなら常に同じ値を返すことを示す.(省略時はnot deterministic)
    396         *      PARAMETER STYLE JAVA    -- 戻り値のタイプ
    397         *      NO SQL LANGUAGE JAVA    -- 関数の中でSQLは実行しないことを示す
    398         *      EXTERNAL NAME 'org.opengion.fukurou.db.Functions.nvl2' ;
    399     *
    400         * @og.rev 7.3.1.3 (2021/03/09) 新規追加
    401         *
    402         * @param       inval   判定する文字列
    403         * @param       val1    invalがNULL(または空文字列)でない場合に返す文字列1
    404         * @param       val2    invalがNULL(または空文字列)の場合に返す文字列2
    405         * @return      文字列がNULLの場合は、val2 を、そうでない場合は、val1 を返す。
    406         */
    407        public static String nvl2( final String inval,final String val1,final String val2 ) {
    408                return inval == null || inval.isEmpty() ? val2 : val1 ;
    409        }
    410}