diff --git a/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java b/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java deleted file mode 100644 index 52590b81..00000000 --- a/core/src/main/java/com/simiacryptus/skyenet/core/OutputInterceptor.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.simiacryptus.skyenet.core; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Map; -import java.util.WeakHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - -public final class OutputInterceptor { - - private OutputInterceptor() { - // Prevent instantiation of the utility class - } - - private static final PrintStream originalOut = System.out; - private static final PrintStream originalErr = System.err; - private static final AtomicBoolean isSetup = new AtomicBoolean(false); - private static final Object globalStreamLock = new Object(); - - public static void setupInterceptor() { - if (isSetup.getAndSet(true)) return; - System.setOut(new PrintStream(new OutputStreamRouter(originalOut))); - System.setErr(new PrintStream(new OutputStreamRouter(originalErr))); - } - - private static final ByteArrayOutputStream globalStream = new ByteArrayOutputStream(); - - private static final Map threadLocalBuffer = new WeakHashMap<>(); - - private static ByteArrayOutputStream getThreadOutputStream() { - Thread currentThread = Thread.currentThread(); - ByteArrayOutputStream outputStream; - synchronized (threadLocalBuffer) { - if ((outputStream = threadLocalBuffer.get(currentThread)) != null) return outputStream; - outputStream = new ByteArrayOutputStream(); - threadLocalBuffer.put(currentThread, outputStream); - } - return outputStream; - } - - public static String getThreadOutput() { - ByteArrayOutputStream outputStream = getThreadOutputStream(); - try { - outputStream.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return outputStream.toString(); - } - - public static void clearThreadOutput() { - getThreadOutputStream().reset(); - } - - public static String getGlobalOutput() { - synchronized (globalStreamLock) { - return globalStream.toString(); - } - } - - public static void clearGlobalOutput() { - synchronized (globalStreamLock) { - globalStream.reset(); - } - } - - private static class OutputStreamRouter extends ByteArrayOutputStream { - private final PrintStream originalStream; - int maxGlobalBuffer = 8 * 1024 * 1024; - int maxThreadBuffer = 1024 * 1024; - - public OutputStreamRouter(PrintStream originalStream) { - this.originalStream = originalStream; - } - - @Override - public void write(int b) { - originalStream.write(b); - synchronized (globalStreamLock) { - if (globalStream.size() > maxGlobalBuffer) { - globalStream.reset(); - } - globalStream.write(b); - } - ByteArrayOutputStream threadOutputStream = getThreadOutputStream(); - if (threadOutputStream.size() > maxThreadBuffer) { - threadOutputStream.reset(); - } - threadOutputStream.write(b); - } - - @Override - public void write(byte[] b, int off, int len) { - originalStream.write(b, off, len); - synchronized (globalStreamLock) { - if (globalStream.size() > maxGlobalBuffer) { - globalStream.reset(); - } - globalStream.write(b, off, len); - } - ByteArrayOutputStream threadOutputStream = getThreadOutputStream(); - if (threadOutputStream.size() > maxThreadBuffer) { - threadOutputStream.reset(); - } - threadOutputStream.write(b, off, len); - } - } -} - - diff --git a/core/src/main/kotlin/com/simiacryptus/skyenet/core/OutputInterceptor.kt b/core/src/main/kotlin/com/simiacryptus/skyenet/core/OutputInterceptor.kt new file mode 100644 index 00000000..efe85ca1 --- /dev/null +++ b/core/src/main/kotlin/com/simiacryptus/skyenet/core/OutputInterceptor.kt @@ -0,0 +1,92 @@ +package com.simiacryptus.skyenet.core + +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.PrintStream +import java.util.* +import java.util.concurrent.atomic.AtomicBoolean + +object OutputInterceptor { + private val originalOut: PrintStream = System.out + private val originalErr: PrintStream = System.err + private val isSetup = AtomicBoolean(false) + private val globalStreamLock = Any() + + fun setupInterceptor() { + if (isSetup.getAndSet(true)) return + System.setOut(PrintStream(OutputStreamRouter(originalOut))) + System.setErr(PrintStream(OutputStreamRouter(originalErr))) + } + + private val globalStream = ByteArrayOutputStream() + + private val threadLocalBuffer = WeakHashMap() + + private fun getThreadOutputStream(): ByteArrayOutputStream { + val currentThread = Thread.currentThread() + synchronized(threadLocalBuffer) { + return threadLocalBuffer.getOrPut(currentThread) { ByteArrayOutputStream() } + } + } + + fun getThreadOutput(): String { + val outputStream = getThreadOutputStream() + try { + outputStream.flush() + } catch (e: IOException) { + throw RuntimeException(e) + } + return outputStream.toString() + } + + fun clearThreadOutput() { + getThreadOutputStream().reset() + } + + fun getGlobalOutput(): String { + synchronized(globalStreamLock) { + return globalStream.toString() + } + } + + fun clearGlobalOutput() { + synchronized(globalStreamLock) { + globalStream.reset() + } + } + + private class OutputStreamRouter(private val originalStream: PrintStream) : ByteArrayOutputStream() { + private val maxGlobalBuffer = 8 * 1024 * 1024 + private val maxThreadBuffer = 1024 * 1024 + + override fun write(b: Int) { + originalStream.write(b) + synchronized(globalStreamLock) { + if (globalStream.size() > maxGlobalBuffer) { + globalStream.reset() + } + globalStream.write(b) + } + val threadOutputStream = getThreadOutputStream() + if (threadOutputStream.size() > maxThreadBuffer) { + threadOutputStream.reset() + } + threadOutputStream.write(b) + } + + override fun write(b: ByteArray, off: Int, len: Int) { + originalStream.write(b, off, len) + synchronized(globalStreamLock) { + if (globalStream.size() > maxGlobalBuffer) { + globalStream.reset() + } + globalStream.write(b, off, len) + } + val threadOutputStream = getThreadOutputStream() + if (threadOutputStream.size() > maxThreadBuffer) { + threadOutputStream.reset() + } + threadOutputStream.write(b, off, len) + } + } +} \ No newline at end of file diff --git a/core/src/test/java/com/simiacryptus/skyenet/core/OutputInterceptorThreadedTest.java b/core/src/test/java/com/simiacryptus/skyenet/core/OutputInterceptorThreadedTest.java index 85e451cc..e7e5a575 100644 --- a/core/src/test/java/com/simiacryptus/skyenet/core/OutputInterceptorThreadedTest.java +++ b/core/src/test/java/com/simiacryptus/skyenet/core/OutputInterceptorThreadedTest.java @@ -13,12 +13,12 @@ public class OutputInterceptorThreadedTest { @Test public void testThreadedInterceptor() throws InterruptedException { - OutputInterceptor.setupInterceptor(); + OutputInterceptor.INSTANCE.setupInterceptor(); AtomicInteger successCounter = new AtomicInteger(0); ExecutorService executorService = Executors.newFixedThreadPool(5); Object lock = new Object(); Runnable task = () -> { - OutputInterceptor.clearThreadOutput(); + OutputInterceptor.INSTANCE.clearThreadOutput(); String threadName = Thread.currentThread().getName(); System.out.println("Thread: " + threadName + " output"); System.err.println("Thread: " + threadName + " error"); @@ -28,7 +28,7 @@ public void testThreadedInterceptor() throws InterruptedException { Thread.currentThread().interrupt(); } String expectedOutput = ("Thread: " + threadName + " output\nThread: " + threadName + " error\n").trim(); - String threadOutput = OutputInterceptor.getThreadOutput().replace("\r", "").trim(); + String threadOutput = OutputInterceptor.INSTANCE.getThreadOutput().replace("\r", "").trim(); if (threadOutput.trim().equals(expectedOutput.trim())) { successCounter.incrementAndGet(); } else { diff --git a/gradle.properties b/gradle.properties index 4fcd6018..6f2a4b62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # Gradle Releases -> https://github.com/gradle/gradle/releases libraryGroup = com.simiacryptus.skyenet -libraryVersion = 1.0.86 +libraryVersion = 1.0.87 gradleVersion = 7.6.1 kotlin.daemon.jvmargs=-Xmx2g diff --git a/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt b/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt index 10a82c87..c7408ac0 100644 --- a/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt +++ b/webui/src/main/kotlin/com/simiacryptus/diff/IterativePatchUtil.kt @@ -332,52 +332,57 @@ object IterativePatchUtil { } } - private fun generatePatchedText( - sourceLines: List, - patchLines: List, - ): List { - log.debug("Starting to generate patched text") - val patchedText: MutableList = mutableListOf() - val usedPatchLines = mutableSetOf() - var sourceIndex = -1 - var lastMatchedPatchIndex = -1 - while (sourceIndex < sourceLines.size - 1) { - val codeLine = sourceLines[++sourceIndex] - when { - codeLine.matchingLine?.type == DELETE -> { - val patchLine = codeLine.matchingLine!! - log.debug("Deleting line: {}", codeLine) - // Delete the line -- do not add to patched text - usedPatchLines.add(patchLine) - checkAfterForInserts(patchLine, usedPatchLines, patchedText) - lastMatchedPatchIndex = patchLine.index - } - - codeLine.matchingLine != null -> { - val patchLine: LineRecord = codeLine.matchingLine!! - log.debug("Patching line: {} <-> {}", codeLine, patchLine) - checkBeforeForInserts(patchLine, usedPatchLines, patchedText) - usedPatchLines.add(patchLine) - patchedText.add(patchLine.line ?: "") // Add the patched line - checkAfterForInserts(patchLine, usedPatchLines, patchedText) - lastMatchedPatchIndex = patchLine.index - } - - else -> { - log.debug("Added unmatched source line: {}", codeLine) - patchedText.add(codeLine.line ?: "") - } - - } - } - if (lastMatchedPatchIndex == -1) patchLines.filter { it.type == ADD && !usedPatchLines.contains(it) } - .forEach { line -> - log.debug("Added patch line: {}", line) - patchedText.add(line.line ?: "") - } - log.debug("Generated patched text with ${patchedText.size} lines") - return patchedText - } +private fun generatePatchedText( + sourceLines: List, + patchLines: List, + ): List { + log.debug("Starting to generate patched text") + val patchedText: MutableList = mutableListOf() + val usedPatchLines = mutableSetOf() + var sourceIndex = -1 + var lastMatchedPatchIndex = -1 + while (sourceIndex < sourceLines.size - 1) { + val codeLine = sourceLines[++sourceIndex] + when { + codeLine.matchingLine?.type == DELETE -> { + val patchLine = codeLine.matchingLine!! + log.debug("Deleting line: {}", codeLine) + // Delete the line -- do not add to patched text + usedPatchLines.add(patchLine) + checkAfterForInserts(patchLine, usedPatchLines, patchedText) + lastMatchedPatchIndex = patchLine.index + } + + codeLine.matchingLine != null -> { + val patchLine: LineRecord = codeLine.matchingLine!! + log.debug("Patching line: {} <-> {}", codeLine, patchLine) + checkBeforeForInserts(patchLine, usedPatchLines, patchedText) + usedPatchLines.add(patchLine) + // Use the source line if it matches the patch line (ignoring whitespace) + if (normalizeLine(codeLine.line ?: "") == normalizeLine(patchLine.line ?: "")) { + patchedText.add(codeLine.line ?: "") + } else { + patchedText.add(patchLine.line ?: "") + } + checkAfterForInserts(patchLine, usedPatchLines, patchedText) + lastMatchedPatchIndex = patchLine.index + } + + else -> { + log.debug("Added unmatched source line: {}", codeLine) + patchedText.add(codeLine.line ?: "") + } + + } + } + if (lastMatchedPatchIndex == -1) patchLines.filter { it.type == ADD && !usedPatchLines.contains(it) } + .forEach { line -> + log.debug("Added patch line: {}", line) + patchedText.add(line.line ?: "") + } + log.debug("Generated patched text with ${patchedText.size} lines") + return patchedText + } private fun checkBeforeForInserts( patchLine: LineRecord, diff --git a/webui/src/test/kotlin/com/simiacryptus/diff/IterativePatchUtilTest.kt b/webui/src/test/kotlin/com/simiacryptus/diff/IterativePatchUtilTest.kt index 8acbbff2..8ab3b9ed 100644 --- a/webui/src/test/kotlin/com/simiacryptus/diff/IterativePatchUtilTest.kt +++ b/webui/src/test/kotlin/com/simiacryptus/diff/IterativePatchUtilTest.kt @@ -1,6 +1,5 @@ package com.simiacryptus.diff -import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -106,7 +105,7 @@ class IterativePatchUtilTest { """.trimIndent() val expected = """ line1 - line3 + line3 """.trimIndent() val result = IterativePatchUtil.applyPatch(source, patch) assertEquals(expected.trim().replace("\r\n", "\n"), result.replace("\r\n", "\n")) @@ -130,13 +129,13 @@ class IterativePatchUtilTest { line3 """.trimIndent() val expected = """ - line1 - lineA - lineB - - line2 - line3 - """.trimIndent() + |line1 + | lineA + | lineB + | + |line2 + |line3 + """.trimMargin() val result = IterativePatchUtil.applyPatch(source, patch) assertEquals(expected.trim().replace("\r\n", "\n"), result.replace("\r\n", "\n")) } @@ -159,13 +158,13 @@ class IterativePatchUtilTest { line3 """.trimIndent() val expected = """ - line1 - lineA - lineB - - line2 - line3 - """.trimIndent() + |line1 + | lineA + | lineB + | + |line2 + |line3 + """.trimMargin() val result = IterativePatchUtil.applyPatch(source, patch) assertEquals(expected.trim().replace("\r\n", "\n"), result.replace("\r\n", "\n")) }