001package com.credibledoc.substitution.doc.module.substitution.logmessage;
002
003import org.springframework.stereotype.Service;
004
005import java.util.ArrayList;
006import java.util.List;
007
008/**
009 * This service helps to parse log messages.
010 * 
011 * @author Kyrylo Semenko
012 */
013@Service
014public class LogMessageService {
015
016    public static final String LINE_SEPARATOR = System.lineSeparator();
017    public static final String LOG_SEPARATOR = " - ";
018    public static final String DOT = ".";
019    public static final String FOUR_SPACES = "    ";
020    private static final String WORDS_SEPARATOR = " ";
021    private static final String BACKWARD_SLASH = "\\";
022    private static final String NOT_ALLOWED_AT_THE_END = "~;";
023
024    /**
025     * Parse a message from a log line and split it to rows.
026     * @param line for example
027     *             <pre>04.03.2019 18:41:13.658|main|INFO |com.credibledoc.substitution.core.configuration.ConfigurationService - Properties loaded by ClassLoader from the resource: file..</pre>
028     * @param maxRowLength maximal number of characters in a row
029     * @return For example <pre>Properties loaded by ClassLoader from the resource: file..</pre>
030     */
031    public String parseMessage(String line, int maxRowLength) {
032        int separatorIndex = line.indexOf(LOG_SEPARATOR);
033        String[] tokens = line.substring(separatorIndex + LOG_SEPARATOR.length()).split("\\s");
034        tokens = splitLongTokens(tokens, maxRowLength);
035        StringBuilder result = new StringBuilder(line.length());
036        StringBuilder row = new StringBuilder(line.length());
037        for (int i = 0; i < tokens.length; i++) {
038            if ("}".equals(tokens[i].trim())) {
039                row.insert(row.length() - 1, tokens[i].trim());
040                tokens[i] = "";
041            }
042            String escapedToken = escapeToken(tokens[i]);
043            if (escapedToken.endsWith(NOT_ALLOWED_AT_THE_END)) {
044                // PlantUML comment line cannot be ended by this sequence. Move this sequence to the next line.
045                if (tokens.length > i + 2) {
046                    tokens[i + 1] = NOT_ALLOWED_AT_THE_END + tokens[i + 1];
047                }
048                escapedToken = escapedToken.substring(0, escapedToken.length() - 2);
049            }
050            boolean hasMoreTokens = i + 1 < tokens.length;
051            boolean isShortRow = row.length() + escapedToken.length() + WORDS_SEPARATOR.length() < maxRowLength;
052            if (isShortRow) {
053                appendEscapedToRow(maxRowLength, row, escapedToken, hasMoreTokens);
054            } else {
055                if (row.toString().endsWith(BACKWARD_SLASH)) {
056                    row.replace(row.length() - 1, row.length(), "");
057                    escapedToken = BACKWARD_SLASH + escapedToken;
058                }
059                result.append(row);
060                result.append(LINE_SEPARATOR);
061                row = new StringBuilder(line.length()).append(FOUR_SPACES);
062                appendEscapedToRow(maxRowLength, row, escapedToken, hasMoreTokens);
063            }
064        }
065        result.append(row);
066        return result.toString();
067    }
068
069    private String[] splitLongTokens(String[] tokens, int maxRowLength) {
070        List<String> result = new ArrayList<>();
071        for (String token : tokens) {
072            if (token.length() <= maxRowLength) {
073                result.add(token);
074            } else {
075                while (token.length() > maxRowLength) {
076                    result.add(token.substring(0, maxRowLength));
077                    token = token.substring(maxRowLength);
078                }
079                if (token.length() > 0) {
080                    result.add(token);
081                }
082            }
083        }
084        return result.toArray(new String[0]);
085    }
086
087    private String escapeToken(String token) {
088        return token
089            .replaceAll(";", "~;")
090            .replaceAll("'", "");
091    }
092
093    private void appendEscapedToRow(int maxRowLength, StringBuilder row, String replaced, boolean hasMoreTokens) {
094        row.append(replaced);
095        if (row.length() < maxRowLength && hasMoreTokens) {
096            row.append(WORDS_SEPARATOR);
097        }
098    }
099}