Coverage Summary for Class: LogxFormatter (kr.open.library.simple_ui.core.logcat.internal.formatter)

Class Class, % Method, % Branch, % Line, % Instruction, %
LogxFormatter 100% (1/1) 100% (7/7) 64% (32/50) 97.3% (72/74) 91.6% (358/391)


 package kr.open.library.simple_ui.core.logcat.internal.formatter
 
 import kr.open.library.simple_ui.core.extensions.trycatch.safeCatch
 import kr.open.library.simple_ui.core.logcat.internal.common.LogxConstants
 import kr.open.library.simple_ui.core.logcat.internal.extractor.LogStackFrame
 import kr.open.library.simple_ui.core.logcat.internal.extractor.LogStackFrames
 
 /**
  * 로그 메시지 포맷을 생성하는 내부 포맷터입니다.
  *
  * Internal formatter that builds log output strings.
  * <br><br>
  * 로그 출력 문자열을 조합하는 내부 포맷터입니다.
  */
 internal object LogxFormatter {
     /**
      * 기본 로그 포맷 문자열을 생성합니다.
      *
      * Builds a basic log output string.
      * <br><br>
      * 기본 로그 출력 문자열을 생성합니다.
      *
      * @param frame 현재 프레임 정보.
      * @param msg 출력 메시지(없을 수 있음).
      * @param hasMessage 메시지 포함 여부.
      */
     fun formatBasic(frame: LogStackFrame, msg: String?, hasMessage: Boolean): String =
         buildString {
             append(formatMeta(frame))
             if (hasMessage) {
                 append(LogxFormatConstants.MESSAGE_SEPARATOR)
                 append(msg ?: LogxFormatConstants.NULL_MESSAGE)
             }
         }
 
     /**
      * PARENT 로그 포맷 문자열 목록을 생성합니다.
      *
      * Builds the PARENT log output lines.
      * <br><br>
      * 부모/현재 프레임을 포함한 PARENT 출력 라인을 생성합니다.
      *
      * @param frames 현재/부모 프레임 묶음.
      * @param msg 출력 메시지(없을 수 있음).
      * @param hasMessage 메시지 포함 여부.
      */
     fun formatParent(frames: LogStackFrames, msg: String?, hasMessage: Boolean): List<String> {
         val parentPayload = if (frames.parent != null) {
             LogxFormatConstants.PARENT_HEADER_PREFIX + formatMeta(frames.parent)
         } else {
             LogxFormatConstants.PARENT_HEADER_PLAIN
         }
 
         val currentPayload = buildString {
             append(LogxFormatConstants.PARENT_FOOTER_PREFIX)
             append(formatMeta(frames.current))
             if (hasMessage) {
                 append(LogxFormatConstants.MESSAGE_SEPARATOR)
                 append(msg ?: LogxFormatConstants.NULL_MESSAGE)
             }
         }
 
         return listOf(parentPayload, currentPayload)
     }
 
     /**
      * 스레드 ID가 포함된 로그 포맷 문자열을 생성합니다.
      *
      * Builds a log output string that includes thread id.
      * <br><br>
      * 스레드 ID 정보를 포함한 로그 문자열을 생성합니다.
      *
      * @param frame 현재 프레임 정보.
      * @param threadId 현재 스레드 ID.
      * @param msg 출력 메시지(없을 수 있음).
      * @param hasMessage 메시지 포함 여부.
      */
     fun formatThread(frame: LogStackFrame, threadId: Long, msg: String?, hasMessage: Boolean): String =
         buildString {
             append("[TID = ")
             append(threadId)
             append("]")
             append(formatMeta(frame))
             if (hasMessage) {
                 append(LogxFormatConstants.MESSAGE_SEPARATOR)
                 append(msg ?: LogxFormatConstants.NULL_MESSAGE)
             }
         }
 
     /**
      * JSON 로그 출력 구성 요소를 생성합니다.
      *
      * Builds a formatted JSON output container.
      * <br><br>
      * JSON 로그 출력 구성을 생성합니다.
      *
      * @param frame 현재 프레임 정보.
      * @param json JSON 원문 문자열.
      */
     fun formatJson(frame: LogStackFrame, json: String): FormattedJson {
         val meta = formatMeta(frame)
         val header = LogxFormatConstants.JSON_HEADER_MARKER + meta + LogxFormatConstants.JSON_HEADER_SEPARATOR
         val bodyLines = formatJsonBody(json)
         return FormattedJson(header = header, bodyLines = bodyLines)
     }
 
     /**
      * 파일/라인/메서드 정보를 포함한 메타 문자열을 생성합니다.
      *
      * Builds a meta string containing file, line, and method info.
      * <br><br>
      * 파일명/라인/메서드를 포함한 메타 문자열을 생성합니다.
      *
      * @param frame 현재 프레임 정보.
      */
     private fun formatMeta(frame: LogStackFrame): String {
         val fileName = frame.fileName
         val lineNumber = if (frame.lineNumber > 0) frame.lineNumber else 0
         return "($fileName:$lineNumber).${frame.methodName}"
     }
 
     /**
      * JSON 본문 라인을 생성합니다.
      *
      * Builds the JSON body lines for output.
      * <br><br>
      * JSON 본문을 라인 단위로 분리합니다.
      *
      * @param json JSON 원문 문자열.
      */
     private fun formatJsonBody(json: String): List<String> {
         val trimmed = json.trim()
         if (trimmed.isEmpty()) return listOf(json)
         val formatted = safeCatch(defaultValue = json) {
             if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
                 formatJsonPretty(trimmed)
             } else {
                 trimmed
             }
         }
 
         return formatted.split("\n")
     }
 
     /**
      * JSON 문자열을 보기 좋게 들여쓰기합니다.
      *
      * Pretty-prints a JSON string using the configured indent size.
      * <br><br>
      * 설정된 들여쓰기 크기로 JSON을 pretty-print 합니다.
      *
      * @param jsonString JSON 문자열.
      */
     private fun formatJsonPretty(jsonString: String): String {
         val result = StringBuilder()
         val indentUnit = " ".repeat(LogxConstants.JSON_INDENT)
         var indentLevel = 0
         var inQuotes = false
         var prevChar = ' '
 
         for (char in jsonString) {
             when {
                 char == '"' && prevChar != '\\' -> {
                     inQuotes = !inQuotes
                     result.append(char)
                 }
                 inQuotes -> {
                     result.append(char)
                 }
                 char == '{' || char == '[' -> {
                     result.append(char)
                     result.append('\n')
                     indentLevel++
                     result.append(indentUnit.repeat(indentLevel))
                 }
                 char == '}' || char == ']' -> {
                     result.append('\n')
                     indentLevel = maxOf(0, indentLevel - 1)
                     result.append(indentUnit.repeat(indentLevel))
                     result.append(char)
                 }
                 char == ',' -> {
                     result.append(char)
                     result.append('\n')
                     result.append(indentUnit.repeat(indentLevel))
                 }
                 char == ':' -> {
                     result.append(char)
                     result.append(' ')
                 }
                 char.isWhitespace() && result.lastOrNull()?.isWhitespace() == true -> {
                     // 연속 공백 제거
                 }
                 else -> {
                     result.append(char)
                 }
             }
             prevChar = char
         }
 
         return result.toString().trim()
     }
 }