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