import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UncheckedIOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.TreeSet; import dto.Kin02Rec; import dto.ZandatacPar; import dto.ZanendacPar; import dto.ZanmsgacPar; import util.ConvUtil; /** * ============================================================ * 【COBOL→Java】KIN03EXP 休暇日別展開処理 * ============================================================ * PROGRAM-ID. KIN03EXP → 类 Kin03ExpMain(PGMパターン:キーブレイク集計) * * 使用DB(SQLite: data/kin.db): * C1: LEAVE_RECORDS(STATUS='1') → 日別展開元 * C2: HOLIDAY_CALENDAR → 休日テーブル * 使用文件(SELECT ASSIGN → 路径常量): * W01OUTFIL(KIN02W01.DAT LEAVE-DAILY F80B) → W01OUT_FILE(COPY KIN02REC → dto.Kin02Rec) * 使用SUB(CALL → 类#execute): * SUB01DAT→Sub01DatSub / SUB02MSG→Sub02MsgSub / SUB03END→Sub03EndSub * 段落对应: * 0000MAJCOLSOR→run() 1000ITTSOR→initPara() 1100C1INITSOR→initC1() * 1100C1FETCHSOR→fetchC1() 1200HDINNSOR→loadHolidays() 2000MAJSOR→majPara() * 2100-PROCESS-EMP→majPara内 2200-EXPAND-DATE→expandDate() 2300-DATE-ADD-1→LocalDate.plusDays * 3000STPSOR→termPara() 4000MSGOUTSOR→msgOutPara() 9999ABDSOR→abendPara() * * ※COBOLはカーソル/SQLエラーを EOF 扱い(ABENDしない)。ABENDは SUB01DAT失敗・W01 OPEN失敗のみ。 * ※INTEGER-OF-DATE/MOD 7 の曜日判定は Java LocalDate.getDayOfWeek() に読替(土日=週末除外、意味等価)。 * ※SEARCH ALL(ASC KEY) は TreeSet.contains に読替(昇順ロード維持、結果等価)。 * ※2300-DATE-ADD-1 の月末/閏年計算は LocalDate.plusDays(1) に読替(意味等価)。 */ public class Kin03ExpMain { // ==================== コンスタント領域 CNSARA ==================== private static final String CST_PRGIDX = "KIN03EXP"; private static final int CST_MSGSTR = 1; private static final int CST_MSGFIN = 2; private static final int CST_MSGSUBEEK = 5; private static final int CST_MSGIINKES = 6; private static final int CST_MSGOUTKES = 7; private static final int CST_MSGKEYINF = 33; private static final int CST_KN0002 = 2; private static final int CST_ABD999 = 999; // ==================== ファイル・DB定義 ==================== /** SELECT W01OUTFIL ASSIGN TO "KIN02W01.DAT". */ private static final String W01OUT_FILE = "data/KIN02W01.DAT"; /** EXEC SQL CONNECT TO 'data/kin.db'. */ private static final String DB_URL = "jdbc:sqlite:data/kin.db"; private static final DateTimeFormatter YMD = DateTimeFormatter.ofPattern("yyyyMMdd"); // ==================== カウンタ領域 ==================== private long cunR01Inn; private long cunW01Out; // ==================== 作業領域 ==================== private String wrkR01Eof = "0"; /** 03 WRK-BFR-EMP-ID PIC X(008). キーブレイク用 */ private String wrkBfrEmpId = ""; /** 03 CUN-EMP-SUB PIC S9(009) COMP-3. 社員別小計 */ private long empSub; /** 直近SQLCODE(デバッグ用、ABEND非対象) */ private int sqlcode; // ==================== DBホスト変数(C1 FETCH 結果) ==================== private int sqlApplId; private String sqlEmpId = ""; private String sqlLeaveType = ""; private String sqlStartDate = ""; private String sqlStartTime = ""; private String sqlEndDate = ""; private String sqlEndTime = ""; // ==================== 休日テーブル ==================== /** WRK-HOLIDAY-TABLE(OCCURS 1 TO 366, ASCENDING KEY)。SEARCH ALL → TreeSet。 */ private final TreeSet hdSet = new TreeSet(); /** 03 WRK-HD-COUNT PIC 9(004). */ private int hdCount; // ==================== サブプログラム連絡領域 ==================== private final ZandatacPar d01UbsPar = new ZandatacPar(); private final ZanmsgacPar m00MhoPar = new ZanmsgacPar(); private final ZanendacPar e01AbdPar = new ZanendacPar(); // ==================== 入出力レコード ==================== /** 01 W01OUTREC.(FD W01OUTFIL)COPY KIN02REC → W01 */ private final Kin02Rec w01 = new Kin02Rec(); // ==================== ハンドル ==================== private Connection conn; private PreparedStatement psC1; private ResultSet rsC1; private PreparedStatement psC2; private ResultSet rsC2; private PrintWriter w01OutFil; // ==================== PROCEDURE DIVISION ==================== public static void main(String[] args) { new Kin03ExpMain().run(); } private void run() { try { initPara(); while (!"1".equals(wrkR01Eof)) { // PERFORM 2000MAJSOR UNTIL WRK-R01EOF='1' majPara(); } termPara(); } finally { closeAll(); } } /** * 段落 1000ITTSOR:初期処理。 * 開始/コンパイル時刻→WRK初期化→DB接続→運用日付→休日読込→W01 OPEN→C1初回FETCH。 */ private void initPara() { msgOutPara(CST_MSGSTR, null, null); String compiled = java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); msgOutPara(CST_MSGKEYINF, compiled, "COMPILED"); resetWork(); // DB接続(失敗時は conn=null → 以降のカーソルが EOF 扱い、ABENDしない) try { Class.forName("org.sqlite.JDBC"); conn = DriverManager.getConnection(DB_URL); conn.setAutoCommit(false); } catch (ClassNotFoundException | SQLException e) { conn = null; // COBOL同様、CONNECT/カーソル失敗は EOF として処理 } // 運用日付取得 CALL 'SUB01DAT' d01UbsPar.reset(); Sub01DatSub.execute(d01UbsPar); if (d01UbsPar.getD01FkiCod() != 0) { // IF D01FKICOD NOT = ZERO → メッセージ + ABEND(999) msgOutPara(CST_MSGSUBEEK, "SUB01DAT", ConvUtil.padLeftZero(d01UbsPar.getD01FkiCod(), 4)); abendPara(); } loadHolidays(); // C2: HOLIDAY_CALENDAR 全件ロード openW01(); // W01 OPEN(失敗時は ABEND) initC1(); // C1 DECLARE/OPEN/初回FETCH } private void resetWork() { cunR01Inn = 0; cunW01Out = 0; wrkBfrEmpId = ""; empSub = 0; sqlcode = 0; hdCount = 0; } /** * 段落 1200HDINNSOR:C2 HOLIDAY_CALENDAR 読込。 * PERFORM UNTIL SQLCODE NOT = 0 → WRK-HD-COUNT++、WRK-HD-DATE格納。 * エラー時は件数0(COBOLはループ終了のみ)。 */ private void loadHolidays() { if (conn == null) { return; } try (PreparedStatement ps = conn.prepareStatement( "SELECT HOLIDAY_DATE FROM HOLIDAY_CALENDAR ORDER BY HOLIDAY_DATE")) { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { hdSet.add(rs.getString("HOLIDAY_DATE")); hdCount++; } } } catch (SQLException e) { // エラー時は取得済み分まで(COBOLは PERFORM UNTIL SQLCODE NOT=0 で終了) sqlcode = e.getErrorCode(); } } /** W01 OPEN(FILE STATUS 異常時は ABEND 999)。 */ private void openW01() { try { w01OutFil = new PrintWriter( new OutputStreamWriter(new FileOutputStream(W01OUT_FILE), "UTF-8"), true); } catch (IOException e) { abendPara(); // IF WS-W01-STATUS NOT = '00' → ABEND } } /** * 段落 1100C1INITSOR:C1 DECLARE/OPEN/初回FETCH。 * SQLCODE=0 → CUN-R01INN++、 ELSE → WRK-R01EOF='1'。 */ private void initC1() { if (conn == null) { wrkR01Eof = "1"; return; } try { psC1 = conn.prepareStatement( "SELECT APPLICATION_ID, EMP_ID, LEAVE_TYPE, START_DATE, " + " START_TIME, END_DATE, END_TIME" + " FROM LEAVE_RECORDS WHERE STATUS = '1'" + " ORDER BY EMP_ID, START_DATE"); rsC1 = psC1.executeQuery(); fetchC1(); } catch (SQLException e) { sqlcode = e.getErrorCode(); wrkR01Eof = "1"; // カーソルエラーは EOF 扱い } } /** * 段落 1100C1FETCHSOR / 1100C1INITSOR 内 FETCH。 * SQLCODE=0 → ホスト変数格納 + CUN-R01INN++、 ELSE → WRK-R01EOF='1'。 */ private void fetchC1() { if (rsC1 == null) { wrkR01Eof = "1"; return; } try { if (rsC1.next()) { sqlApplId = rsC1.getInt("APPLICATION_ID"); sqlEmpId = rsC1.getString("EMP_ID"); sqlLeaveType = rsC1.getString("LEAVE_TYPE"); sqlStartDate = rsC1.getString("START_DATE"); sqlStartTime = rsC1.getString("START_TIME"); sqlEndDate = rsC1.getString("END_DATE"); sqlEndTime = rsC1.getString("END_TIME"); cunR01Inn++; } else { wrkR01Eof = "1"; } } catch (SQLException e) { sqlcode = e.getErrorCode(); wrkR01Eof = "1"; // フェッチエラーは EOF 扱い(COBOL準拠) } } /** * 段落 2000MAJSOR:主処理(社員番号キーブレイク)。 * SQL-EMP-ID→WRK-BFR-EMP-ID、1社員分処理(2100)→社員別小計出力。 */ private void majPara() { wrkBfrEmpId = sqlEmpId; // キー保存 empSub = 0; // CUN-EMP-SUB 初期化 // 2100-PROCESS-EMP:EOF またはキー変化までループ while (!"1".equals(wrkR01Eof) && sqlEmpId.equals(wrkBfrEmpId)) { expandDate(); // 2200-EXPAND-DATE fetchC1(); // 次レコード FETCH } // 社員別小計出力(キーブレイク) msgOutPara(CST_MSGKEYINF, wrkBfrEmpId, Long.toString(empSub), "EMP SUB"); } /** * 段落 2200-EXPAND-DATE:開始日〜終了日を日別展開。 * 週末(Sat/Sun)・休日を除外し、非週末かつ非休日で W01 出力。 */ private void expandDate() { LocalDate cur; LocalDate end; try { cur = LocalDate.parse(sqlStartDate, YMD); end = LocalDate.parse(sqlEndDate, YMD); } catch (DateTimeParseException e) { return; // 日付解析不可は当該申請をスキップ(COBOLは 0/SPACE で CONTINUE 相当) } while (!cur.isAfter(end)) { DayOfWeek dow = cur.getDayOfWeek(); if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) { // 非週末:休日テーブル検索 String ymd = cur.format(YMD); if (!hdSet.contains(ymd)) { w01.reset(); w01.setEmpId(wrkBfrEmpId); // MOVE WRK-BFR-EMP-ID w01.setDate(Integer.parseInt(ymd)); // MOVE WRK-DATE-ALPHA w01.setLeaveType(sqlLeaveType); // MOVE SQL-LEAVE-TYPE w01.setStartTime(Integer.parseInt(sqlStartTime)); // MOVE SQL-START-TIME w01.setEndTime(Integer.parseInt(sqlEndTime)); // MOVE SQL-END-TIME w01.setApplId(sqlApplId); // MOVE NUMVAL(SQL-APPL-ID) writeW01(); empSub++; } } cur = cur.plusDays(1); // 2300-DATE-ADD-1(LocalDate が月末/閏年を処理) } } /** * 段落 3000STPSOR:終了処理。 * CURSOR CLOSE→W01 CLOSE→件数メッセージ×3→終了メッセージ。 */ private void termPara() { // *** 件数メッセージ出力 msgOutPara(CST_MSGIINKES, "LEAVE_RECORDS", Long.toString(cunR01Inn)); msgOutPara(CST_MSGOUTKES, "KIN02W01", Long.toString(cunW01Out)); msgOutPara(CST_MSGKEYINF, "HOLIDAYS LOADED", Long.toString(hdCount)); // *** 終了メッセージ出力 msgOutPara(CST_MSGFIN, null, null); } /** * 段落 4000MSGOUTSOR:メッセージ編集出力処理。 * M00UMKDATS22-03(1:1)/-04(1:1) に '2'、-05 にプログラムIDを設定し SUB02MSG を呼ぶ。 */ private void msgOutPara(final int msgCod, final String p1, final String p2) { msgOutPara(msgCod, p1, p2, null); } private void msgOutPara(final int msgCod, final String p1, final String p2, final String p3) { m00MhoPar.reset(); m00MhoPar.setM00MsgCod(msgCod); if (p1 != null) { m00MhoPar.setM00Umkdats2201(p1); } if (p2 != null) { m00MhoPar.setM00Umkdats2202(p2); } if (p3 != null) { m00MhoPar.setM00Umkdats2203(p3); } String p3cur = m00MhoPar.getM00Umkdats2203(); String newP3 = (p3cur.length() == 0) ? ConvUtil.padRight(Integer.toString(CST_KN0002), 30) : ("2" + p3cur.substring(1)); m00MhoPar.setM00Umkdats2203(newP3); m00MhoPar.setM00Umkdats2204(ConvUtil.padRight(Integer.toString(CST_KN0002), 30)); m00MhoPar.setM00Umkdats2205(CST_PRGIDX); Sub02MsgSub.execute(m00MhoPar); } /** * 段落 9999ABDSOR:ABEND処理(ABENDコード 999)。 */ private void abendPara() { e01AbdPar.reset(); e01AbdPar.setE01AbdCod(CST_ABD999); Sub03EndSub.execute(e01AbdPar); // System.exit(999) } // ==================== ファイル操作補助 ==================== /** WRITE W01OUTREC./ADD 1 TO CUN-W01OUT. */ private void writeW01() { w01OutFil.println(w01.toRecordLine()); cunW01Out++; } /** CURSOR / W01 / DB 接続のクローズ(finally からも呼ばれ、冪等)。 */ private void closeAll() { closeQuiet(rsC1); closeQuiet(psC1); closeQuiet(rsC2); closeQuiet(psC2); closeQuiet(w01OutFil); closeQuiet(conn); rsC1 = null; psC1 = null; rsC2 = null; psC2 = null; w01OutFil = null; conn = null; } private static void closeQuiet(final Object handle) { try { if (handle instanceof ResultSet) { ((ResultSet) handle).close(); } else if (handle instanceof PreparedStatement) { ((PreparedStatement) handle).close(); } else if (handle instanceof PrintWriter) { ((PrintWriter) handle).close(); } else if (handle instanceof Connection) { ((Connection) handle).close(); } } catch (Exception e) { // CLOSE異常は無視 } } }