From 025c2dd41b7935a98d42f94c1635e217b5412abe Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Wed, 29 Nov 2023 18:06:59 +0100 Subject: [PATCH 01/19] Replace scanner with reader #3406 --- .../{PipedInputScanner.kt => PipedInputReader.kt} | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/{PipedInputScanner.kt => PipedInputReader.kt} (82%) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputScanner.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt similarity index 82% rename from analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputScanner.kt rename to analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt index 55f00a06d4..bfc47e1701 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputScanner.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt @@ -1,7 +1,6 @@ package de.maibornwolff.codecharta.serialization import java.io.InputStream -import java.util.Scanner fun InputStream.mapLines(transform: (String) -> R): List { val result = mutableListOf() @@ -19,10 +18,9 @@ fun InputStream.mapLines(transform: (String) -> R): List { // sign to wait. In order to give the preceding command time to send this blank, we wait for some time before // checking the availability of the InputStream. fun InputStream.forEachLine(action: (String) -> Unit) { - val scanner = Scanner(this) + val reader = bufferedReader() Thread.sleep(1000) - if (available() <= 0) return // Otherwise it will get stuck waiting for user input - while (scanner.hasNext()) { - action(scanner.nextLine()) + while (reader.ready()) { + action(reader.readLine()) } } From 4a239ed87191911de1165065efb98a588b2a0639 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Thu, 30 Nov 2023 08:09:44 +0100 Subject: [PATCH 02/19] Update changelog #3406 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5c23a9c42..ce922ac82c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/) - Fix file extensions of output files for merged projects [#3421](https://github.com/MaibornWolff/codecharta/pull/3421) - Fix the ability for users to accidentally pass invalid metrics to the RawTextParser without it crashing [#3424](https://github.com/MaibornWolff/codecharta/pull/3424) - Fix deselected buildings with green/red roof in delta mode do not reset their color roof [#3426](https://github.com/MaibornWolff/codecharta/pull/3426) +- Fix parser hang issue in interactive mode caused by unintentional "enter" input after the last question [#3422](https://github.com/MaibornWolff/codecharta/pull/3422) ### Chore ‍👨‍💻 👩‍💻 From 6ed03596ea8138489554a7e185c0a6eb003b6670 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Thu, 30 Nov 2023 11:22:18 +0100 Subject: [PATCH 03/19] Add test cases for input stream handling #3406 --- .../serialization/PipedInputReaderKtTest.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt new file mode 100644 index 0000000000..3680bf4443 --- /dev/null +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt @@ -0,0 +1,38 @@ +package de.maibornwolff.codecharta.serialization + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.io.PipedInputStream +import java.io.PipedOutputStream +import java.nio.charset.StandardCharsets + +class PipedInputReaderTest { + @Test + fun `forEachLine should not wait for data when handling blocking input stream`() { + // given + val line1 = "line1" + val line2 = "line2" + val newLine = "\n" + val expectedLines = listOf(line1) + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + val lines = mutableListOf() + + // when + Thread { + Thread.sleep(5000) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + }.start() + + inputStream.forEachLine { + lines.add(it) + } + + // then + assertEquals(expectedLines, lines) + } +} From e0893b793ba00a80ad4880903321db8cded4f6ad Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Thu, 30 Nov 2023 11:33:28 +0100 Subject: [PATCH 04/19] Fix naming of test #3406 --- .../codecharta/serialization/PipedInputReader.kt | 14 +++++++------- ...nputReaderKtTest.kt => PipedInputReaderTest.kt} | 0 2 files changed, 7 insertions(+), 7 deletions(-) rename analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/{PipedInputReaderKtTest.kt => PipedInputReaderTest.kt} (100%) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt index bfc47e1701..bda2395ebe 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt @@ -10,13 +10,13 @@ fun InputStream.mapLines(transform: (String) -> R): List { return result } -// Bash runs commands in a pipe chain in parallel. This means that the input may not be available/complete at the -// time of reading it. -// The ccsh commands need to determine whether there is piped input or not. If there is, it should wait until the -// input is complete. To indicate that there will be a piped project, the filters/importers of ccsh send a blank to -// their OutputStream as soon as they start. This is detected by potentially following commands and is taken as a -// sign to wait. In order to give the preceding command time to send this blank, we wait for some time before -// checking the availability of the InputStream. +/* +Bash runs commands concurrently in a pipe chain, causing potential delays in input availability. +For ccsh commands, they check for piped input and wait until it's complete. +To signal piped projects, ccsh filters/importers send a blank to OutputStream at the start. +Subsequent commands detect this blank as a cue to wait. +To allow time for the preceding command to send the blank, a brief delay precedes InputStream availability checks. +*/ fun InputStream.forEachLine(action: (String) -> Unit) { val reader = bufferedReader() Thread.sleep(1000) diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt similarity index 100% rename from analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderKtTest.kt rename to analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt From 82b6c6715ea8e68655e8a38c499266c4fc49bd2a Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Thu, 30 Nov 2023 22:24:25 +0100 Subject: [PATCH 05/19] Simplify reading project string #3406 --- .../importer/tokeiimporter/TokeiImporter.kt | 4 +- .../serialization/ProjectDeserializer.kt | 2 +- ...edInputReader.kt => ProjectInputReader.kt} | 17 ++-- .../serialization/PipedInputReaderTest.kt | 38 --------- .../serialization/ProjectInputReaderTest.kt | 84 +++++++++++++++++++ 5 files changed, 93 insertions(+), 52 deletions(-) rename analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/{PipedInputReader.kt => ProjectInputReader.kt} (69%) delete mode 100644 analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt create mode 100644 analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt diff --git a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt index 12fe28a8fe..901931de82 100644 --- a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt +++ b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt @@ -9,7 +9,7 @@ import de.maibornwolff.codecharta.model.AttributeType import de.maibornwolff.codecharta.model.AttributeTypes import de.maibornwolff.codecharta.model.ProjectBuilder import de.maibornwolff.codecharta.serialization.ProjectSerializer -import de.maibornwolff.codecharta.serialization.mapLines +import de.maibornwolff.codecharta.serialization.readNonBlockingInput import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants @@ -125,7 +125,7 @@ class TokeiImporter( } } else { launch { - val projectString: String = input.mapLines { it }.joinToString(separator = "") { it } + val projectString: String = input.readNonBlockingInput() if (projectString.isNotEmpty()) { root = JsonParser.parseString(projectString) } else { diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt index 9880451271..744766bc0c 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt @@ -36,7 +36,7 @@ object ProjectDeserializer { fun deserializeProject(input: InputStream): Project? { val content = CompressedStreamHandler.wrapInput(input) - val projectString = content.mapLines { it }.joinToString(separator = "") { it } + val projectString = content.readNonBlockingInput() if (projectString.length <= 1) return null return try { diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt similarity index 69% rename from analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt rename to analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index bda2395ebe..8426efdcf4 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -1,14 +1,7 @@ package de.maibornwolff.codecharta.serialization import java.io.InputStream - -fun InputStream.mapLines(transform: (String) -> R): List { - val result = mutableListOf() - forEachLine { - result.add(transform(it)) - } - return result -} +import java.lang.StringBuilder /* Bash runs commands concurrently in a pipe chain, causing potential delays in input availability. @@ -17,10 +10,12 @@ To signal piped projects, ccsh filters/importers send a blank to OutputStream at Subsequent commands detect this blank as a cue to wait. To allow time for the preceding command to send the blank, a brief delay precedes InputStream availability checks. */ -fun InputStream.forEachLine(action: (String) -> Unit) { - val reader = bufferedReader() +fun InputStream.readNonBlockingInput(): String { Thread.sleep(1000) + val result = StringBuilder() + val reader = bufferedReader() while (reader.ready()) { - action(reader.readLine()) + result.append(reader.readLine()) } + return result.toString() } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt deleted file mode 100644 index 3680bf4443..0000000000 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/PipedInputReaderTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package de.maibornwolff.codecharta.serialization - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.nio.charset.StandardCharsets - -class PipedInputReaderTest { - @Test - fun `forEachLine should not wait for data when handling blocking input stream`() { - // given - val line1 = "line1" - val line2 = "line2" - val newLine = "\n" - val expectedLines = listOf(line1) - val inputStream = PipedInputStream() - val outputStream = PipedOutputStream(inputStream) - outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - val lines = mutableListOf() - - // when - Thread { - Thread.sleep(5000) - outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.close() - }.start() - - inputStream.forEachLine { - lines.add(it) - } - - // then - assertEquals(expectedLines, lines) - } -} diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt new file mode 100644 index 0000000000..cf46ccf704 --- /dev/null +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -0,0 +1,84 @@ +package de.maibornwolff.codecharta.serialization + +import org.assertj.core.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.io.PipedInputStream +import java.io.PipedOutputStream +import java.nio.charset.StandardCharsets + +class ProjectInputReaderTest { + @Test + fun `Should not wait for input when handling blocking input stream`() { + // given + val line1 = "line1" + val line2 = "line2" + val newLine = "\n" + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + + // when + Thread { + Thread.sleep(5000) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + }.start() + + val linesRead = inputStream.readNonBlockingInput() + + // then + assertEquals(line1, linesRead) + } + + @Test + fun `Should finish reading when encountering EOF in input stream`() { + // given + val line1 = "line1" + val line2 = "line2" + val line3 = "line3" + val newLine = "\n" + val expectedResult = buildString { + append(line1) + append(line2) + append(line3) + } + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line3.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + + // when + val linesRead = inputStream.readNonBlockingInput() + + // then + assertEquals(expectedResult, linesRead) + } + + @Test + fun `Should remove new line characters when provided with multiline input stream`() { + // given + val line1 = "line1" + val line2 = "line2" + val newLine = "\n" + + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + + // when + val linesRead = inputStream.readNonBlockingInput() + + // then + Assertions.assertThat(linesRead).doesNotContain(newLine) + } +} From 0c3570029c38270c78b8cd28767e369164ad1d50 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Fri, 1 Dec 2023 08:14:33 +0100 Subject: [PATCH 06/19] Close project reader properly #3406 --- .../codecharta/serialization/ProjectInputReader.kt | 9 ++++++--- .../codecharta/serialization/ProjectInputReaderTest.kt | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 8426efdcf4..db299191da 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -13,9 +13,12 @@ To allow time for the preceding command to send the blank, a brief delay precede fun InputStream.readNonBlockingInput(): String { Thread.sleep(1000) val result = StringBuilder() - val reader = bufferedReader() - while (reader.ready()) { - result.append(reader.readLine()) + + bufferedReader().use { reader -> + while (reader.ready()) { + result.append(reader.readLine()) + } } + return result.toString() } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index cf46ccf704..fbe6f99218 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -3,10 +3,15 @@ package de.maibornwolff.codecharta.serialization import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.Timeout import java.io.PipedInputStream import java.io.PipedOutputStream import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@Timeout(value = 10, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test fun `Should not wait for input when handling blocking input stream`() { From 39e6223aadabc2670c5077c64cd0acb7a96b2e5f Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Fri, 1 Dec 2023 09:23:00 +0100 Subject: [PATCH 07/19] Revert "Close project reader properly #3406" This reverts commit 1cae0bf47eb75190440d285740e1a3b92c4a7adb. --- .../codecharta/serialization/ProjectInputReader.kt | 9 +++------ .../codecharta/serialization/ProjectInputReaderTest.kt | 5 ----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index db299191da..8426efdcf4 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -13,12 +13,9 @@ To allow time for the preceding command to send the blank, a brief delay precede fun InputStream.readNonBlockingInput(): String { Thread.sleep(1000) val result = StringBuilder() - - bufferedReader().use { reader -> - while (reader.ready()) { - result.append(reader.readLine()) - } + val reader = bufferedReader() + while (reader.ready()) { + result.append(reader.readLine()) } - return result.toString() } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index fbe6f99218..cf46ccf704 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -3,15 +3,10 @@ package de.maibornwolff.codecharta.serialization import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.Timeout import java.io.PipedInputStream import java.io.PipedOutputStream import java.nio.charset.StandardCharsets -import java.util.concurrent.TimeUnit -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@Timeout(value = 10, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test fun `Should not wait for input when handling blocking input stream`() { From 483b24c24cfb167c24d64816a960ed21fb25e450 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Mon, 4 Dec 2023 08:15:45 +0100 Subject: [PATCH 08/19] Implement read-blocking-input #3406 --- .../serialization/ProjectInputReader.kt | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 8426efdcf4..d9f028f9e6 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -2,6 +2,7 @@ package de.maibornwolff.codecharta.serialization import java.io.InputStream import java.lang.StringBuilder +import java.util.Scanner /* Bash runs commands concurrently in a pipe chain, causing potential delays in input availability. @@ -12,10 +13,49 @@ To allow time for the preceding command to send the blank, a brief delay precede */ fun InputStream.readNonBlockingInput(): String { Thread.sleep(1000) + val availableBytes = available() + + if (availableBytes <= 0) { + return "" + } + + val maxBufferSize = 1024 + val bufferSize = minOf(availableBytes, maxBufferSize) + val buffer = ByteArray(bufferSize) + mark(bufferSize) + read(buffer, 0, bufferSize) + + val startSignal = " " + val startSignalBytes = startSignal.toByteArray() + val isStartSignalContained = buffer.containsSubarray(startSignalBytes) + + if (!isStartSignalContained) { + return "" + } + + reset() + val scanner = Scanner(this) val result = StringBuilder() - val reader = bufferedReader() - while (reader.ready()) { - result.append(reader.readLine()) + + while (scanner.hasNextLine()) { + val line = scanner.nextLine() + if (line.contains("\"checksum\":") || line.contains("\"data\":")) { + result.append(line) + while (scanner.hasNextLine()) { + result.append(scanner.nextLine()) + } + break + } } + return result.toString() } + +fun ByteArray.containsSubarray(subarray: ByteArray): Boolean { + for (i in 0 until size - subarray.size + 1) { + if (copyOfRange(i, i + subarray.size).contentEquals(subarray)) { + return true + } + } + return false +} From 6be85896b805b893354e2590d768798db969c46c Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 10:08:26 +0100 Subject: [PATCH 09/19] Create pipeable parser #3406 --- .../StructureModifierTest.kt | 4 +- analysis/import/GitLogParser/build.gradle | 1 + .../importer/gitlogparser/GitLogParser.kt | 6 +- analysis/import/SVNLogParser/build.gradle | 1 + .../importer/svnlogparser/SVNLogParser.kt | 8 +- analysis/import/SourceCodeParser/build.gradle | 1 + .../sourcecodeparser/SourceCodeParserMain.kt | 17 ++-- analysis/import/TokeiImporter/build.gradle | 1 + .../importer/tokeiimporter/TokeiImporter.kt | 11 ++- .../serialization/ProjectDeserializer.kt | 2 +- .../serialization/ProjectInputReader.kt | 87 ++++++++----------- .../serialization/ProjectDeserializerTest.kt | 3 + .../serialization/ProjectInputReaderTest.kt | 62 ++++++------- analysis/parser/RawTextParser/build.gradle | 1 + .../parser/rawtextparser/RawTextParser.kt | 7 +- analysis/settings.gradle | 5 +- analysis/tools/PipeableParser/build.gradle | 7 ++ .../tools/pipeableparser/PipeableParser.kt | 7 ++ .../pipeableparser/PipeableParserSyncFlag.kt | 5 ++ 19 files changed, 129 insertions(+), 107 deletions(-) create mode 100644 analysis/tools/PipeableParser/build.gradle create mode 100644 analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParser.kt create mode 100644 analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParserSyncFlag.kt diff --git a/analysis/filter/StructureModifier/src/test/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifierTest.kt b/analysis/filter/StructureModifier/src/test/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifierTest.kt index 5c79b05672..36075f50b6 100644 --- a/analysis/filter/StructureModifier/src/test/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifierTest.kt +++ b/analysis/filter/StructureModifier/src/test/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifierTest.kt @@ -33,8 +33,8 @@ class StructureModifierTest { @Test fun `reads project piped input`() { - val input = File("src/test/resources/sample_project.cc.json").bufferedReader().readLines() - .joinToString(separator = "") { it } + val inputFilePath = "src/test/resources/sample_project.cc.json" + val input = File(inputFilePath).bufferedReader().readLines().joinToString(separator = "") { it } val cliResult = executeForOutput(input, arrayOf("-r=/does/not/exist")) diff --git a/analysis/import/GitLogParser/build.gradle b/analysis/import/GitLogParser/build.gradle index d02d7c06c0..a8b275ebaa 100644 --- a/analysis/import/GitLogParser/build.gradle +++ b/analysis/import/GitLogParser/build.gradle @@ -2,6 +2,7 @@ dependencies { implementation project(':model') implementation project(':filter:MergeFilter') implementation project(':tools:InteractiveParser') + implementation project(':tools:PipeableParser') implementation group: 'org.apache.commons', name: 'commons-lang3', version: commons_lang3_version implementation group: 'info.picocli', name: 'picocli', version: picocli_version diff --git a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt index adf212a2b6..ce15b2cb7d 100644 --- a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt +++ b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt @@ -14,6 +14,8 @@ import de.maibornwolff.codecharta.serialization.ProjectSerializer import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParser +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import de.maibornwolff.codecharta.util.ResourceSearchHelper import org.mozilla.universalchardet.UniversalDetector import picocli.CommandLine @@ -36,7 +38,7 @@ class GitLogParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser { +) : Callable, InteractiveParser, PipeableParser { private val inputFormatNames = GIT_LOG_NUMSTAT_RAW_REVERSED @@ -95,7 +97,7 @@ class GitLogParser( @Throws(IOException::class) override fun call(): Void? { - print(" ") + logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) return null } diff --git a/analysis/import/SVNLogParser/build.gradle b/analysis/import/SVNLogParser/build.gradle index 1988bc9044..f7be31ec6e 100644 --- a/analysis/import/SVNLogParser/build.gradle +++ b/analysis/import/SVNLogParser/build.gradle @@ -2,6 +2,7 @@ dependencies { implementation project(':model') implementation project(':filter:MergeFilter') implementation project(':tools:InteractiveParser') + implementation project(':tools:PipeableParser') implementation group: 'org.apache.commons', name: 'commons-lang3', version: commons_lang3_version implementation group: 'info.picocli', name: 'picocli', version: picocli_version diff --git a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt index f917e17a2b..7ac2f24737 100644 --- a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt +++ b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt @@ -12,6 +12,8 @@ import de.maibornwolff.codecharta.serialization.ProjectSerializer import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParser +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import de.maibornwolff.codecharta.util.InputHelper import de.maibornwolff.codecharta.util.ResourceSearchHelper import org.mozilla.universalchardet.UniversalDetector @@ -35,7 +37,7 @@ class SVNLogParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser { +) : Callable, InteractiveParser, PipeableParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -74,7 +76,7 @@ class SVNLogParser( "weeks_with_commits", "highly_coupled_files", "median_coupled_files" - ) + ) return when (inputFormatNames) { SVN_LOG -> MetricsFactory(nonChurnMetrics) @@ -108,7 +110,7 @@ class SVNLogParser( @Throws(IOException::class) override fun call(): Void? { - print(" ") + logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) if (!InputHelper.isInputValidAndNotNull(arrayOf(file), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for SVNLogParser, stopping execution...") diff --git a/analysis/import/SourceCodeParser/build.gradle b/analysis/import/SourceCodeParser/build.gradle index 1e856629a9..df407b9039 100644 --- a/analysis/import/SourceCodeParser/build.gradle +++ b/analysis/import/SourceCodeParser/build.gradle @@ -2,6 +2,7 @@ dependencies { implementation project(':model') implementation project(':filter:MergeFilter') implementation project(':tools:InteractiveParser') + implementation project(':tools:PipeableParser') implementation group: 'info.picocli', name: 'picocli', version: picocli_version diff --git a/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt b/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt index b990344e4b..19617e2d45 100644 --- a/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt +++ b/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt @@ -9,6 +9,8 @@ import de.maibornwolff.codecharta.serialization.ProjectDeserializer import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParser +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import de.maibornwolff.codecharta.util.InputHelper import de.maibornwolff.codecharta.util.ResourceSearchHelper import mu.KotlinLogging @@ -30,10 +32,10 @@ import java.util.concurrent.Callable footer = [SourceCodeParserMain.FOOTER] ) class SourceCodeParserMain( - private val outputStream: PrintStream, - private val input: InputStream = System.`in`, - private val error: PrintStream = System.err -) : Callable, InteractiveParser { + private val output: PrintStream, + private val input: InputStream = System.`in`, + private val error: PrintStream = System.err +) : Callable, InteractiveParser, PipeableParser { // we need this constructor because ccsh requires an empty constructor constructor() : this(System.out) @@ -104,7 +106,8 @@ class SourceCodeParserMain( @Throws(IOException::class) override fun call(): Void? { - print(" ") + logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) + if (!InputHelper.isInputValidAndNotNull(arrayOf(file), canInputContainFolders = true)) { throw IllegalArgumentException("Input invalid file for SourceCodeParser, stopping execution...") } @@ -134,7 +137,7 @@ class SourceCodeParserMain( private fun getCsvOutputWriter(): Writer { return if (outputFile == null) { - OutputStreamWriter(outputStream) + OutputStreamWriter(output) } else { val outputName = outputFile!!.name BufferedWriter(FileWriter(OutputFileHandler.checkAndFixFileExtension(outputName, false, FileExtension.CSV))) @@ -152,7 +155,7 @@ class SourceCodeParserMain( } } - private fun getJsonOutputStream() = OutputFileHandler.stream(outputFile?.absolutePath, outputStream, compress) + private fun getJsonOutputStream() = OutputFileHandler.stream(outputFile?.absolutePath, output, compress) override fun getDialog(): ParserDialogInterface = ParserDialog override fun isApplicable(resourceToBeParsed: String): Boolean { diff --git a/analysis/import/TokeiImporter/build.gradle b/analysis/import/TokeiImporter/build.gradle index 5e42d06665..a4f3060376 100644 --- a/analysis/import/TokeiImporter/build.gradle +++ b/analysis/import/TokeiImporter/build.gradle @@ -1,6 +1,7 @@ dependencies { implementation project(':model') implementation project(':tools:InteractiveParser') + implementation project(':tools:PipeableParser') implementation group: 'info.picocli', name: 'picocli', version: picocli_version implementation group: 'io.fastjson', name: 'boon', version: boon_version diff --git a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt index 901931de82..670ac1d7a6 100644 --- a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt +++ b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt @@ -8,11 +8,13 @@ import de.maibornwolff.codecharta.importer.tokeiimporter.strategy.TokeiTwelveStr import de.maibornwolff.codecharta.model.AttributeType import de.maibornwolff.codecharta.model.AttributeTypes import de.maibornwolff.codecharta.model.ProjectBuilder +import de.maibornwolff.codecharta.serialization.ProjectInputReader import de.maibornwolff.codecharta.serialization.ProjectSerializer -import de.maibornwolff.codecharta.serialization.readNonBlockingInput import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParser +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import de.maibornwolff.codecharta.util.InputHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -35,7 +37,7 @@ class TokeiImporter( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser { +) : Callable, InteractiveParser, PipeableParser { private val logger = KotlinLogging.logger {} @@ -85,7 +87,8 @@ class TokeiImporter( @Throws(IOException::class) override fun call(): Void? { - print(" ") + logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) + projectBuilder = ProjectBuilder() val root = getInput() ?: return null runBlocking(Dispatchers.Default) { @@ -125,7 +128,7 @@ class TokeiImporter( } } else { launch { - val projectString: String = input.readNonBlockingInput() + val projectString: String = ProjectInputReader.extractProjectString(input) if (projectString.isNotEmpty()) { root = JsonParser.parseString(projectString) } else { diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt index 744766bc0c..161da42568 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializer.kt @@ -36,7 +36,7 @@ object ProjectDeserializer { fun deserializeProject(input: InputStream): Project? { val content = CompressedStreamHandler.wrapInput(input) - val projectString = content.readNonBlockingInput() + val projectString = ProjectInputReader.extractProjectString(content) if (projectString.length <= 1) return null return try { diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index d9f028f9e6..97ca39e37f 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -1,61 +1,50 @@ package de.maibornwolff.codecharta.serialization +import java.io.BufferedInputStream import java.io.InputStream -import java.lang.StringBuilder +import java.io.InputStreamReader +import java.nio.charset.StandardCharsets import java.util.Scanner -/* -Bash runs commands concurrently in a pipe chain, causing potential delays in input availability. -For ccsh commands, they check for piped input and wait until it's complete. -To signal piped projects, ccsh filters/importers send a blank to OutputStream at the start. -Subsequent commands detect this blank as a cue to wait. -To allow time for the preceding command to send the blank, a brief delay precedes InputStream availability checks. -*/ -fun InputStream.readNonBlockingInput(): String { - Thread.sleep(1000) - val availableBytes = available() - - if (availableBytes <= 0) { - return "" - } - - val maxBufferSize = 1024 - val bufferSize = minOf(availableBytes, maxBufferSize) - val buffer = ByteArray(bufferSize) - mark(bufferSize) - read(buffer, 0, bufferSize) - - val startSignal = " " - val startSignalBytes = startSignal.toByteArray() - val isStartSignalContained = buffer.containsSubarray(startSignalBytes) - - if (!isStartSignalContained) { - return "" - } - - reset() - val scanner = Scanner(this) - val result = StringBuilder() +object ProjectInputReader { + /** + * Extracts a JSON string representing a project from the given InputStream. + * Because piped bash commands run concurrently, pipeable ccsh-parser send a sync flag + * to signal other parsers to check for piped input. + * A short wait ensures the availability of potential sync flags. + * + * @param input InputStream with serialized project data. + * @return JSON string of the project, or an empty string if no valid data is found. + */ + fun extractProjectString(input: InputStream): String { + Thread.sleep(100) + val availableBytes = input.available() + + if (availableBytes <= 0) { + return "" + } + if (!input.markSupported()) { + return extractProjectString(BufferedInputStream(input)) + } - while (scanner.hasNextLine()) { - val line = scanner.nextLine() - if (line.contains("\"checksum\":") || line.contains("\"data\":")) { - result.append(line) - while (scanner.hasNextLine()) { - result.append(scanner.nextLine()) - } - break + input.mark(availableBytes) + val buffer = CharArray(availableBytes) + val reader = InputStreamReader(input, StandardCharsets.UTF_8) + val bytesRead = reader.read(buffer, 0, availableBytes) + val content = StringBuilder().appendRange(buffer, 0, bytesRead).toString() + val newLineCharacter = '\n' + if (content[content.length - 1] == newLineCharacter) { + return content.replace(newLineCharacter.toString(), "") } - } + input.reset() - return result.toString() -} + val scanner = Scanner(input) + val stringBuilder = StringBuilder() -fun ByteArray.containsSubarray(subarray: ByteArray): Boolean { - for (i in 0 until size - subarray.size + 1) { - if (copyOfRange(i, i + subarray.size).contentEquals(subarray)) { - return true + while (scanner.hasNextLine()) { + stringBuilder.append(scanner.nextLine()) } + + return stringBuilder.toString() } - return false } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt index 1dd6cd094d..8c5e02959d 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt @@ -62,6 +62,7 @@ class ProjectDeserializerTest { assertThat(node.children).isNotNull } + /* @Test fun `should deserialize project from cc json gz with api version 1_2 or lower`() { val expectedInputStream = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_GZ_VERSION_1_0)!! @@ -80,4 +81,6 @@ class ProjectDeserializerTest { assertThat(project!!.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } + + */ } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index cf46ccf704..47d95c42f5 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -1,18 +1,21 @@ package de.maibornwolff.codecharta.serialization +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout import java.io.PipedInputStream import java.io.PipedOutputStream import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit +@Timeout(value = 15, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test - fun `Should not wait for input when handling blocking input stream`() { + fun `Should not accept input when pipeable parser sync flag is not set`() { // given val line1 = "line1" - val line2 = "line2" val newLine = "\n" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) @@ -20,65 +23,52 @@ class ProjectInputReaderTest { outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) // when - Thread { - Thread.sleep(5000) - outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.close() - }.start() - - val linesRead = inputStream.readNonBlockingInput() + val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - assertEquals(line1, linesRead) + Assertions.assertThat(linesRead).isEmpty() } @Test - fun `Should finish reading when encountering EOF in input stream`() { + fun `Should wait for input when pipeable parser sync flag is set`() { // given - val line1 = "line1" - val line2 = "line2" - val line3 = "line3" - val newLine = "\n" - val expectedResult = buildString { - append(line1) - append(line2) - append(line3) - } + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value + val line1 = "{\"data\":\"data\"}" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) - outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(line3.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) // when - val linesRead = inputStream.readNonBlockingInput() + Thread { + Thread.sleep(5000) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + }.start() + + val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - assertEquals(expectedResult, linesRead) + assertEquals(line1, linesRead) } @Test - fun `Should remove new line characters when provided with multiline input stream`() { + fun `Should discard input before project string when project comes at end of stream`() { // given + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value val line1 = "line1" - val line2 = "line2" - val newLine = "\n" + val line2 = "{\"data\":\"data\"}" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() // when - val linesRead = inputStream.readNonBlockingInput() + val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - Assertions.assertThat(linesRead).doesNotContain(newLine) + Assertions.assertThat(linesRead).isEqualTo(line2) } } diff --git a/analysis/parser/RawTextParser/build.gradle b/analysis/parser/RawTextParser/build.gradle index 1bef8c4eb0..c7be25c907 100644 --- a/analysis/parser/RawTextParser/build.gradle +++ b/analysis/parser/RawTextParser/build.gradle @@ -2,6 +2,7 @@ dependencies { implementation project(':model') implementation project(':filter:MergeFilter') implementation project(':tools:InteractiveParser') + implementation project(':tools:PipeableParser') implementation group: 'info.picocli', name: 'picocli', version: picocli_version implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version diff --git a/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt b/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt index e306027726..23ba6f00fd 100644 --- a/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt +++ b/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt @@ -5,6 +5,8 @@ import de.maibornwolff.codecharta.serialization.ProjectSerializer import de.maibornwolff.codecharta.tools.interactiveparser.InteractiveParser import de.maibornwolff.codecharta.tools.interactiveparser.ParserDialogInterface import de.maibornwolff.codecharta.tools.interactiveparser.util.CodeChartaConstants +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParser +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import de.maibornwolff.codecharta.util.CommaSeparatedStringToListConverter import de.maibornwolff.codecharta.util.FileExtensionConverter import de.maibornwolff.codecharta.util.InputHelper @@ -25,7 +27,7 @@ class RawTextParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err, -) : Callable, InteractiveParser { +) : Callable, InteractiveParser, PipeableParser { private val logger = KotlinLogging.logger {} @@ -93,7 +95,8 @@ class RawTextParser( @Throws(IOException::class) override fun call(): Void? { - print(" ") + logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) + if (!InputHelper.isInputValidAndNotNull(arrayOf(inputFile), canInputContainFolders = true)) { throw IllegalArgumentException("Input invalid file for RawTextParser, stopping execution...") } diff --git a/analysis/settings.gradle b/analysis/settings.gradle index fd6be04e59..162e935ab7 100644 --- a/analysis/settings.gradle +++ b/analysis/settings.gradle @@ -3,6 +3,9 @@ include 'filter:MergeFilter', 'filter:EdgeFilter', 'filter:StructureModifier' include 'import:CSVImporter', 'import:SVNLogParser', 'import:GitLogParser', 'import:SonarImporter', 'import:SourceCodeParser', 'import:CodeMaatImporter', 'import:TokeiImporter', 'import:MetricGardenerImporter' include 'parser:RawTextParser' include 'export:CSVExporter' -include 'tools:ValidationTool', 'tools:ccsh', 'tools:InteractiveParser' +include 'tools:ValidationTool', 'tools:ccsh', 'tools:InteractiveParser', 'tools:PipeableParser' rootProject.name = 'codecharta' +include 'tools:PipeableParser' +findProject(':tools:PipeableParser')?.name = 'PipeableParser' + diff --git a/analysis/tools/PipeableParser/build.gradle b/analysis/tools/PipeableParser/build.gradle new file mode 100644 index 0000000000..9595688b39 --- /dev/null +++ b/analysis/tools/PipeableParser/build.gradle @@ -0,0 +1,7 @@ +repositories { + mavenCentral() +} + +dependencies { + +} diff --git a/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParser.kt b/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParser.kt new file mode 100644 index 0000000000..13ea07e6ae --- /dev/null +++ b/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParser.kt @@ -0,0 +1,7 @@ +package de.maibornwolff.codecharta.tools.pipeableparser + +interface PipeableParser { + fun logPipeableParserSyncSignal(syncSignal: PipeableParserSyncFlag) { + print(syncSignal.value) + } +} diff --git a/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParserSyncFlag.kt b/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParserSyncFlag.kt new file mode 100644 index 0000000000..4b2a678880 --- /dev/null +++ b/analysis/tools/PipeableParser/src/main/kotlin/de/maibornwolff/codecharta/tools/pipeableparser/PipeableParserSyncFlag.kt @@ -0,0 +1,5 @@ +package de.maibornwolff.codecharta.tools.pipeableparser + +enum class PipeableParserSyncFlag(val value: String) { + SYNC_FLAG("\r\r\r\r\r") +} From cabeaf8a3190c6fc4188ca6f1192ed3dd97e19d4 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 10:43:20 +0100 Subject: [PATCH 10/19] Add pipeable parser as dependency to ccsh #3406 --- .../serialization/ProjectInputReaderTest.kt | 56 +++++++++++-------- analysis/tools/ccsh/build.gradle | 8 ++- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 47d95c42f5..69ed2ca4b2 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -1,21 +1,18 @@ package de.maibornwolff.codecharta.serialization -import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Timeout import java.io.PipedInputStream import java.io.PipedOutputStream import java.nio.charset.StandardCharsets -import java.util.concurrent.TimeUnit -@Timeout(value = 15, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test - fun `Should not accept input when pipeable parser sync flag is not set`() { + fun `Should not wait for input when handling blocking input stream`() { // given val line1 = "line1" + val line2 = "line2" val newLine = "\n" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) @@ -23,52 +20,65 @@ class ProjectInputReaderTest { outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) // when + Thread { + Thread.sleep(5000) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + }.start() + val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - Assertions.assertThat(linesRead).isEmpty() + assertEquals(line1, linesRead) } @Test - fun `Should wait for input when pipeable parser sync flag is set`() { + fun `Should finish reading when encountering EOF in input stream`() { // given - val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value - val line1 = "{\"data\":\"data\"}" + val line1 = "line1" + val line2 = "line2" + val line3 = "line3" + val newLine = "\n" + val expectedResult = buildString { + append(line1) + append(line2) + append(line3) + } val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) - outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line3.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) // when - Thread { - Thread.sleep(5000) - outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.close() - }.start() - val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - assertEquals(line1, linesRead) + assertEquals(expectedResult, linesRead) } @Test - fun `Should discard input before project string when project comes at end of stream`() { + fun `Should remove new line characters when provided with multiline input stream`() { // given - val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value val line1 = "line1" - val line2 = "{\"data\":\"data\"}" + val line2 = "line2" + val newLine = "\n" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) - outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.close() + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) // when val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - Assertions.assertThat(linesRead).isEqualTo(line2) + Assertions.assertThat(linesRead).doesNotContain(newLine) } } diff --git a/analysis/tools/ccsh/build.gradle b/analysis/tools/ccsh/build.gradle index 6355a8f1b3..37cb4a68b3 100644 --- a/analysis/tools/ccsh/build.gradle +++ b/analysis/tools/ccsh/build.gradle @@ -33,6 +33,7 @@ dependencies { def csvExporter = project(':export:CSVExporter') def rawTextParser = project(':parser:RawTextParser') def interactiveParser = project(':tools:InteractiveParser') + def pipeableParser = project(':tools:PipeableParser') def metricGardenerImporter = project(':import:MetricGardenerImporter') // first implementation is for dependency in main, testImplementation is so our test suite can find all other tests @@ -50,6 +51,7 @@ dependencies { implementation structureModifier; testImplementation structureModifier.sourceSets.test.output implementation rawTextParser; testImplementation rawTextParser.sourceSets.test.output implementation interactiveParser; testImplementation interactiveParser.sourceSets.test.output + implementation pipeableParser; testImplementation pipeableParser.sourceSets.test.output implementation metricGardenerImporter; testImplementation metricGardenerImporter.sourceSets.test.output implementation group: 'info.picocli', name: 'picocli', version: picocli_version @@ -68,8 +70,8 @@ jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes 'Main-Class': mainClassName, - 'Implementation-Title': 'CodeCharta ccsh', - 'Implementation-Version': archiveVersion + 'Implementation-Title': 'CodeCharta ccsh', + 'Implementation-Version': archiveVersion } zip64 true exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA' @@ -87,5 +89,5 @@ startScripts{ } test { - useJUnitPlatform() + useJUnitPlatform() } From 59fa833fa11b44ec8c3a484f52e2ac1dee2b6ffe Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 11:52:06 +0100 Subject: [PATCH 11/19] Check sync signal and project start #3406 --- .../serialization/ProjectInputReader.kt | 80 ++++++++++++++++--- .../serialization/ProjectInputReaderTest.kt | 39 +++++++-- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 97ca39e37f..d54220bf21 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -1,5 +1,6 @@ package de.maibornwolff.codecharta.serialization +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import java.io.BufferedInputStream import java.io.InputStream import java.io.InputStreamReader @@ -27,24 +28,85 @@ object ProjectInputReader { return extractProjectString(BufferedInputStream(input)) } + val isSyncSignalContained = isSyncSignalContained(input, availableBytes) + val isProjectAtFrontOfStream = isJsonObjectAtFrontOfStream(input, availableBytes) + + if (!isSyncSignalContained and !isProjectAtFrontOfStream) { + return "" + } + + val scanner = Scanner(input) + val stringBuilder = StringBuilder() + + while (scanner.hasNextLine()) { + stringBuilder.append(scanner.nextLine()) + } + + return extractJsonObjectFromEndOfStream(stringBuilder.toString()) + } + + private fun isSyncSignalContained(input: InputStream, availableBytes: Int): Boolean { + val maxBufferSize = 1024 + val bufferSize = minOf(availableBytes, maxBufferSize) + val buffer = ByteArray(bufferSize) + input.mark(bufferSize) + input.read(buffer, 0, bufferSize) + + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value + val syncSignalBytes = syncFlag.toByteArray() + input.reset() + return isSubarray(syncSignalBytes, buffer) + } + + private fun isJsonObjectAtFrontOfStream(input: InputStream, availableBytes: Int): Boolean { input.mark(availableBytes) val buffer = CharArray(availableBytes) val reader = InputStreamReader(input, StandardCharsets.UTF_8) val bytesRead = reader.read(buffer, 0, availableBytes) val content = StringBuilder().appendRange(buffer, 0, bytesRead).toString() - val newLineCharacter = '\n' - if (content[content.length - 1] == newLineCharacter) { - return content.replace(newLineCharacter.toString(), "") - } + val openingBracket = "{".toCharArray()[0] + val closingBracket = "}".toCharArray()[0] + val lastClosingBracketIndex = content.lastIndexOf(closingBracket) + val firstOpeningBracketIndex = content.lastIndexOf(openingBracket) input.reset() + return firstOpeningBracketIndex == 0 && lastClosingBracketIndex == -1 + } - val scanner = Scanner(input) - val stringBuilder = StringBuilder() + private fun extractJsonObjectFromEndOfStream(streamContent: String): String { + var count = 0 + val openingBracket = "{".toCharArray()[0] + val closingBracket = "}".toCharArray()[0] + val lastClosingBracketIndex = streamContent.lastIndexOf(closingBracket) + if (lastClosingBracketIndex == -1) { + return streamContent + } - while (scanner.hasNextLine()) { - stringBuilder.append(scanner.nextLine()) + var index = lastClosingBracketIndex + while (index >= 0) { + when (streamContent[index]) { + closingBracket -> count++ + openingBracket -> count-- + } + if (count == 0) { + break + } + index-- } - return stringBuilder.toString() + return if (count == 0) { + streamContent.substring(index, lastClosingBracketIndex + 1) + } else { + streamContent + } } + + private fun isSubarray(subarray: ByteArray, buffer: ByteArray): Boolean { + for (i in 0 until buffer.size - subarray.size + 1) { + if (buffer.copyOfRange(i, i + subarray.size).contentEquals(subarray)) { + return true + } + } + return false + } + } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 69ed2ca4b2..422567dd17 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -1,29 +1,47 @@ package de.maibornwolff.codecharta.serialization +import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout import java.io.PipedInputStream import java.io.PipedOutputStream import java.nio.charset.StandardCharsets +import java.util.concurrent.TimeUnit +@Timeout(value = 15, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test - fun `Should not wait for input when handling blocking input stream`() { + fun `Should not accept input when pipeable parser sync flag is not set`() { // given val line1 = "line1" - val line2 = "line2" val newLine = "\n" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + // when + val linesRead = ProjectInputReader.extractProjectString(inputStream) + + // then + Assertions.assertThat(linesRead).isEmpty() + } + + @Test + fun `Should wait for input when pipeable parser sync flag is set`() { + // given + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value + val line1 = "{\"data\":\"data\"}" + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) + // when Thread { Thread.sleep(5000) - outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) outputStream.close() }.start() @@ -34,9 +52,11 @@ class ProjectInputReaderTest { } @Test - fun `Should finish reading when encountering EOF in input stream`() { + fun `Should discard input before project string when project comes at end of stream`() { // given + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value val line1 = "line1" +<<<<<<< Updated upstream val line2 = "line2" val line3 = "line3" val newLine = "\n" @@ -67,18 +87,21 @@ class ProjectInputReaderTest { val line1 = "line1" val line2 = "line2" val newLine = "\n" +======= + val line2 = "{\"data\":\"data\"}" +>>>>>>> Stashed changes val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() // when val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - Assertions.assertThat(linesRead).doesNotContain(newLine) + Assertions.assertThat(linesRead).isEqualTo(line2) } } From 31bd966b6eb61b1250df0384ef0fb048c13ccec6 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 12:02:52 +0100 Subject: [PATCH 12/19] Add pipeableparser to model module #3406 --- analysis/model/build.gradle | 2 ++ .../serialization/ProjectInputReaderTest.kt | 33 ------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/analysis/model/build.gradle b/analysis/model/build.gradle index 3763de5832..b656fc58fe 100644 --- a/analysis/model/build.gradle +++ b/analysis/model/build.gradle @@ -3,6 +3,7 @@ dependencies { implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version implementation group: 'io.github.microutils', name: 'kotlin-logging-jvm', version: '2.1.23' + implementation project(':tools:PipeableParser') implementation group: 'info.picocli', name: 'picocli', version: picocli_version implementation group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version @@ -12,6 +13,7 @@ dependencies { testImplementation group: 'org.assertj', name: 'assertj-core', version: assertj_version testImplementation group: 'io.mockk', name: 'mockk', version: mockk_version testImplementation group: 'org.skyscreamer', name: 'jsonassert', version: jsonassert_version + testImplementation project(':tools:PipeableParser') testRuntimeOnly group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 422567dd17..47d95c42f5 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -56,40 +56,7 @@ class ProjectInputReaderTest { // given val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value val line1 = "line1" -<<<<<<< Updated upstream - val line2 = "line2" - val line3 = "line3" - val newLine = "\n" - val expectedResult = buildString { - append(line1) - append(line2) - append(line3) - } - val inputStream = PipedInputStream() - val outputStream = PipedOutputStream(inputStream) - outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(line3.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) - - // when - val linesRead = ProjectInputReader.extractProjectString(inputStream) - - // then - assertEquals(expectedResult, linesRead) - } - - @Test - fun `Should remove new line characters when provided with multiline input stream`() { - // given - val line1 = "line1" - val line2 = "line2" - val newLine = "\n" -======= val line2 = "{\"data\":\"data\"}" ->>>>>>> Stashed changes val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) From dd51649b5cd21dbe5de11b5549866f27392cd116 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 12:17:47 +0100 Subject: [PATCH 13/19] Remove check project at start #3406 --- .../serialization/ProjectInputReader.kt | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index d54220bf21..db03a9cfad 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -18,7 +18,7 @@ object ProjectInputReader { * @return JSON string of the project, or an empty string if no valid data is found. */ fun extractProjectString(input: InputStream): String { - Thread.sleep(100) + Thread.sleep(1000) val availableBytes = input.available() if (availableBytes <= 0) { @@ -29,20 +29,22 @@ object ProjectInputReader { } val isSyncSignalContained = isSyncSignalContained(input, availableBytes) - val isProjectAtFrontOfStream = isJsonObjectAtFrontOfStream(input, availableBytes) - if (!isSyncSignalContained and !isProjectAtFrontOfStream) { - return "" - } + if (isSyncSignalContained) { + val scanner = Scanner(input) + val stringBuilder = StringBuilder() - val scanner = Scanner(input) - val stringBuilder = StringBuilder() + while (scanner.hasNextLine()) { + stringBuilder.append(scanner.nextLine()) + } - while (scanner.hasNextLine()) { - stringBuilder.append(scanner.nextLine()) + return extractJsonObjectFromEndOfStream(stringBuilder.toString()) } - return extractJsonObjectFromEndOfStream(stringBuilder.toString()) + val buffer = CharArray(availableBytes) + val reader = InputStreamReader(input, StandardCharsets.UTF_8) + val bytesRead = reader.read(buffer, 0, availableBytes) + return StringBuilder().appendRange(buffer, 0, bytesRead).toString() } private fun isSyncSignalContained(input: InputStream, availableBytes: Int): Boolean { @@ -58,20 +60,6 @@ object ProjectInputReader { return isSubarray(syncSignalBytes, buffer) } - private fun isJsonObjectAtFrontOfStream(input: InputStream, availableBytes: Int): Boolean { - input.mark(availableBytes) - val buffer = CharArray(availableBytes) - val reader = InputStreamReader(input, StandardCharsets.UTF_8) - val bytesRead = reader.read(buffer, 0, availableBytes) - val content = StringBuilder().appendRange(buffer, 0, bytesRead).toString() - val openingBracket = "{".toCharArray()[0] - val closingBracket = "}".toCharArray()[0] - val lastClosingBracketIndex = content.lastIndexOf(closingBracket) - val firstOpeningBracketIndex = content.lastIndexOf(openingBracket) - input.reset() - return firstOpeningBracketIndex == 0 && lastClosingBracketIndex == -1 - } - private fun extractJsonObjectFromEndOfStream(streamContent: String): String { var count = 0 val openingBracket = "{".toCharArray()[0] From d3f25b93a597c15eb734efbbda1ad217abc78a97 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 12:43:54 +0100 Subject: [PATCH 14/19] Fix preoject input reader tests #3406 --- .../serialization/ProjectInputReaderTest.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 47d95c42f5..739ad552ac 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -13,20 +13,25 @@ import java.util.concurrent.TimeUnit @Timeout(value = 15, unit = TimeUnit.SECONDS) class ProjectInputReaderTest { @Test - fun `Should not accept input when pipeable parser sync flag is not set`() { + fun `Should not wait for input when pipeable parser sync flag is not set`() { // given val line1 = "line1" - val newLine = "\n" + val line2 = "line2" val inputStream = PipedInputStream() val outputStream = PipedOutputStream(inputStream) outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) - outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) // when + Thread { + Thread.sleep(5000) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + }.start() + val linesRead = ProjectInputReader.extractProjectString(inputStream) // then - Assertions.assertThat(linesRead).isEmpty() + Assertions.assertThat(linesRead).isEqualTo(line1) } @Test From 9ad129e1c9354a2e3fcd72e94142140a83572757 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 14:39:58 +0100 Subject: [PATCH 15/19] Use fixed buffer size #3406 --- .../serialization/ProjectInputReader.kt | 24 +++++++++++++------ .../serialization/ProjectDeserializerTest.kt | 7 +++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index db03a9cfad..583afe1a50 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -2,10 +2,13 @@ package de.maibornwolff.codecharta.serialization import de.maibornwolff.codecharta.tools.pipeableparser.PipeableParserSyncFlag import java.io.BufferedInputStream +import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader +import java.io.Reader import java.nio.charset.StandardCharsets import java.util.Scanner +import java.util.zip.GZIPInputStream object ProjectInputReader { /** @@ -19,6 +22,7 @@ object ProjectInputReader { */ fun extractProjectString(input: InputStream): String { Thread.sleep(1000) + val maxBufferSize = 1024 val availableBytes = input.available() if (availableBytes <= 0) { @@ -28,7 +32,9 @@ object ProjectInputReader { return extractProjectString(BufferedInputStream(input)) } - val isSyncSignalContained = isSyncSignalContained(input, availableBytes) + println(GZIPInputStream.GZIP_MAGIC.toByte()) + + val isSyncSignalContained = isSyncSignalContained(input, availableBytes, maxBufferSize) if (isSyncSignalContained) { val scanner = Scanner(input) @@ -41,14 +47,18 @@ object ProjectInputReader { return extractJsonObjectFromEndOfStream(stringBuilder.toString()) } - val buffer = CharArray(availableBytes) - val reader = InputStreamReader(input, StandardCharsets.UTF_8) - val bytesRead = reader.read(buffer, 0, availableBytes) - return StringBuilder().appendRange(buffer, 0, bytesRead).toString() + val charBuffer = CharArray(maxBufferSize) + val reader = BufferedReader(InputStreamReader(input, StandardCharsets.UTF_8)) + val stringBuilder = StringBuilder() + while (reader.ready()) { + val bytesRead = reader.read(charBuffer) + stringBuilder.appendRange(charBuffer, 0, bytesRead) + } + return stringBuilder.toString() } - private fun isSyncSignalContained(input: InputStream, availableBytes: Int): Boolean { - val maxBufferSize = 1024 + private fun isSyncSignalContained(input: InputStream, availableBytes: Int, maxBufferSize: Int): Boolean { + val bufferSize = minOf(availableBytes, maxBufferSize) val buffer = ByteArray(bufferSize) input.mark(bufferSize) diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt index 8c5e02959d..9fb3d7874c 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt @@ -2,6 +2,8 @@ package de.maibornwolff.codecharta.serialization import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import java.io.File +import java.io.InputStream import java.io.StringReader class ProjectDeserializerTest { @@ -34,7 +36,7 @@ class ProjectDeserializerTest { fun `should deserialize project from cc json string with api version 1_2 or lower`() { val expectedJsonString = this.javaClass.classLoader.getResource(EXAMPLE_JSON_VERSION_1_0)!!.readText() - val project = ProjectDeserializer.deserializeProject(expectedJsonString) + val project = ProjectDeserializer.deserializeProject("garbage xxx xxx " + expectedJsonString) assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) @@ -62,7 +64,6 @@ class ProjectDeserializerTest { assertThat(node.children).isNotNull } - /* @Test fun `should deserialize project from cc json gz with api version 1_2 or lower`() { val expectedInputStream = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_GZ_VERSION_1_0)!! @@ -81,6 +82,4 @@ class ProjectDeserializerTest { assertThat(project!!.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } - - */ } From 93e4ae11f8b5a9e3fec0f708db06fa5fc5ddfb65 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 14:51:49 +0100 Subject: [PATCH 16/19] Remove unused imports #3406 --- .../maibornwolff/codecharta/serialization/ProjectInputReader.kt | 2 -- .../codecharta/serialization/ProjectDeserializerTest.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 583afe1a50..1fb1d267a5 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -5,7 +5,6 @@ import java.io.BufferedInputStream import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader -import java.io.Reader import java.nio.charset.StandardCharsets import java.util.Scanner import java.util.zip.GZIPInputStream @@ -106,5 +105,4 @@ object ProjectInputReader { } return false } - } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt index 9fb3d7874c..17df753cf7 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt @@ -2,8 +2,6 @@ package de.maibornwolff.codecharta.serialization import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import java.io.File -import java.io.InputStream import java.io.StringReader class ProjectDeserializerTest { From 185563ee58301c04bc9cadedc833076a75d36a37 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Tue, 5 Dec 2023 15:19:02 +0100 Subject: [PATCH 17/19] Remove newline characters from piped project #3406 --- .../codecharta/serialization/ProjectInputReader.kt | 6 ++---- .../codecharta/serialization/ProjectDeserializerTest.kt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 1fb1d267a5..40c230612b 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -7,7 +7,6 @@ import java.io.InputStream import java.io.InputStreamReader import java.nio.charset.StandardCharsets import java.util.Scanner -import java.util.zip.GZIPInputStream object ProjectInputReader { /** @@ -31,8 +30,6 @@ object ProjectInputReader { return extractProjectString(BufferedInputStream(input)) } - println(GZIPInputStream.GZIP_MAGIC.toByte()) - val isSyncSignalContained = isSyncSignalContained(input, availableBytes, maxBufferSize) if (isSyncSignalContained) { @@ -53,7 +50,8 @@ object ProjectInputReader { val bytesRead = reader.read(charBuffer) stringBuilder.appendRange(charBuffer, 0, bytesRead) } - return stringBuilder.toString() + val content = stringBuilder.toString() + return content.replace(Regex("[\\n\\r]"), "") } private fun isSyncSignalContained(input: InputStream, availableBytes: Int, maxBufferSize: Int): Boolean { diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt index 17df753cf7..1dd6cd094d 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt @@ -34,7 +34,7 @@ class ProjectDeserializerTest { fun `should deserialize project from cc json string with api version 1_2 or lower`() { val expectedJsonString = this.javaClass.classLoader.getResource(EXAMPLE_JSON_VERSION_1_0)!!.readText() - val project = ProjectDeserializer.deserializeProject("garbage xxx xxx " + expectedJsonString) + val project = ProjectDeserializer.deserializeProject(expectedJsonString) assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) From efc07f9bab3b23db8dd1e16f09f65affd51d9549 Mon Sep 17 00:00:00 2001 From: nereboss Date: Tue, 5 Dec 2023 16:09:28 +0100 Subject: [PATCH 18/19] Add tests for handling invalid input #3406 --- .../importer/svnlogparser/SVNLogParser.kt | 5 +-- .../serialization/ProjectInputReader.kt | 13 +++--- .../serialization/ProjectDeserializerTest.kt | 42 +++++++++++++++++++ .../serialization/ProjectInputReaderTest.kt | 24 +++++++++++ 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt index 7ac2f24737..954476a0d4 100644 --- a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt +++ b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt @@ -24,7 +24,6 @@ import java.io.InputStream import java.io.PrintStream import java.nio.charset.Charset import java.nio.file.Files -import java.util.Arrays import java.util.concurrent.Callable import java.util.stream.Stream @@ -66,7 +65,7 @@ class SVNLogParser( private val metricsFactory: MetricsFactory get() { - val nonChurnMetrics = Arrays.asList( + val nonChurnMetrics = listOf( "age_in_weeks", "number_of_authors", "number_of_commits", @@ -76,7 +75,7 @@ class SVNLogParser( "weeks_with_commits", "highly_coupled_files", "median_coupled_files" - ) + ) return when (inputFormatNames) { SVN_LOG -> MetricsFactory(nonChurnMetrics) diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index 40c230612b..e201f4e2dc 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -11,7 +11,7 @@ import java.util.Scanner object ProjectInputReader { /** * Extracts a JSON string representing a project from the given InputStream. - * Because piped bash commands run concurrently, pipeable ccsh-parser send a sync flag + * Because piped bash commands run concurrently, a pipeable ccsh-parser sends a sync flag * to signal other parsers to check for piped input. * A short wait ensures the availability of potential sync flags. * @@ -19,8 +19,7 @@ object ProjectInputReader { * @return JSON string of the project, or an empty string if no valid data is found. */ fun extractProjectString(input: InputStream): String { - Thread.sleep(1000) - val maxBufferSize = 1024 + Thread.sleep(100) val availableBytes = input.available() if (availableBytes <= 0) { @@ -30,7 +29,7 @@ object ProjectInputReader { return extractProjectString(BufferedInputStream(input)) } - val isSyncSignalContained = isSyncSignalContained(input, availableBytes, maxBufferSize) + val isSyncSignalContained = isSyncSignalContained(input, availableBytes) if (isSyncSignalContained) { val scanner = Scanner(input) @@ -43,7 +42,7 @@ object ProjectInputReader { return extractJsonObjectFromEndOfStream(stringBuilder.toString()) } - val charBuffer = CharArray(maxBufferSize) + val charBuffer = CharArray(1024) val reader = BufferedReader(InputStreamReader(input, StandardCharsets.UTF_8)) val stringBuilder = StringBuilder() while (reader.ready()) { @@ -54,9 +53,9 @@ object ProjectInputReader { return content.replace(Regex("[\\n\\r]"), "") } - private fun isSyncSignalContained(input: InputStream, availableBytes: Int, maxBufferSize: Int): Boolean { + private fun isSyncSignalContained(input: InputStream, availableBytes: Int): Boolean { - val bufferSize = minOf(availableBytes, maxBufferSize) + val bufferSize = minOf(availableBytes, 1024) val buffer = ByteArray(bufferSize) input.mark(bufferSize) input.read(buffer, 0, bufferSize) diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt index 1dd6cd094d..0b95ee9c8d 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectDeserializerTest.kt @@ -2,7 +2,10 @@ package de.maibornwolff.codecharta.serialization import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test +import java.io.PipedInputStream +import java.io.PipedOutputStream import java.io.StringReader +import java.nio.charset.StandardCharsets class ProjectDeserializerTest { val EXAMPLE_JSON_VERSION_1_0 = "example.cc.json" @@ -12,51 +15,66 @@ class ProjectDeserializerTest { @Test fun `should deserialize project from cc json with api version 1_2 or lower`() { + // given val expectedJsonReader = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_VERSION_1_0)!!.reader() + // when val project = ProjectDeserializer.deserializeProject(expectedJsonReader) + // then assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } @Test fun `should deserialize project from cc json with api version greater or equal than 1_3`() { + // given val expectedJsonReader = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_VERSION_1_3)!!.reader() + // when val project = ProjectDeserializer.deserializeProject(expectedJsonReader) + // then assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } @Test fun `should deserialize project from cc json string with api version 1_2 or lower`() { + // given val expectedJsonString = this.javaClass.classLoader.getResource(EXAMPLE_JSON_VERSION_1_0)!!.readText() + // when val project = ProjectDeserializer.deserializeProject(expectedJsonString) + // then assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } @Test fun `should deserialize project from cc json string with api version greater or equal than 1_3`() { + // given val expectedJsonReader = this.javaClass.classLoader.getResource(EXAMPLE_JSON_VERSION_1_3)!!.readText() + // when val project = ProjectDeserializer.deserializeProject(expectedJsonReader) + // then assertThat(project.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } @Test fun `should deserialize project from json string and set nonexisting values to defaults`() { + // given val jsonString = "{projectName='some Project', apiVersion='1.0', nodes=[{name:'root',type:'Folder'}]}" + // when val project = ProjectDeserializer.deserializeProject(StringReader(jsonString)) val node = project.rootNode + // then assertThat(node.link).isNull() assertThat(node.attributes).isNotNull assertThat(node.children).isNotNull @@ -64,8 +82,13 @@ class ProjectDeserializerTest { @Test fun `should deserialize project from cc json gz with api version 1_2 or lower`() { + // given val expectedInputStream = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_GZ_VERSION_1_0)!! + + // when val project = ProjectDeserializer.deserializeProject(expectedInputStream) + + // then assertThat(project).isNotNull assertThat(project!!.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) @@ -73,11 +96,30 @@ class ProjectDeserializerTest { @Test fun `should deserialize project from cc json gz with api version greater or equal than 1_3`() { + // given val expectedInputStream = this.javaClass.classLoader.getResourceAsStream(EXAMPLE_JSON_GZ_VERSION_1_3)!! + // when val project = ProjectDeserializer.deserializeProject(expectedInputStream) + + // then assertThat(project).isNotNull assertThat(project!!.projectName).isEqualTo("201701poolobject") assertThat(project.size).isEqualTo(6) } + + @Test + fun `should not create deserialized project when input is not valid project`() { + // given + val invalidInput = "This is \n invalid \t data" + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(invalidInput.toByteArray(StandardCharsets.UTF_8)) + + // when + val project = ProjectDeserializer.deserializeProject(inputStream) + + // then + assertThat(project).isNull() + } } diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 739ad552ac..9f117f9945 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -56,6 +56,30 @@ class ProjectInputReaderTest { assertEquals(line1, linesRead) } + @Test + fun `Should discard newline characters when input contains newline characters`() { + // given + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value + val line1 = "line1" + val line2 = "line2" + val newLine = "\n" + + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line1.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(line2.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(newLine.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + + // when + val linesRead = ProjectInputReader.extractProjectString(inputStream) + + // then + Assertions.assertThat(linesRead).isEqualTo(line1 + line2) + } + @Test fun `Should discard input before project string when project comes at end of stream`() { // given From fe35ae52e5e1833416dcd65406d3ec4c335c4d82 Mon Sep 17 00:00:00 2001 From: Ludwig Fritsch Date: Wed, 6 Dec 2023 11:58:17 +0100 Subject: [PATCH 19/19] Replace return type void with unit #3406 --- .../codecharta/exporter/csv/CSVExporter.kt | 4 ++-- .../filter/edgefilter/EdgeFilter.kt | 4 ++-- .../filter/mergefilter/MergeFilter.kt | 4 ++-- .../structuremodifier/StructureModifier.kt | 4 ++-- .../codecharta/importer/csv/CSVImporter.kt | 4 ++-- .../sourcemonitor/SourceMonitorImporter.kt | 4 ++-- .../importer/codemaat/CodeMaatImporter.kt | 4 ++-- .../importer/gitlogparser/GitLogParser.kt | 4 ++-- .../subcommands/LogScanCommand.kt | 4 ++-- .../subcommands/RepoScanCommand.kt | 4 ++-- .../MetricGardenerImporter.kt | 4 ++-- .../importer/svnlogparser/SVNLogParser.kt | 4 ++-- .../importer/sonar/SonarImporterMain.kt | 4 ++-- .../sourcecodeparser/SourceCodeParserMain.kt | 4 ++-- .../importer/tokeiimporter/TokeiImporter.kt | 4 ++-- .../serialization/ProjectInputReader.kt | 4 ++-- .../serialization/ProjectInputReaderTest.kt | 19 +++++++++++++++++++ .../parser/rawtextparser/RawTextParser.kt | 4 ++-- .../tools/validation/ValidationTool.kt | 4 ++-- .../codecharta/tools/ccsh/Ccsh.kt | 8 ++++---- 20 files changed, 59 insertions(+), 40 deletions(-) diff --git a/analysis/export/CSVExporter/src/main/kotlin/de/maibornwolff/codecharta/exporter/csv/CSVExporter.kt b/analysis/export/CSVExporter/src/main/kotlin/de/maibornwolff/codecharta/exporter/csv/CSVExporter.kt index 2d2e569114..1ca806dc0f 100644 --- a/analysis/export/CSVExporter/src/main/kotlin/de/maibornwolff/codecharta/exporter/csv/CSVExporter.kt +++ b/analysis/export/CSVExporter/src/main/kotlin/de/maibornwolff/codecharta/exporter/csv/CSVExporter.kt @@ -27,7 +27,7 @@ import java.util.concurrent.Callable description = [CSVExporter.DESCRIPTION], footer = [CodeChartaConstants.General.GENERIC_FOOTER] ) -class CSVExporter() : Callable, InteractiveParser { +class CSVExporter() : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -65,7 +65,7 @@ class CSVExporter() : Callable, InteractiveParser { } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { if (maxHierarchy < 0) { throw IllegalArgumentException("depth-of-hierarchy must not be negative") } diff --git a/analysis/filter/EdgeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/edgefilter/EdgeFilter.kt b/analysis/filter/EdgeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/edgefilter/EdgeFilter.kt index 1f6c7b1d28..9d76f8968a 100644 --- a/analysis/filter/EdgeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/edgefilter/EdgeFilter.kt +++ b/analysis/filter/EdgeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/edgefilter/EdgeFilter.kt @@ -18,7 +18,7 @@ import java.util.concurrent.Callable ) class EdgeFilter( private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) var help: Boolean = false @@ -45,7 +45,7 @@ class EdgeFilter( } } - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValidAndNotNull(arrayOf(source), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for EdgeFilter, stopping execution...") } diff --git a/analysis/filter/MergeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/mergefilter/MergeFilter.kt b/analysis/filter/MergeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/mergefilter/MergeFilter.kt index 984b5e262f..9c7b44aadd 100644 --- a/analysis/filter/MergeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/mergefilter/MergeFilter.kt +++ b/analysis/filter/MergeFilter/src/main/kotlin/de/maibornwolff/codecharta/filter/mergefilter/MergeFilter.kt @@ -20,7 +20,7 @@ import java.util.concurrent.Callable ) class MergeFilter( private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) var help: Boolean = false @@ -68,7 +68,7 @@ class MergeFilter( } } - override fun call(): Void? { + override fun call(): Unit? { val nodeMergerStrategy = when { leafStrategySet -> LeafNodeMergerStrategy(addMissingNodes, ignoreCase) diff --git a/analysis/filter/StructureModifier/src/main/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifier.kt b/analysis/filter/StructureModifier/src/main/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifier.kt index 848a925ecb..3c55136a77 100644 --- a/analysis/filter/StructureModifier/src/main/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifier.kt +++ b/analysis/filter/StructureModifier/src/main/kotlin/de/maibornwolff/codecharta/filter/structuremodifier/StructureModifier.kt @@ -23,7 +23,7 @@ class StructureModifier( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) var help: Boolean = false @@ -69,7 +69,7 @@ class StructureModifier( } } - override fun call(): Void? { + override fun call(): Unit? { project = readProject() ?: return null diff --git a/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/csv/CSVImporter.kt b/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/csv/CSVImporter.kt index b053c6e1df..1db1118bd3 100644 --- a/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/csv/CSVImporter.kt +++ b/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/csv/CSVImporter.kt @@ -19,7 +19,7 @@ import java.util.concurrent.Callable ) class CSVImporter( private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -56,7 +56,7 @@ class CSVImporter( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValid(files.toTypedArray(), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for CSVImporter, stopping execution...") } diff --git a/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcemonitor/SourceMonitorImporter.kt b/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcemonitor/SourceMonitorImporter.kt index 8a83eb3fbf..da90a83c5c 100644 --- a/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcemonitor/SourceMonitorImporter.kt +++ b/analysis/import/CSVImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcemonitor/SourceMonitorImporter.kt @@ -21,7 +21,7 @@ import java.util.concurrent.Callable ) class SourceMonitorImporter( private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -53,7 +53,7 @@ class SourceMonitorImporter( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValid(files.toTypedArray(), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for SourceMonitorImporter, stopping execution...") } diff --git a/analysis/import/CodeMaatImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/codemaat/CodeMaatImporter.kt b/analysis/import/CodeMaatImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/codemaat/CodeMaatImporter.kt index fc53d4bc40..0adb93e1e7 100644 --- a/analysis/import/CodeMaatImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/codemaat/CodeMaatImporter.kt +++ b/analysis/import/CodeMaatImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/codemaat/CodeMaatImporter.kt @@ -21,7 +21,7 @@ import java.util.concurrent.Callable footer = [CodeChartaConstants.General.GENERIC_FOOTER] ) class CodeMaatImporter( - private val output: PrintStream = System.out) : Callable, InteractiveParser { + private val output: PrintStream = System.out) : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -53,7 +53,7 @@ class CodeMaatImporter( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValid(files.toTypedArray(), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for CodeMaatImporter, stopping execution...") } diff --git a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt index ce15b2cb7d..86c0851f56 100644 --- a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt +++ b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/GitLogParser.kt @@ -38,7 +38,7 @@ class GitLogParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser, PipeableParser { +) : Callable, InteractiveParser, PipeableParser { private val inputFormatNames = GIT_LOG_NUMSTAT_RAW_REVERSED @@ -96,7 +96,7 @@ class GitLogParser( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) return null } diff --git a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/LogScanCommand.kt b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/LogScanCommand.kt index ca47678ff3..a3c595e2b3 100644 --- a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/LogScanCommand.kt +++ b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/LogScanCommand.kt @@ -15,7 +15,7 @@ import java.util.concurrent.Callable footer = [CodeChartaConstants.General.GENERIC_FOOTER] ) -class LogScanCommand : Callable, InteractiveParser { +class LogScanCommand : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -62,7 +62,7 @@ class LogScanCommand : Callable, InteractiveParser { const val DESCRIPTION = "git log parser log-scan - generates cc.json from a given git-log file" } - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValidAndNotNull(arrayOf(gitLogFile), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for GitLogScan, stopping execution...") } diff --git a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/RepoScanCommand.kt b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/RepoScanCommand.kt index 58e98f2797..a2887b1a51 100644 --- a/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/RepoScanCommand.kt +++ b/analysis/import/GitLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/gitlogparser/subcommands/RepoScanCommand.kt @@ -17,7 +17,7 @@ import java.util.concurrent.Callable footer = ["Copyright(c) 2022, MaibornWolff GmbH"] ) -class RepoScanCommand : Callable, InteractiveParser { +class RepoScanCommand : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -53,7 +53,7 @@ class RepoScanCommand : Callable, InteractiveParser { const val DESCRIPTION = "git log parser repo-scan - generates cc.json from an automatically generated git-log file" } - override fun call(): Void? { + override fun call(): Unit? { val repoPath: Path if (repoPathName == null || !InputHelper.isInputValid(arrayOf(File(repoPathName!!)), canInputContainFolders = true)) { throw IllegalArgumentException("Input invalid file for GitRepoScan, stopping execution...") diff --git a/analysis/import/MetricGardenerImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/metricgardenerimporter/MetricGardenerImporter.kt b/analysis/import/MetricGardenerImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/metricgardenerimporter/MetricGardenerImporter.kt index 8194797fdf..dfb223ed49 100644 --- a/analysis/import/MetricGardenerImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/metricgardenerimporter/MetricGardenerImporter.kt +++ b/analysis/import/MetricGardenerImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/metricgardenerimporter/MetricGardenerImporter.kt @@ -25,7 +25,7 @@ import java.util.concurrent.Callable class MetricGardenerImporter( private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { private val mapper = jacksonObjectMapper() @@ -71,7 +71,7 @@ class MetricGardenerImporter( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValidAndNotNull(arrayOf(inputFile), canInputContainFolders = true)) { throw IllegalArgumentException("Input invalid file for MetricGardenerImporter, stopping execution...") } diff --git a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt index 954476a0d4..79fe57daf3 100644 --- a/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt +++ b/analysis/import/SVNLogParser/src/main/kotlin/de/maibornwolff/codecharta/importer/svnlogparser/SVNLogParser.kt @@ -36,7 +36,7 @@ class SVNLogParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser, PipeableParser { +) : Callable, InteractiveParser, PipeableParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) private var help = false @@ -108,7 +108,7 @@ class SVNLogParser( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) if (!InputHelper.isInputValidAndNotNull(arrayOf(file), canInputContainFolders = false)) { diff --git a/analysis/import/SonarImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sonar/SonarImporterMain.kt b/analysis/import/SonarImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sonar/SonarImporterMain.kt index 5d0ec89728..dea77c29a6 100644 --- a/analysis/import/SonarImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sonar/SonarImporterMain.kt +++ b/analysis/import/SonarImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/sonar/SonarImporterMain.kt @@ -25,7 +25,7 @@ import java.util.concurrent.Callable class SonarImporterMain( private val input: InputStream = System.`in`, private val output: PrintStream = System.out -) : Callable, InteractiveParser { +) : Callable, InteractiveParser { @CommandLine.Option( names = ["-h", "--help"], usageHelp = true, description = [ @@ -88,7 +88,7 @@ class SonarImporterMain( return SonarMeasuresAPIImporter(measuresDatasource, metricsDatasource, sonarCodeURLLinker, translator, usePath) } - override fun call(): Void? { + override fun call(): Unit? { if (url == "" || projectId == "") { throw IllegalArgumentException("Input invalid Url or ProjectID for SonarImporter, stopping execution...") } diff --git a/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt b/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt index 19617e2d45..4ebd7c056e 100644 --- a/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt +++ b/analysis/import/SourceCodeParser/src/main/kotlin/de/maibornwolff/codecharta/importer/sourcecodeparser/SourceCodeParserMain.kt @@ -35,7 +35,7 @@ class SourceCodeParserMain( private val output: PrintStream, private val input: InputStream = System.`in`, private val error: PrintStream = System.err -) : Callable, InteractiveParser, PipeableParser { +) : Callable, InteractiveParser, PipeableParser { // we need this constructor because ccsh requires an empty constructor constructor() : this(System.out) @@ -105,7 +105,7 @@ class SourceCodeParserMain( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) if (!InputHelper.isInputValidAndNotNull(arrayOf(file), canInputContainFolders = true)) { diff --git a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt index 670ac1d7a6..bb97bd3766 100644 --- a/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt +++ b/analysis/import/TokeiImporter/src/main/kotlin/de/maibornwolff/codecharta/importer/tokeiimporter/TokeiImporter.kt @@ -37,7 +37,7 @@ class TokeiImporter( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err -) : Callable, InteractiveParser, PipeableParser { +) : Callable, InteractiveParser, PipeableParser { private val logger = KotlinLogging.logger {} @@ -86,7 +86,7 @@ class TokeiImporter( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) projectBuilder = ProjectBuilder() diff --git a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt index e201f4e2dc..f45de47018 100644 --- a/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt +++ b/analysis/model/src/main/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReader.kt @@ -68,8 +68,8 @@ object ProjectInputReader { private fun extractJsonObjectFromEndOfStream(streamContent: String): String { var count = 0 - val openingBracket = "{".toCharArray()[0] - val closingBracket = "}".toCharArray()[0] + val openingBracket = '{' + val closingBracket = '}' val lastClosingBracketIndex = streamContent.lastIndexOf(closingBracket) if (lastClosingBracketIndex == -1) { return streamContent diff --git a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt index 9f117f9945..111e94df4f 100644 --- a/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt +++ b/analysis/model/src/test/kotlin/de/maibornwolff/codecharta/serialization/ProjectInputReaderTest.kt @@ -100,4 +100,23 @@ class ProjectInputReaderTest { // then Assertions.assertThat(linesRead).isEqualTo(line2) } + + @Test + fun `Should return stream content when no valid project at end of stream`() { + // given + val syncFlag = PipeableParserSyncFlag.SYNC_FLAG.value + val invalidProjectData = "data\":\"data\"}" + + val inputStream = PipedInputStream() + val outputStream = PipedOutputStream(inputStream) + outputStream.write(syncFlag.toByteArray(StandardCharsets.UTF_8)) + outputStream.write(invalidProjectData.toByteArray(StandardCharsets.UTF_8)) + outputStream.close() + + // when + val linesRead = ProjectInputReader.extractProjectString(inputStream) + + // then + Assertions.assertThat(linesRead).isEqualTo(invalidProjectData) + } } diff --git a/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt b/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt index 23ba6f00fd..d063f730a8 100644 --- a/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt +++ b/analysis/parser/RawTextParser/src/main/kotlin/de/maibornwolff/codecharta/parser/rawtextparser/RawTextParser.kt @@ -27,7 +27,7 @@ class RawTextParser( private val input: InputStream = System.`in`, private val output: PrintStream = System.out, private val error: PrintStream = System.err, -) : Callable, InteractiveParser, PipeableParser { +) : Callable, InteractiveParser, PipeableParser { private val logger = KotlinLogging.logger {} @@ -94,7 +94,7 @@ class RawTextParser( } @Throws(IOException::class) - override fun call(): Void? { + override fun call(): Unit? { logPipeableParserSyncSignal(PipeableParserSyncFlag.SYNC_FLAG) if (!InputHelper.isInputValidAndNotNull(arrayOf(inputFile), canInputContainFolders = true)) { diff --git a/analysis/tools/ValidationTool/src/main/kotlin/de/maibornwolff/codecharta/tools/validation/ValidationTool.kt b/analysis/tools/ValidationTool/src/main/kotlin/de/maibornwolff/codecharta/tools/validation/ValidationTool.kt index 138c851143..76b7c28441 100644 --- a/analysis/tools/ValidationTool/src/main/kotlin/de/maibornwolff/codecharta/tools/validation/ValidationTool.kt +++ b/analysis/tools/ValidationTool/src/main/kotlin/de/maibornwolff/codecharta/tools/validation/ValidationTool.kt @@ -14,7 +14,7 @@ import java.util.concurrent.Callable description = [ValidationTool.DESCRIPTION], footer = [CodeChartaConstants.General.GENERIC_FOOTER] ) -class ValidationTool : Callable, InteractiveParser { +class ValidationTool : Callable, InteractiveParser { @CommandLine.Option(names = ["-h", "--help"], usageHelp = true, description = ["displays this help and exits"]) var help: Boolean = false @@ -32,7 +32,7 @@ class ValidationTool : Callable, InteractiveParser { const val SCHEMA_PATH = "cc.json" } - override fun call(): Void? { + override fun call(): Unit? { if (!InputHelper.isInputValidAndNotNull(arrayOf(file), canInputContainFolders = false)) { throw IllegalArgumentException("Input invalid file for ValidationTool, stopping execution...") } diff --git a/analysis/tools/ccsh/src/main/kotlin/de/maibornwolff/codecharta/tools/ccsh/Ccsh.kt b/analysis/tools/ccsh/src/main/kotlin/de/maibornwolff/codecharta/tools/ccsh/Ccsh.kt index 798c8533d1..12a09fbb61 100644 --- a/analysis/tools/ccsh/src/main/kotlin/de/maibornwolff/codecharta/tools/ccsh/Ccsh.kt +++ b/analysis/tools/ccsh/src/main/kotlin/de/maibornwolff/codecharta/tools/ccsh/Ccsh.kt @@ -56,7 +56,7 @@ import kotlin.system.exitProcess footer = [CodeChartaConstants.General.GENERIC_FOOTER] ) -class Ccsh : Callable { +class Ccsh : Callable { @CommandLine.Option( names = ["-v", "--version"], @@ -71,7 +71,7 @@ class Ccsh : Callable { @CommandLine.Option(names = ["-i", "--interactive"], description = ["starts interactive parser"]) var shouldUseInteractiveShell: Boolean = false - override fun call(): Void? { + override fun call(): Unit? { // info: always run return null @@ -245,9 +245,9 @@ class Ccsh : Callable { } @CommandLine.Command(name = "install", description = ["[deprecated]: does nothing"]) -class Installer : Callable { +class Installer : Callable { - override fun call(): Void? { + override fun call(): Unit? { println("[deprecated]: does nothing") return null }