From bc47bc0e7cb14dfbf92df069a2098db1ceb70486 Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Mon, 2 Mar 2026 01:59:16 +0000 Subject: [PATCH 1/6] feat: implement COB_FILE_SEQ_BUFFER_SIZE --- doc/environment_variables.md | 15 +- doc/environment_variables_JP.md | 15 +- .../libcobj/common/CobolUtil.java | 19 +- .../libcobj/file/CobolFile.java | 5 +- .../opensourcecobol/libcobj/file/FileIO.java | 328 ++++++++++++------ 5 files changed, 268 insertions(+), 114 deletions(-) diff --git a/doc/environment_variables.md b/doc/environment_variables.md index a19c7fae6..2da990b6b 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -446,13 +446,22 @@ $ OC_EXTEND_CREATES=yes java ocextcreates FILE STATUS: 00 ``` -#### COB_FILE_SEQ_WRITE_BUFFER_SIZE +#### COB_FILE_SEQ_BUFFER_SIZE -Specifies the write buffer size for sequential files. +Specifies the buffer size for sequential file I/O (both read and write). + +- **Value**: Integer >= 0 (default: 10). The value represents a multiplier for the record size. +- **Example**: `COB_FILE_SEQ_BUFFER_SIZE=100` +- **Purpose**: Adjusts read and write performance for SEQUENTIAL and LINE SEQUENTIAL files. The actual buffer size is calculated as `COB_FILE_SEQ_BUFFER_SIZE * record_max` bytes. Setting to 0 disables buffering. + +#### COB_FILE_SEQ_WRITE_BUFFER_SIZE (Deprecated) + +Deprecated. Use `COB_FILE_SEQ_BUFFER_SIZE` instead. + +This environment variable is retained for backward compatibility. When `COB_FILE_SEQ_BUFFER_SIZE` is not set, this variable is used as a fallback with the same effect. - **Value**: Integer >= 0 (default: 10) - **Example**: `COB_FILE_SEQ_WRITE_BUFFER_SIZE=100` -- **Purpose**: Adjusts write performance. #### COB_IO_ASSUME_REWRITE diff --git a/doc/environment_variables_JP.md b/doc/environment_variables_JP.md index aed991ef8..129472677 100644 --- a/doc/environment_variables_JP.md +++ b/doc/environment_variables_JP.md @@ -446,13 +446,22 @@ $ OC_EXTEND_CREATES=yes java ocextcreates FILE STATUS: 00 ``` -#### COB_FILE_SEQ_WRITE_BUFFER_SIZE +#### COB_FILE_SEQ_BUFFER_SIZE -順編成ファイルの書き込みバッファサイズを指定します。 +順編成ファイルの読み書きバッファサイズを指定します。 + +- **値**: 0以上の整数(デフォルト: 10)。レコードサイズに対する倍率を指定します。 +- **例**: `COB_FILE_SEQ_BUFFER_SIZE=100` +- **用途**: SEQUENTIALおよびLINE SEQUENTIALファイルの読み書きパフォーマンスを調整できます。実際のバッファサイズは `COB_FILE_SEQ_BUFFER_SIZE * record_max` バイトとなります。0を指定するとバッファリングが無効になります。 + +#### COB_FILE_SEQ_WRITE_BUFFER_SIZE(非推奨) + +非推奨です。代わりに `COB_FILE_SEQ_BUFFER_SIZE` を使用してください。 + +後方互換性のために残されています。`COB_FILE_SEQ_BUFFER_SIZE` が設定されていない場合にフォールバックとして同じ効果で使用されます。 - **値**: 0以上の整数(デフォルト: 10) - **例**: `COB_FILE_SEQ_WRITE_BUFFER_SIZE=100` -- **用途**: 書き込みパフォーマンスを調整できます。 #### COB_IO_ASSUME_REWRITE diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java index 0e0dd923a..1ee926f63 100755 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java @@ -69,8 +69,8 @@ public class CobolUtil { /** TDOD: 準備中 */ public static Calendar cal; - /** TDOD: 準備中 */ - public static int fileSeqWriteBufferSize = 10; + /** 順編成ファイルの読み書きバッファサイズ(レコード数単位) */ + public static int fileSeqBufferSize = 10; /** DISPLAY/ACCEPT文によるデータ出力時のエンコーディング */ public static CobolEncoding terminalEncoding = CobolEncoding.SHIFT_JIS; @@ -345,11 +345,18 @@ public static void cob_init(String[] argv, boolean cobInitialized) { CobolUtil.nibbleCForUnsigned = true; } - s = System.getenv("COB_FILE_SEQ_WRITE_BUFFER_SIZE"); + s = CobolUtil.getEnv("COB_FILE_SEQ_BUFFER_SIZE"); + if (s == null) { + s = CobolUtil.getEnv("COB_FILE_SEQ_WRITE_BUFFER_SIZE"); + } if (s != null) { - int size = Integer.parseInt(s); - if (size >= 0) { - CobolUtil.fileSeqWriteBufferSize = size; + try { + int size = Integer.parseInt(s); + if (size >= 0) { + CobolUtil.fileSeqBufferSize = size; + } + } catch (NumberFormatException e) { + // ignore invalid value } } diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java index f3747fcfe..de53105c4 100755 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/CobolFile.java @@ -1243,9 +1243,8 @@ public int open_(String filename, int mode, int sharing) throws IOException { } if ((this.organization == COB_ORG_SEQUENTIAL || this.organization == COB_ORG_LINE_SEQUENTIAL) - && (mode == COB_OPEN_OUTPUT || mode == COB_OPEN_EXTEND) - && CobolUtil.fileSeqWriteBufferSize > 0) { - this.file.prepareWriteBuffer(CobolUtil.fileSeqWriteBufferSize * this.record_max); + && CobolUtil.fileSeqBufferSize > 0) { + this.file.prepareBuffer(CobolUtil.fileSeqBufferSize * this.record_max); } return 0; } diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java index b3634358e..23e2aa9ff 100644 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java @@ -38,24 +38,20 @@ class FileIO { private boolean useStdIn = true; private boolean atEnd = false; - private static final boolean USE_READ_BUFFER = false; - private static final int READ_BUFFER_SIZE = 1024; - private int readBufferIndex; - private byte[] readBuffer; - private int readBufferEndIndex; + private static final int BUFFER_MODE_NONE = 0; + private static final int BUFFER_MODE_READ = 1; + private static final int BUFFER_MODE_WRITE = 2; - private int writeBufferSize = 0; - private int writeBufferEndIndex = 0; - private byte[] writeBuffer; + private int bufferMode = BUFFER_MODE_NONE; + private int bufferSize = 0; + private byte[] buffer; + private int bufferDataStart = 0; + private int bufferDataEnd = 0; /** TODO: 準備中 */ FileIO() { this.useStdOut = true; this.useStdIn = true; - - this.readBufferIndex = READ_BUFFER_SIZE; - this.readBuffer = new byte[READ_BUFFER_SIZE]; - this.readBufferEndIndex = READ_BUFFER_SIZE; } /** @@ -116,19 +112,78 @@ void setIn(InputStream in) { * * @param bufferSize TODO: 準備中 */ - void prepareWriteBuffer(int bufferSize) { + void prepareBuffer(int bufferSize) { if (bufferSize > 0) { - this.writeBufferSize = bufferSize; - this.writeBufferEndIndex = 0; - if (this.writeBuffer == null || this.writeBuffer.length < bufferSize) { - this.writeBuffer = new byte[bufferSize]; + this.bufferSize = bufferSize; + this.bufferMode = BUFFER_MODE_NONE; + this.bufferDataStart = 0; + this.bufferDataEnd = 0; + if (this.buffer == null || this.buffer.length < bufferSize) { + this.buffer = new byte[bufferSize]; } } } - private void destroyWriteBuffer() { - this.writeBufferSize = 0; - this.writeBufferEndIndex = 0; + private void destroyBuffer() { + this.bufferSize = 0; + this.bufferMode = BUFFER_MODE_NONE; + this.bufferDataStart = 0; + this.bufferDataEnd = 0; + } + + private boolean flushWriteBuffer() { + if (bufferMode == BUFFER_MODE_WRITE && bufferDataEnd > 0 && bufferSize > 0) { + ByteBuffer bb = ByteBuffer.wrap(buffer, 0, bufferDataEnd); + if (!writeByteBuffer(bb)) { + return false; + } + bufferDataEnd = 0; + } + return true; + } + + private int fillReadBuffer() throws IOException { + bufferDataStart = 0; + bufferDataEnd = 0; + ByteBuffer bb = ByteBuffer.wrap(buffer, 0, bufferSize); + int readBytes; + try { + readBytes = this.fc.read(bb); + } catch (NonReadableChannelException e) { + return -1; + } + if (readBytes <= 0) { + return -1; + } + bufferDataEnd = readBytes; + return readBytes; + } + + private void transitionToRead() throws IOException { + if (bufferMode == BUFFER_MODE_WRITE) { + if (!flushWriteBuffer()) { + throw new IOException("Failed to flush write buffer"); + } + } + if (bufferMode != BUFFER_MODE_READ) { + bufferMode = BUFFER_MODE_READ; + bufferDataStart = 0; + bufferDataEnd = 0; + } + } + + private void transitionToWrite() throws IOException { + if (bufferMode == BUFFER_MODE_READ) { + int unread = bufferDataEnd - bufferDataStart; + if (unread > 0) { + this.fc.position(this.fc.position() - unread); + } + } + if (bufferMode != BUFFER_MODE_WRITE) { + bufferMode = BUFFER_MODE_WRITE; + bufferDataStart = 0; + bufferDataEnd = 0; + } } /** @@ -144,6 +199,35 @@ int read(byte[] bytes, int size) { System.err.println("read stdin not implmented"); return 0; } else { + if (bufferSize > 0) { + try { + transitionToRead(); + int offset = 0; + int remaining = size; + while (remaining > 0) { + int available = bufferDataEnd - bufferDataStart; + if (available <= 0) { + if (fillReadBuffer() < 0) { + this.atEnd = true; + if (offset == 0) { + return 0; + } + return 1; + } + available = bufferDataEnd - bufferDataStart; + } + int toCopy = Math.min(remaining, available); + System.arraycopy(buffer, bufferDataStart, bytes, offset, toCopy); + bufferDataStart += toCopy; + offset += toCopy; + remaining -= toCopy; + } + } catch (IOException e) { + return 0; + } + return 1; + } + int readSize; ByteBuffer data = ByteBuffer.wrap(bytes); try { @@ -175,6 +259,29 @@ int read(CobolDataStorage storage, int size) throws IOException { if (this.fc == null) { throw new IOException(); } + if (bufferSize > 0) { + transitionToRead(); + int offset = 0; + int remaining = size; + while (remaining > 0) { + int available = bufferDataEnd - bufferDataStart; + if (available <= 0) { + if (fillReadBuffer() < 0) { + return offset; + } + available = bufferDataEnd - bufferDataStart; + } + int toCopy = Math.min(remaining, available); + for (int i = 0; i < toCopy; ++i) { + storage.setByte(offset + i, buffer[bufferDataStart + i]); + } + bufferDataStart += toCopy; + offset += toCopy; + remaining -= toCopy; + } + return size; + } + int i = 0; try { for (i = 0; i < size; ++i) { @@ -201,17 +308,6 @@ private boolean writeByteBuffer(ByteBuffer bb) { return true; } - private boolean outputWriteBuffer() { - if (writeBufferEndIndex > 0 && writeBufferSize > 0) { - ByteBuffer bb = ByteBuffer.wrap(writeBuffer, 0, writeBufferEndIndex); - if (!writeByteBuffer(bb)) { - return false; - } - writeBufferEndIndex = 0; - } - return true; - } - /** * TODO: 準備中 * @@ -223,18 +319,27 @@ boolean write(byte[] bytes, int size) { if (this.fc == null) { return false; } - if (writeBufferSize > 0 && size <= writeBufferSize - writeBufferEndIndex) { - System.arraycopy(bytes, 0, writeBuffer, writeBufferEndIndex, size); - writeBufferEndIndex += size; - return true; - } - if (!outputWriteBuffer()) { - return false; - } - if (writeBufferSize > 0 && size <= writeBufferSize - writeBufferEndIndex) { - System.arraycopy(bytes, 0, writeBuffer, writeBufferEndIndex, size); - writeBufferEndIndex += size; - return true; + if (bufferSize > 0) { + try { + transitionToWrite(); + } catch (IOException e) { + return false; + } + if (size <= bufferSize - bufferDataEnd) { + System.arraycopy(bytes, 0, buffer, bufferDataEnd, size); + bufferDataEnd += size; + return true; + } + if (!flushWriteBuffer()) { + return false; + } + if (size <= bufferSize - bufferDataEnd) { + System.arraycopy(bytes, 0, buffer, bufferDataEnd, size); + bufferDataEnd += size; + return true; + } + ByteBuffer bb = ByteBuffer.wrap(bytes, 0, size); + return writeByteBuffer(bb); } ByteBuffer bb = ByteBuffer.wrap(bytes, 0, size); return writeByteBuffer(bb); @@ -251,22 +356,31 @@ boolean write(CobolDataStorage storage, int size) { if (this.fc == null) { return false; } - if (writeBufferSize > 0 && size <= writeBufferSize - writeBufferEndIndex) { - for (int i = 0; i < size; ++i) { - writeBuffer[writeBufferEndIndex + i] = storage.getByte(i); + if (bufferSize > 0) { + try { + transitionToWrite(); + } catch (IOException e) { + return false; } - writeBufferEndIndex += size; - return true; - } - if (!outputWriteBuffer()) { - return false; - } - if (writeBufferSize > 0 && size <= writeBufferSize - writeBufferEndIndex) { - for (int i = 0; i < size; ++i) { - writeBuffer[writeBufferEndIndex + i] = storage.getByte(i); + if (size <= bufferSize - bufferDataEnd) { + for (int i = 0; i < size; ++i) { + buffer[bufferDataEnd + i] = storage.getByte(i); + } + bufferDataEnd += size; + return true; } - writeBufferEndIndex += size; - return true; + if (!flushWriteBuffer()) { + return false; + } + if (size <= bufferSize - bufferDataEnd) { + for (int i = 0; i < size; ++i) { + buffer[bufferDataEnd + i] = storage.getByte(i); + } + bufferDataEnd += size; + return true; + } + ByteBuffer bb = storage.getByteBuffer(size); + return writeByteBuffer(bb); } ByteBuffer bb = storage.getByteBuffer(size); return writeByteBuffer(bb); @@ -282,16 +396,23 @@ byte putc(byte val) { if (this.fc == null) { return 0; } - if (writeBufferSize > 0 && 1 <= writeBufferSize - writeBufferEndIndex) { - writeBuffer[writeBufferEndIndex++] = val; - return val; - } - if (!outputWriteBuffer()) { - return -1; - } - if (writeBufferSize > 0 && 1 <= writeBufferSize - writeBufferEndIndex) { - writeBuffer[writeBufferEndIndex++] = val; - return val; + if (bufferSize > 0) { + try { + transitionToWrite(); + } catch (IOException e) { + return -1; + } + if (1 <= bufferSize - bufferDataEnd) { + buffer[bufferDataEnd++] = val; + return val; + } + if (!flushWriteBuffer()) { + return -1; + } + if (1 <= bufferSize - bufferDataEnd) { + buffer[bufferDataEnd++] = val; + return val; + } } byte[] arr = {val}; if (writeByteBuffer(ByteBuffer.wrap(arr))) { @@ -310,40 +431,29 @@ int getc() { if (this.fc == null) { return 0; } - if (USE_READ_BUFFER) { - if (readBufferIndex >= READ_BUFFER_SIZE) { - this.readBufferIndex = 0; - try { - ByteBuffer bb = ByteBuffer.wrap(readBuffer); - int readBytes = this.fc.read(bb); - if (readBytes <= 0) { - this.readBufferEndIndex = -1; - } else { - this.readBufferEndIndex = readBytes; + if (bufferSize > 0) { + try { + transitionToRead(); + if (bufferDataStart >= bufferDataEnd) { + if (fillReadBuffer() < 0) { + return -1; } - } catch (IOException | NonReadableChannelException e) { - return -1; } - } - - if (this.readBufferIndex >= this.readBufferEndIndex) { + return buffer[bufferDataStart++] & 0xFF; + } catch (IOException e) { return -1; } - - return readBuffer[readBufferIndex++]; - - } else { - try { - byte[] b = new byte[1]; - ByteBuffer bb = ByteBuffer.wrap(b); - if (this.fc.read(bb) == 1) { - return b[0]; - } else { - return -1; - } - } catch (IOException | NonReadableChannelException e) { + } + try { + byte[] b = new byte[1]; + ByteBuffer bb = ByteBuffer.wrap(b); + if (this.fc.read(bb) == 1) { + return b[0] & 0xFF; + } else { return -1; } + } catch (IOException | NonReadableChannelException e) { + return -1; } } @@ -351,8 +461,10 @@ int getc() { void close() { if (!useStdOut && !useStdIn && this.fc != null) { try { - outputWriteBuffer(); - destroyWriteBuffer(); + if (bufferMode == BUFFER_MODE_WRITE) { + flushWriteBuffer(); + } + destroyBuffer(); this.fc.close(); } catch (IOException e) { return; @@ -364,7 +476,9 @@ void close() { void flush() { if (!useStdOut) { try { - outputWriteBuffer(); + if (bufferMode == BUFFER_MODE_WRITE) { + flushWriteBuffer(); + } this.fc.force(false); } catch (IOException e) { return; @@ -388,16 +502,26 @@ void flush() { boolean seek(long offset, int origin) { if (!useStdOut && !useStdIn) { try { + if (bufferMode == BUFFER_MODE_WRITE) { + flushWriteBuffer(); + } switch (origin) { case FileIO.SEEK_SET: this.fc.position(offset); break; case FileIO.SEEK_CUR: - this.fc.position(this.fc.position() + offset); + long adjustment = 0; + if (bufferMode == BUFFER_MODE_READ) { + adjustment = bufferDataEnd - bufferDataStart; + } + this.fc.position(this.fc.position() + offset - adjustment); break; default: return false; } + bufferMode = BUFFER_MODE_NONE; + bufferDataStart = 0; + bufferDataEnd = 0; } catch (IOException e) { return false; } @@ -412,6 +536,12 @@ void seekInit() {} void rewind() { if (!useStdOut && !useStdIn) { try { + if (bufferMode == BUFFER_MODE_WRITE) { + flushWriteBuffer(); + } + bufferMode = BUFFER_MODE_NONE; + bufferDataStart = 0; + bufferDataEnd = 0; this.fc.position(0L); } catch (IOException e) { return; From f12d3e3dc8cf16222df0f1e249a45a907aa01c2a Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Mon, 2 Mar 2026 02:12:11 +0000 Subject: [PATCH 2/6] refactor: resolve a PMD warning --- .../jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java index 1ee926f63..a244ba361 100755 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/common/CobolUtil.java @@ -355,7 +355,7 @@ public static void cob_init(String[] argv, boolean cobInitialized) { if (size >= 0) { CobolUtil.fileSeqBufferSize = size; } - } catch (NumberFormatException e) { + } catch (NumberFormatException ignored) { // ignore invalid value } } From 81966b5ea300bc49444242561b1a1452ea16c2fd Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Mon, 2 Mar 2026 02:22:20 +0000 Subject: [PATCH 3/6] test: add tests for COB_FILE_SEQ_BUFFER_SIZE --- tests/Makefile.am | 1 + tests/Makefile.in | 1 + tests/misc.at | 1 + tests/misc.src/seq-buffer.at | 471 +++++++++++++++++++++++++++++++++++ tests/package.m4 | 6 +- 5 files changed, 477 insertions(+), 3 deletions(-) create mode 100644 tests/misc.src/seq-buffer.at diff --git a/tests/Makefile.am b/tests/Makefile.am index a14a525ca..bc20ada38 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -268,6 +268,7 @@ misc_DEPENDENCIES = \ misc.src/display-inspect-sign.at \ misc.src/comp1-comp2.at \ misc.src/variable-length-file.at \ + misc.src/seq-buffer.at \ misc.src/convert-string-concat.at \ misc.src/complex-odo.at diff --git a/tests/Makefile.in b/tests/Makefile.in index 6ae0a4324..bfdb571d3 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -817,6 +817,7 @@ misc_DEPENDENCIES = \ misc.src/display-inspect-sign.at \ misc.src/comp1-comp2.at \ misc.src/variable-length-file.at \ + misc.src/seq-buffer.at \ misc.src/convert-string-concat.at \ misc.src/complex-odo.at diff --git a/tests/misc.at b/tests/misc.at index 606706449..3aa7d0052 100644 --- a/tests/misc.at +++ b/tests/misc.at @@ -59,3 +59,4 @@ m4_include([comp1-comp2.at]) m4_include([variable-length-file.at]) m4_include([convert-string-concat.at]) m4_include([complex-odo.at]) +m4_include([seq-buffer.at]) diff --git a/tests/misc.src/seq-buffer.at b/tests/misc.src/seq-buffer.at new file mode 100644 index 000000000..d774c354b --- /dev/null +++ b/tests/misc.src/seq-buffer.at @@ -0,0 +1,471 @@ +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL read]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTSEQ" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CCCC" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=10 ${RUN_MODULE} prog], [0], +[AAAA +BBBB +CCCC +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL read with buffer size 1]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTSEQ" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CCCC" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=1 ${RUN_MODULE} prog], [0], +[AAAA +BBBB +CCCC +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL read with buffer disabled]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTSEQ" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CCCC" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=0 ${RUN_MODULE} prog], [0], +[AAAA +BBBB +CCCC +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: LINE SEQUENTIAL read via getc]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTLSEQ" + ORGANIZATION LINE SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(8). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "LINE0001" TO TEST-REC. + WRITE TEST-REC. + MOVE "LINE0002" TO TEST-REC. + WRITE TEST-REC. + MOVE "LINE0003" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=10 ${RUN_MODULE} prog], [0], +[LINE0001 +LINE0002 +LINE0003 +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL I-O rewrite]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTREWR" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CCCC" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN I-O TEST-FILE. + READ TEST-FILE. + READ TEST-FILE. + MOVE "XXXX" TO TEST-REC. + REWRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=10 ${RUN_MODULE} prog], [0], +[AAAA +XXXX +CCCC +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL many records]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTMANY" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(10). + WORKING-STORAGE SECTION. + 01 CTR PIC 9(4) VALUE 0. + 01 READ-CTR PIC 9(4) VALUE 0. + 01 F-STATUS PIC X(2). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + PERFORM 100 TIMES + ADD 1 TO CTR + MOVE CTR TO TEST-REC + WRITE TEST-REC + END-PERFORM. + CLOSE TEST-FILE. + + MOVE 0 TO READ-CTR. + OPEN INPUT TEST-FILE. + PERFORM UNTIL EXIT + READ TEST-FILE + AT END + EXIT PERFORM + END-READ + ADD 1 TO READ-CTR + END-PERFORM. + CLOSE TEST-FILE. + DISPLAY READ-CTR. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=3 ${RUN_MODULE} prog], [0], +[0100 +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: LINE SEQUENTIAL many records]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTLMNY" + ORGANIZATION LINE SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(10). + WORKING-STORAGE SECTION. + 01 CTR PIC 9(4) VALUE 0. + 01 READ-CTR PIC 9(4) VALUE 0. + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + PERFORM 100 TIMES + ADD 1 TO CTR + MOVE CTR TO TEST-REC + WRITE TEST-REC + END-PERFORM. + CLOSE TEST-FILE. + + MOVE 0 TO READ-CTR. + OPEN INPUT TEST-FILE. + PERFORM UNTIL EXIT + READ TEST-FILE + AT END + EXIT PERFORM + END-READ + ADD 1 TO READ-CTR + END-PERFORM. + CLOSE TEST-FILE. + DISPLAY READ-CTR. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=3 ${RUN_MODULE} prog], [0], +[0100 +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_WRITE_BUFFER_SIZE: deprecated fallback]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTDEP" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_WRITE_BUFFER_SIZE=10 ${RUN_MODULE} prog], [0], +[AAAA +BBBB +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL read then write in I-O mode]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTRW" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AAAA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BBBB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CCCC" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN I-O TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "ZZZZ" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=10 ${RUN_MODULE} prog], [0], +[AAAA +BBBB +CCCC +ZZZZ +BBBB +CCCC +]) + +AT_CLEANUP + + +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: LINE SEQUENTIAL read with short records]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTSHRT" + ORGANIZATION LINE SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(20). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CDEFGHIJ" TO TEST-REC. + WRITE TEST-REC. + MOVE "K" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY ">" TEST-REC "<". + READ TEST-FILE. + DISPLAY ">" TEST-REC "<". + READ TEST-FILE. + DISPLAY ">" TEST-REC "<". + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=2 ${RUN_MODULE} prog], [0], +[>AB < +>CDEFGHIJ < +>K < +]) + +AT_CLEANUP diff --git a/tests/package.m4 b/tests/package.m4 index 78d404454..4ce0d7d3d 100644 --- a/tests/package.m4 +++ b/tests/package.m4 @@ -1,6 +1,6 @@ # Signature of the current package. m4_define([AT_PACKAGE_NAME], [opensource COBOL 4J]) -m4_define([AT_PACKAGE_TARNAME], [opensource-cobol-4j-1.1.17]) -m4_define([AT_PACKAGE_VERSION], [1.1.17]) -m4_define([AT_PACKAGE_STRING], [opensource COBOL 4J 1.1.17]) +m4_define([AT_PACKAGE_TARNAME], [opensource-cobol-4j-1.1.18]) +m4_define([AT_PACKAGE_VERSION], [1.1.18]) +m4_define([AT_PACKAGE_STRING], [opensource COBOL 4J 1.1.18]) m4_define([AT_PACKAGE_BUGREPORT], [ws-opensource-cobol-contact@osscons.jp]) From 6c046859a62d5ed7cbd1e4de4e919e6d66076366 Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Mon, 2 Mar 2026 02:56:06 +0000 Subject: [PATCH 4/6] test: fix misc.src/seq-buffer.at --- tests/misc.src/seq-buffer.at | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/misc.src/seq-buffer.at b/tests/misc.src/seq-buffer.at index d774c354b..602341ee2 100644 --- a/tests/misc.src/seq-buffer.at +++ b/tests/misc.src/seq-buffer.at @@ -239,7 +239,8 @@ AT_DATA([prog.cbl], [ INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT TEST-FILE ASSIGN "TESTMANY" - ORGANIZATION SEQUENTIAL. + ORGANIZATION SEQUENTIAL + FILE STATUS F-STATUS. DATA DIVISION. FILE SECTION. FD TEST-FILE. @@ -248,6 +249,7 @@ AT_DATA([prog.cbl], [ 01 CTR PIC 9(4) VALUE 0. 01 READ-CTR PIC 9(4) VALUE 0. 01 F-STATUS PIC X(2). + 01 EOF-FLAG PIC 9 VALUE 0. PROCEDURE DIVISION. OPEN OUTPUT TEST-FILE. PERFORM 100 TIMES @@ -258,13 +260,15 @@ AT_DATA([prog.cbl], [ CLOSE TEST-FILE. MOVE 0 TO READ-CTR. + MOVE 0 TO EOF-FLAG. OPEN INPUT TEST-FILE. - PERFORM UNTIL EXIT + PERFORM UNTIL EOF-FLAG = 1 READ TEST-FILE AT END - EXIT PERFORM + MOVE 1 TO EOF-FLAG + NOT AT END + ADD 1 TO READ-CTR END-READ - ADD 1 TO READ-CTR END-PERFORM. CLOSE TEST-FILE. DISPLAY READ-CTR. @@ -288,7 +292,8 @@ AT_DATA([prog.cbl], [ INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT TEST-FILE ASSIGN "TESTLMNY" - ORGANIZATION LINE SEQUENTIAL. + ORGANIZATION LINE SEQUENTIAL + FILE STATUS F-STATUS. DATA DIVISION. FILE SECTION. FD TEST-FILE. @@ -296,6 +301,8 @@ AT_DATA([prog.cbl], [ WORKING-STORAGE SECTION. 01 CTR PIC 9(4) VALUE 0. 01 READ-CTR PIC 9(4) VALUE 0. + 01 F-STATUS PIC X(2). + 01 EOF-FLAG PIC 9 VALUE 0. PROCEDURE DIVISION. OPEN OUTPUT TEST-FILE. PERFORM 100 TIMES @@ -306,13 +313,15 @@ AT_DATA([prog.cbl], [ CLOSE TEST-FILE. MOVE 0 TO READ-CTR. + MOVE 0 TO EOF-FLAG. OPEN INPUT TEST-FILE. - PERFORM UNTIL EXIT + PERFORM UNTIL EOF-FLAG = 1 READ TEST-FILE AT END - EXIT PERFORM + MOVE 1 TO EOF-FLAG + NOT AT END + ADD 1 TO READ-CTR END-READ - ADD 1 TO READ-CTR END-PERFORM. CLOSE TEST-FILE. DISPLAY READ-CTR. From d99cc0b131a482593f7f8eda5bdd5b241657f3ad Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Tue, 10 Mar 2026 01:15:22 +0000 Subject: [PATCH 5/6] test: improve test for I/O buffers --- tests/misc.src/seq-buffer.at | 192 +++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) diff --git a/tests/misc.src/seq-buffer.at b/tests/misc.src/seq-buffer.at index 602341ee2..d3a169397 100644 --- a/tests/misc.src/seq-buffer.at +++ b/tests/misc.src/seq-buffer.at @@ -230,6 +230,111 @@ CCCC AT_CLEANUP +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL I-O interleaved read and rewrite]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTREWR" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(4). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "REC1" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC2" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC3" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC4" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC5" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC6" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC7" TO TEST-REC. + WRITE TEST-REC. + MOVE "REC8" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN I-O TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "UPD1" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "UPD3" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "UPD4" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "UPD6" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=2 ${RUN_MODULE} prog], [0], +[REC1 +REC2 +REC3 +REC4 +REC5 +REC6 +REC7 +REC8 +UPD1 +REC2 +UPD3 +UPD4 +REC5 +UPD6 +REC7 +REC8 +]) + +AT_CLEANUP + + AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL many records]) AT_DATA([prog.cbl], [ @@ -435,6 +540,93 @@ CCCC AT_CLEANUP +AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: SEQUENTIAL I-O repeated mode transitions]) + +AT_DATA([prog.cbl], [ + IDENTIFICATION DIVISION. + PROGRAM-ID. prog. + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT TEST-FILE ASSIGN "TESTRW" + ORGANIZATION SEQUENTIAL. + DATA DIVISION. + FILE SECTION. + FD TEST-FILE. + 01 TEST-REC PIC X(2). + PROCEDURE DIVISION. + OPEN OUTPUT TEST-FILE. + MOVE "AA" TO TEST-REC. + WRITE TEST-REC. + MOVE "BB" TO TEST-REC. + WRITE TEST-REC. + MOVE "CC" TO TEST-REC. + WRITE TEST-REC. + MOVE "DD" TO TEST-REC. + WRITE TEST-REC. + MOVE "EE" TO TEST-REC. + WRITE TEST-REC. + MOVE "FF" TO TEST-REC. + WRITE TEST-REC. + CLOSE TEST-FILE. + + OPEN I-O TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "XX" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "YY" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + MOVE "ZZ" TO TEST-REC. + REWRITE TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + + OPEN INPUT TEST-FILE. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + READ TEST-FILE. + DISPLAY TEST-REC. + CLOSE TEST-FILE. + STOP RUN. +]) + +AT_CHECK([${COMPILE} prog.cbl]) +AT_CHECK([COB_FILE_SEQ_BUFFER_SIZE=1 ${RUN_MODULE} prog], [0], +[AA +BB +CC +DD +EE +FF +XX +YY +CC +DD +ZZ +FF +]) + +AT_CLEANUP + + AT_SETUP([COB_FILE_SEQ_BUFFER_SIZE: LINE SEQUENTIAL read with short records]) AT_DATA([prog.cbl], [ From 23b16ee368993a7f2c3b4dbe581901956e0d41dc Mon Sep 17 00:00:00 2001 From: Yutaro Sakamoto Date: Tue, 10 Mar 2026 05:08:19 +0000 Subject: [PATCH 6/6] improve: the buffer algorithm --- .../opensourcecobol/libcobj/file/FileIO.java | 370 ++++++++++-------- 1 file changed, 217 insertions(+), 153 deletions(-) diff --git a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java index 23e2aa9ff..1c9b2354c 100644 --- a/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java +++ b/libcobj/app/src/main/java/jp/osscons/opensourcecobol/libcobj/file/FileIO.java @@ -38,15 +38,13 @@ class FileIO { private boolean useStdIn = true; private boolean atEnd = false; - private static final int BUFFER_MODE_NONE = 0; - private static final int BUFFER_MODE_READ = 1; - private static final int BUFFER_MODE_WRITE = 2; - - private int bufferMode = BUFFER_MODE_NONE; private int bufferSize = 0; private byte[] buffer; - private int bufferDataStart = 0; - private int bufferDataEnd = 0; + private int bufferPos = 0; + private int bufferValidEnd = 0; + private long bufferFileOffset = 0; + private boolean bufferDirty = false; + private boolean bufferActive = false; /** TODO: 準備中 */ FileIO() { @@ -115,36 +113,55 @@ void setIn(InputStream in) { void prepareBuffer(int bufferSize) { if (bufferSize > 0) { this.bufferSize = bufferSize; - this.bufferMode = BUFFER_MODE_NONE; - this.bufferDataStart = 0; - this.bufferDataEnd = 0; + this.bufferActive = false; + this.bufferDirty = false; + this.bufferPos = 0; + this.bufferValidEnd = 0; + this.bufferFileOffset = 0; if (this.buffer == null || this.buffer.length < bufferSize) { this.buffer = new byte[bufferSize]; } } } - private void destroyBuffer() { - this.bufferSize = 0; - this.bufferMode = BUFFER_MODE_NONE; - this.bufferDataStart = 0; - this.bufferDataEnd = 0; + private long logicalPosition() throws IOException { + if (bufferActive) { + return bufferFileOffset + bufferPos; + } + return fc.position(); + } + + private void deactivateBuffer() { + this.bufferActive = false; + this.bufferDirty = false; + this.bufferPos = 0; + this.bufferValidEnd = 0; + this.bufferFileOffset = 0; } - private boolean flushWriteBuffer() { - if (bufferMode == BUFFER_MODE_WRITE && bufferDataEnd > 0 && bufferSize > 0) { - ByteBuffer bb = ByteBuffer.wrap(buffer, 0, bufferDataEnd); + private boolean flushBuffer() { + if (bufferDirty && bufferValidEnd > 0) { + try { + fc.position(bufferFileOffset); + } catch (IOException e) { + return false; + } + ByteBuffer bb = ByteBuffer.wrap(buffer, 0, bufferValidEnd); if (!writeByteBuffer(bb)) { return false; } - bufferDataEnd = 0; + bufferDirty = false; } return true; } private int fillReadBuffer() throws IOException { - bufferDataStart = 0; - bufferDataEnd = 0; + long filePos = logicalPosition(); + bufferActive = false; + bufferFileOffset = filePos; + bufferPos = 0; + bufferValidEnd = 0; + fc.position(filePos); ByteBuffer bb = ByteBuffer.wrap(buffer, 0, bufferSize); int readBytes; try { @@ -155,37 +172,12 @@ private int fillReadBuffer() throws IOException { if (readBytes <= 0) { return -1; } - bufferDataEnd = readBytes; + bufferValidEnd = readBytes; + bufferActive = true; + bufferDirty = false; return readBytes; } - private void transitionToRead() throws IOException { - if (bufferMode == BUFFER_MODE_WRITE) { - if (!flushWriteBuffer()) { - throw new IOException("Failed to flush write buffer"); - } - } - if (bufferMode != BUFFER_MODE_READ) { - bufferMode = BUFFER_MODE_READ; - bufferDataStart = 0; - bufferDataEnd = 0; - } - } - - private void transitionToWrite() throws IOException { - if (bufferMode == BUFFER_MODE_READ) { - int unread = bufferDataEnd - bufferDataStart; - if (unread > 0) { - this.fc.position(this.fc.position() - unread); - } - } - if (bufferMode != BUFFER_MODE_WRITE) { - bufferMode = BUFFER_MODE_WRITE; - bufferDataStart = 0; - bufferDataEnd = 0; - } - } - /** * TODO: 準備中 * @@ -201,26 +193,32 @@ int read(byte[] bytes, int size) { } else { if (bufferSize > 0) { try { - transitionToRead(); int offset = 0; int remaining = size; while (remaining > 0) { - int available = bufferDataEnd - bufferDataStart; - if (available <= 0) { - if (fillReadBuffer() < 0) { - this.atEnd = true; - if (offset == 0) { - return 0; - } - return 1; + if (bufferActive) { + int available = bufferValidEnd - bufferPos; + if (available > 0) { + int toCopy = Math.min(remaining, available); + System.arraycopy(buffer, bufferPos, bytes, offset, toCopy); + bufferPos += toCopy; + offset += toCopy; + remaining -= toCopy; + continue; + } + } + if (bufferDirty) { + if (!flushBuffer()) { + return 0; + } + } + if (fillReadBuffer() < 0) { + this.atEnd = true; + if (offset == 0) { + return 0; } - available = bufferDataEnd - bufferDataStart; + return 1; } - int toCopy = Math.min(remaining, available); - System.arraycopy(buffer, bufferDataStart, bytes, offset, toCopy); - bufferDataStart += toCopy; - offset += toCopy; - remaining -= toCopy; } } catch (IOException e) { return 0; @@ -260,24 +258,30 @@ int read(CobolDataStorage storage, int size) throws IOException { throw new IOException(); } if (bufferSize > 0) { - transitionToRead(); int offset = 0; int remaining = size; while (remaining > 0) { - int available = bufferDataEnd - bufferDataStart; - if (available <= 0) { - if (fillReadBuffer() < 0) { + if (bufferActive) { + int available = bufferValidEnd - bufferPos; + if (available > 0) { + int toCopy = Math.min(remaining, available); + for (int i = 0; i < toCopy; ++i) { + storage.setByte(offset + i, buffer[bufferPos + i]); + } + bufferPos += toCopy; + offset += toCopy; + remaining -= toCopy; + continue; + } + } + if (bufferDirty) { + if (!flushBuffer()) { return offset; } - available = bufferDataEnd - bufferDataStart; } - int toCopy = Math.min(remaining, available); - for (int i = 0; i < toCopy; ++i) { - storage.setByte(offset + i, buffer[bufferDataStart + i]); + if (fillReadBuffer() < 0) { + return offset; } - bufferDataStart += toCopy; - offset += toCopy; - remaining -= toCopy; } return size; } @@ -308,6 +312,14 @@ private boolean writeByteBuffer(ByteBuffer bb) { return true; } + private void startNewBuffer(long filePos) { + bufferFileOffset = filePos; + bufferPos = 0; + bufferValidEnd = 0; + bufferActive = true; + bufferDirty = false; + } + /** * TODO: 準備中 * @@ -321,23 +333,43 @@ boolean write(byte[] bytes, int size) { } if (bufferSize > 0) { try { - transitionToWrite(); + if (bufferActive) { + if (bufferPos + size <= bufferSize) { + System.arraycopy(bytes, 0, buffer, bufferPos, size); + bufferPos += size; + bufferValidEnd = Math.max(bufferValidEnd, bufferPos); + bufferDirty = true; + return true; + } + if (!flushBuffer()) { + return false; + } + long filePos = bufferFileOffset + bufferPos; + fc.position(filePos); + startNewBuffer(filePos); + if (size <= bufferSize) { + System.arraycopy(bytes, 0, buffer, 0, size); + bufferPos = size; + bufferValidEnd = size; + bufferDirty = true; + return true; + } + deactivateBuffer(); + ByteBuffer bb = ByteBuffer.wrap(bytes, 0, size); + return writeByteBuffer(bb); + } + startNewBuffer(fc.position()); + if (size <= bufferSize) { + System.arraycopy(bytes, 0, buffer, 0, size); + bufferPos = size; + bufferValidEnd = size; + bufferDirty = true; + return true; + } + deactivateBuffer(); } catch (IOException e) { return false; } - if (size <= bufferSize - bufferDataEnd) { - System.arraycopy(bytes, 0, buffer, bufferDataEnd, size); - bufferDataEnd += size; - return true; - } - if (!flushWriteBuffer()) { - return false; - } - if (size <= bufferSize - bufferDataEnd) { - System.arraycopy(bytes, 0, buffer, bufferDataEnd, size); - bufferDataEnd += size; - return true; - } ByteBuffer bb = ByteBuffer.wrap(bytes, 0, size); return writeByteBuffer(bb); } @@ -358,26 +390,48 @@ boolean write(CobolDataStorage storage, int size) { } if (bufferSize > 0) { try { - transitionToWrite(); - } catch (IOException e) { - return false; - } - if (size <= bufferSize - bufferDataEnd) { - for (int i = 0; i < size; ++i) { - buffer[bufferDataEnd + i] = storage.getByte(i); + if (bufferActive) { + if (bufferPos + size <= bufferSize) { + for (int i = 0; i < size; ++i) { + buffer[bufferPos + i] = storage.getByte(i); + } + bufferPos += size; + bufferValidEnd = Math.max(bufferValidEnd, bufferPos); + bufferDirty = true; + return true; + } + if (!flushBuffer()) { + return false; + } + long filePos = bufferFileOffset + bufferPos; + fc.position(filePos); + startNewBuffer(filePos); + if (size <= bufferSize) { + for (int i = 0; i < size; ++i) { + buffer[i] = storage.getByte(i); + } + bufferPos = size; + bufferValidEnd = size; + bufferDirty = true; + return true; + } + deactivateBuffer(); + ByteBuffer bb = storage.getByteBuffer(size); + return writeByteBuffer(bb); } - bufferDataEnd += size; - return true; - } - if (!flushWriteBuffer()) { - return false; - } - if (size <= bufferSize - bufferDataEnd) { - for (int i = 0; i < size; ++i) { - buffer[bufferDataEnd + i] = storage.getByte(i); + startNewBuffer(fc.position()); + if (size <= bufferSize) { + for (int i = 0; i < size; ++i) { + buffer[i] = storage.getByte(i); + } + bufferPos = size; + bufferValidEnd = size; + bufferDirty = true; + return true; } - bufferDataEnd += size; - return true; + deactivateBuffer(); + } catch (IOException e) { + return false; } ByteBuffer bb = storage.getByteBuffer(size); return writeByteBuffer(bb); @@ -398,21 +452,34 @@ byte putc(byte val) { } if (bufferSize > 0) { try { - transitionToWrite(); - } catch (IOException e) { - return -1; - } - if (1 <= bufferSize - bufferDataEnd) { - buffer[bufferDataEnd++] = val; + if (bufferActive) { + if (bufferPos < bufferSize) { + buffer[bufferPos++] = val; + bufferValidEnd = Math.max(bufferValidEnd, bufferPos); + bufferDirty = true; + return val; + } + if (!flushBuffer()) { + return -1; + } + long filePos = bufferFileOffset + bufferPos; + fc.position(filePos); + startNewBuffer(filePos); + buffer[0] = val; + bufferPos = 1; + bufferValidEnd = 1; + bufferDirty = true; + return val; + } + startNewBuffer(fc.position()); + buffer[0] = val; + bufferPos = 1; + bufferValidEnd = 1; + bufferDirty = true; return val; - } - if (!flushWriteBuffer()) { + } catch (IOException e) { return -1; } - if (1 <= bufferSize - bufferDataEnd) { - buffer[bufferDataEnd++] = val; - return val; - } } byte[] arr = {val}; if (writeByteBuffer(ByteBuffer.wrap(arr))) { @@ -433,13 +500,18 @@ int getc() { } if (bufferSize > 0) { try { - transitionToRead(); - if (bufferDataStart >= bufferDataEnd) { - if (fillReadBuffer() < 0) { + if (bufferActive && bufferPos < bufferValidEnd) { + return buffer[bufferPos++] & 0xFF; + } + if (bufferDirty) { + if (!flushBuffer()) { return -1; } } - return buffer[bufferDataStart++] & 0xFF; + if (fillReadBuffer() < 0) { + return -1; + } + return buffer[bufferPos++] & 0xFF; } catch (IOException e) { return -1; } @@ -461,10 +533,9 @@ int getc() { void close() { if (!useStdOut && !useStdIn && this.fc != null) { try { - if (bufferMode == BUFFER_MODE_WRITE) { - flushWriteBuffer(); - } - destroyBuffer(); + flushBuffer(); + deactivateBuffer(); + this.bufferSize = 0; this.fc.close(); } catch (IOException e) { return; @@ -476,9 +547,7 @@ void close() { void flush() { if (!useStdOut) { try { - if (bufferMode == BUFFER_MODE_WRITE) { - flushWriteBuffer(); - } + flushBuffer(); this.fc.force(false); } catch (IOException e) { return; @@ -502,26 +571,31 @@ void flush() { boolean seek(long offset, int origin) { if (!useStdOut && !useStdIn) { try { - if (bufferMode == BUFFER_MODE_WRITE) { - flushWriteBuffer(); - } + long targetPos; switch (origin) { case FileIO.SEEK_SET: - this.fc.position(offset); + targetPos = offset; break; case FileIO.SEEK_CUR: - long adjustment = 0; - if (bufferMode == BUFFER_MODE_READ) { - adjustment = bufferDataEnd - bufferDataStart; - } - this.fc.position(this.fc.position() + offset - adjustment); + targetPos = logicalPosition() + offset; break; default: return false; } - bufferMode = BUFFER_MODE_NONE; - bufferDataStart = 0; - bufferDataEnd = 0; + + if (bufferActive) { + long relativePos = targetPos - bufferFileOffset; + if (relativePos >= 0 && relativePos <= bufferValidEnd) { + bufferPos = (int) relativePos; + return true; + } + if (!flushBuffer()) { + return false; + } + deactivateBuffer(); + } + + this.fc.position(targetPos); } catch (IOException e) { return false; } @@ -535,17 +609,7 @@ void seekInit() {} /** TODO: 準備中 */ void rewind() { if (!useStdOut && !useStdIn) { - try { - if (bufferMode == BUFFER_MODE_WRITE) { - flushWriteBuffer(); - } - bufferMode = BUFFER_MODE_NONE; - bufferDataStart = 0; - bufferDataEnd = 0; - this.fc.position(0L); - } catch (IOException e) { - return; - } + seek(0, SEEK_SET); } }