com.mgnt.utils.TextUtils Maven / Gradle / Ivy
package com.mgnt.utils;
import com.mgnt.utils.textutils.InvalidVersionFormatException;
import com.mgnt.utils.textutils.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
/**
* This class provides various utilities for work with String that represents some other type. In current version this class provides methods for
* converting a String into its numeric value of various types (Integer, Float, Byte, Double, Long, Short). There are 2 methods for retrieving
* Exception stacktrace as a String in full or shortened version. Shortened version of the stacktrace will contain concise information focusing on
* specific package or subpackage while removing long parts of irrelevant stacktrace. This could be very useful for logging in web-based architecture
* where stacktrace may contain long parts of server provided classes trace that could be eliminated with the methods of this class while retaining
* important parts of the stacktrace relating to user's packages. Also this class provides methods that work with textual representation of versions.
* Valid version is a String of the following format:
*
*
* X[.X[.X[...]]]
*
*
*
* where X is a zero or positive integer not larger than 2147483647. Leading or trailing white spaces in this string are permitted and are ignored.
* Examples of valid versions are: "1.6", "58", " 7.34.17 " etc. (Note that last example contains both leading and trailing white spaces and it is
* still a valid version)
*
* Note that this class has a loose dependency on slf4J library. If in the project some other compatible logging library is present
* (such as Log4J) this class will still work without any ill effects
*
*
* @author Michael Gantman
*/
public class TextUtils {
private static final Logger logger = LoggerFactory.getLogger(TextUtils.class);
/*
* Strings defined bellow are for the use of methods getStacktrace() of this class
*/
private static String RELEVANT_PACKAGE = null;
private static final String STANDARD_STAKTRACE_PREFIX = "at ";
private static final String SKIPPING_LINES_STRING = "\t...";
private static final String CAUSE_STAKTRACE_PREFIX = "Caused by:";
private static final String SUPPRESED_STAKTRACE_PREFIX = "Suppressed:";
/**
* This method compares 2 Strings as versions
*
* @param ver1 String that contains version for comparison
* @param ver2 String that contains version for comparison
* @return negative integer (-1) if the first version is lesser then the second, 0 if the versions are equal and positive integer (1) if the first
* version is greater than second
* @throws com.mgnt.utils.textutils.InvalidVersionFormatException
* if any of the String instances is not a valid Version
*/
public static int compareVersions(String ver1, String ver2) throws InvalidVersionFormatException {
return new Version(ver1).compareTo(new Version(ver2));
}
/**
* This method compares a Version to a String that represents a version
*
* @param ver1 Version for comparison
* @param ver2 String that contains version for comparison
* @return negative integer (-1) if the first version is lesser then the second, 0 if the versions are equal and positive integer (1) if the first
* version is greater than second
* @throws com.mgnt.utils.textutils.InvalidVersionFormatException
* if the String parameter is not a valid Version
*/
public static int compareVersions(Version ver1, String ver2) throws InvalidVersionFormatException {
return ver1.compareTo(new Version(ver2));
}
/**
* This method compares a String that represents a version to a Version
*
* @param ver1 String that contains version for comparison
* @param ver2 Version for comparison
* @return negative integer (-1) if the first version is lesser then the second, 0 if the versions are equal and positive integer (1) if the first
* version is greater than second
* @throws com.mgnt.utils.textutils.InvalidVersionFormatException
* if the String parameter is not a valid Version
*/
public static int compareVersions(String ver1, Version ver2) throws InvalidVersionFormatException {
return new Version(ver1).compareTo(ver2);
}
/**
* This method compares two Version instances
*
* @param ver1 Version for comparison
* @param ver2 Version for comparison
* @return negative integer (-1) if the first version is lesser then the second, 0 if the versions are equal and positive integer (1) if the first
* version is greater than second
* @throws com.mgnt.utils.textutils.InvalidVersionFormatException
* if the String parameter is not a valid Version
*/
public static int compareVersions(Version ver1, Version ver2) {
return ver1.compareTo(ver2);
}
/**
* This method parses a String to its Numeric value. If parsing does not succeed because the String is not of appropriate number format the
* default numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is
* returned and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by
* project that uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty
* Strings than the correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static int parseStringToInt(CharSequence num, int defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Integer result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Integer.parseInt(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method parses a String to Numeric value. If parsing does not succeed because the String is not of appropriate number format the default
* numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is returned
* and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by project that
* uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty Strings than the
* correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static float parseStringToFloat(CharSequence num, float defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Float result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Float.parseFloat(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method parses a String to Numeric value. If parsing does not succeed because the String is not of appropriate number format the default
* numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is returned
* and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by project that
* uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty Strings than the
* correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static Byte parseStringToByte(CharSequence num, byte defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Byte result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Byte.parseByte(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method parses a String to Numeric value. If parsing does not succeed because the String is not of appropriate number format the default
* numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is returned
* and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by project that
* uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty Strings than the
* correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static double parseStringToDouble(CharSequence num, double defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Double result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Double.parseDouble(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method parses a String to Numeric value. If parsing does not succeed because the String is not of appropriate number format the default
* numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is returned
* and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by project that
* uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty Strings than the
* correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static long parseStringToLong(CharSequence num, long defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Long result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Long.parseLong(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method parses a String to Numeric value. If parsing does not succeed because the String is not of appropriate number format the default
* numeric value is returned and appropriate error message is printed into log. Also if the String is blank or null the default value is returned
* and appropriate error message is printed into log. Note that logging relies on slf4j being present and appropriately configured by project that
* uses this utility. If {@code nullOrEmptyStringErrorMessage} or {@code numberFormatErrorMessage} parameters are null or empty Strings than the
* correlating error will not be printed.
*
* @param num CharSequence to be parsed
* @param defaultValue value that will be returned by this method if parsing of the String failed
* @param nullOrEmptyStringErrorMessage String that holds an error message that will printed into log if parameter {@code num} is null or blank
* @param numberFormatErrorMessage String that holds an error message that will printed into log if parameter {@code num} is not in appropriate format
* @return numeric value parsed from the String
*/
public static Short parseStringToShort(CharSequence num, short defaultValue,
String nullOrEmptyStringErrorMessage, String numberFormatErrorMessage) {
Short result = null;
if (num == null || "".equals(num.toString())) {
if (nullOrEmptyStringErrorMessage != null && !"".equals(nullOrEmptyStringErrorMessage)) {
logger.warn(nullOrEmptyStringErrorMessage);
}
result = defaultValue;
} else {
try {
result = Short.parseShort(num.toString());
} catch (NumberFormatException nfe) {
if (numberFormatErrorMessage != null && !"".equals(numberFormatErrorMessage)) {
warn(numberFormatErrorMessage, nfe);
}
result = defaultValue;
}
}
return result;
}
/**
* This method retrieves a stacktrace from {@link Throwable} as a String in full or shortened format. Shortened format skips the lines in the
* stacktrace that do not start with a configurable package prefix and replaces them with "..." line. The stacktrace is viewed as consisting
* possibly of several parts. If stacktrace contains {@code "caused by"} or {@code "Suppressed"} section, each such section for the purposes of
* this utility is called "Singular stacktrace". For example the stacktrace bellow contains 2 singular stacktraces: First is 4 top lines and the
* second starting from the line {@code "Caused by: ..."} and to the end.
*
*
* java.lang.Exception: Bad error
* at com.plain.analytics.v2.utils.test.UtilsTester.TestGetStackTrace(UtilsTester.java:80)
* at com.plain.analytics.v2.utils.test.UtilsTester.run(UtilsTester.java:30)
* at com.plain.analytics.v2.utils.test.UtilsTester.main(UtilsTester.java:25)
* Caused by: java.lang.NumberFormatException: For input string: "Hello"
* at java.lang.NumberFormatException.forInputString(Unknown Source)
* at java.lang.Integer.parseInt(Unknown Source)
* at java.lang.Integer.parseInt(Unknown Source)
* at com.plain.analytics.v2.utils.test2.UtilsTesterHelper.parseInt(UtilsTesterHelper.java:8)
* at com.plain.analytics.v2.utils.test.UtilsTester$Invoker.runParser(UtilsTester.java:97)
* at com.plain.analytics.v2.utils.test2.UtilsTesterHelper.innerInvokeParser(UtilsTesterHelper.java:17)
* at com.plain.analytics.v2.utils.test2.UtilsTesterHelper.invokeParser(UtilsTesterHelper.java:12)
* at com.plain.analytics.v2.utils.test.UtilsTester.TestGetStackTrace(UtilsTester.java:76)
* ... 2 more
*
*
* The way this method shortens the stacktrace is as follows. Each "singular" stacktraces are analyzed and shortened separately. For each singular
* stacktrace the error message is always printed. Then all the lines that follow are printed even if they do not start with prefix specified by
* relevantPackage. Once the first line with the prefix is found this line and all immediately following lines that start with the relevant
* package prefix are printed as well. The first line that does not start with the prefix after a section of the lines that did is also printed.
* But all the following lines that do not start with the prefix are skipped and replaced with a single line "...". If at some point within the
* stacktrace a line that starts with the prefix is encountered again this line and all the following line that start with the prefix + one
* following line that does not start with the prefix are printed in. And so on. Here is an example: Assume that exception above was passed as a
* parameter to this method and parameter relevantPackage is set to {@code "com.plain.analytics.v2.utils.test."} which means that the lines starting with
* that prefix are the important or "relevant" lines. (Also the parameter cutTBS set to true which means that stacktrace should be
* shortened at all. In this case the result of this method should be as follows:
*
*
* java.lang.Exception: Bad error
* at com.plain.analytics.v2.utils.test.UtilsTester.TestGetStackTrace(UtilsTester.java:80)
* at com.plain.analytics.v2.utils.test.UtilsTester.run(UtilsTester.java:30)
* at com.plain.analytics.v2.utils.test.UtilsTester.main(UtilsTester.java:25)
* Caused by: java.lang.NumberFormatException: For input string: "Hello"
* at java.lang.NumberFormatException.forInputString(Unknown Source)
* at java.lang.Integer.parseInt(Unknown Source)
* at java.lang.Integer.parseInt(Unknown Source)
* at com.plain.analytics.v2.utils.test2.UtilsTesterHelper.parseInt(UtilsTesterHelper.java:8)
* at com.plain.analytics.v2.utils.test.UtilsTester$Invoker.runParser(UtilsTester.java:97)
* at com.plain.analytics.v2.utils.test2.UtilsTesterHelper.innerInvokeParser(UtilsTesterHelper.java:17)
* ...
* at com.plain.analytics.v2.utils.test.UtilsTester.TestGetStackTrace(UtilsTester.java:76)
* ... 2 more
*
*
* Note that the first singular stacktrace is printed in full because all the lines start with the required prefix. The second singular stacktrace
* prints the first 7 lines because at first all the lines are printed until the first line with relevant prefix is found, and then all the lines
* with the prefix (one in our case) are printed + plus one following line without the prefix. And then the second line without the prefix (3d
* from the bottom) is skipped and replaced with line "...". But then again we encounter a line with the prefix which is printed and finally the
* last line is printed because it is the first line without prefix following the one with the prefix. In this particular example only one line
* was skipped over which is not very much, but for web-based environments for the long stacktraces that contain long traces of server related
* classes this method could be very effective in removing irrelevant lines and leaving only application related lines making log files more
* concise and clear.
*
*
* Important Note: Parameter relevantPackage may be left null. In this case the value of relevant package prefix will be taken from
* RelevantPackage property (See the methods {@link #setRelevantPackage(String)} and {@link #getRelevantPackage()}). Using method
* {@link #setRelevantPackage(String)} to set the value will preset the value of relevant package prefix for all calls for which parameter
* relevantPackage is null. In fact there is a convinience method {@link #getStacktrace(Throwable, boolean)} that invokes this method with
* parameter relevantPackage set to null and relies on the globally set property through method {@link #setRelevantPackage(String)}.
* However if the global property was not set and parameter relevantPackage was left null then the method will return stacktrace in full as
* if the parameter cutTBS was set to false
*
* @param e {@link Throwable} from which stacktrace should be retrieved
* @param cutTBS boolean that specifies if stacktrace should be shortened. The stacktrace should be shortened if this flag is set to {@code true}.
* Note that if this parameter set to {@code false} the stacktrace will be printed in full and parameter relevantPackage becomes
* irrelevant.
* @param relevantPackage {@link String} that contains the prefix specifying which lines are relevant. It is recommended to be in the following format
* "packag_name1.[package_name2.[...]]." In the example above it should be "com.plain.analytics.v2.utils.test.".
* @return String with stacktrace value
*/
public static String getStacktrace(Throwable e, boolean cutTBS, String relevantPackage) {
StringBuilder result = new StringBuilder("\n");
// retrieve full stacktrace as byte array
ByteArrayOutputStream stacktraceContent = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(stacktraceContent));
// Determine the value of relevant package prefix
String relPack = (relevantPackage != null && !relevantPackage.isEmpty()) ? relevantPackage : RELEVANT_PACKAGE;
/*
* If the relevant package prefix was not set neither locally nor globally revert to retrieving full stacktrace even if shortening was
* requested
*/
if (relPack == null || "".equals(relPack)) {
if (cutTBS) {
cutTBS = false;
logger.warn("Relevant package was not set for the method. Stacktrace can not be shortened. Returning full stacktrace");
}
}
if (cutTBS) {
if (stacktraceContent.size() > 0) {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(new ByteArrayInputStream(stacktraceContent.toByteArray())))) {
/*
* First line from stacktrace is an actual error message and is always printed. If it happens to be null (which should never
* occur) it won't cause any problems as appending null to StringBuilder (within method traverseSingularStacktrace()) will be a
* no-op and subsequent readLine will still return null and will be detected in the method traverseSingularStacktrace()
*/
String line = reader.readLine();
do {
/*
* Process singular stacktraces until all are processed.
*/
line = traverseSingularStacktrace(result, relPack, reader, line);
} while (line != null);
} catch (IOException ioe) {
/*
* In the very unlikely event of any error just fall back on printing the full stacktrace
*/
error("Error occurred while reading and shortening stacktrace of an exception. Printing the original stacktrace", ioe);
result.delete(0, result.length()).append(new String(stacktraceContent.toByteArray()));
}
}
} else {
/*
* This is the branch that prints full stacktrace
*/
result.append(new String(stacktraceContent.toByteArray()));
}
return result.toString();
}
/**
* This method retrieves a stacktrace from {@link Throwable} as a String in full or shortened format. This is convenience method that invokes
* method {@link #getStacktrace(Throwable, boolean, String)} with last parameter as {@code null}. It relies on relevant package prefix to have
* been set by method {@link #setRelevantPackage(String)}. In case when Spring framework is used it is
* recommended to add the following bean into your Spring configuration xml file. This will ensure an invocation of method
* {@link #setRelevantPackage(String)} which will appropriately initialize the package prefix and enable the use of this method
*
*
* <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
*  <property name="targetClass" value="com.mgnt.utils.TextUtils"/>
* <property name="targetMethod" value="setRelevantPackage"/>
* <property name="arguments" value="com.plain."/>
* </bean>
*
*
* @param e {@link Throwable} from which stacktrace should be retrieved
* @param cutTBS boolean flag that specifies if stacktrace should be shortened or not. It is shortened if the flag value is {@code true}
* @return String that contains the stacktrace
* @see #getStacktrace(Throwable, boolean, String)
*/
public static String getStacktrace(Throwable e, boolean cutTBS) {
return getStacktrace(e, cutTBS, null);
}
/**
* This method retrieves a stacktrace from {@link Throwable} as a String in shortened format. This is convenience method that invokes method
* {@link #getStacktrace(Throwable, boolean, String)} with second parameter set to {@code 'true'} and last parameter as {@code null}. It relies on
* relevant package prefix to have been set by method {@link #setRelevantPackage(String)}. In case when Spring framework is used it is
* recommended to add the following bean into your Spring configuration xml file. This will ensure an invocation of method
* {@link #setRelevantPackage(String)} which will appropriately initialize the package prefix and enable the use of this method
*
*
* <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
* <property name="targetClass" value="com.mgnt.utils.TextUtils"/>
*  <property name="targetMethod" value="setRelevantPackage"/>
* <property name="arguments" value="com.plain."/>
* </bean>
*
*
* @param e {@link Throwable} from which stacktrace should be retrieved
* @return String that contains the stacktrace
* @see #getStacktrace(Throwable, boolean, String)
*/
public static String getStacktrace(Throwable e) {
return getStacktrace(e, true, null);
}
/**
* This method retrieves a stacktrace from {@link Throwable} as a String in shortened format. This is convenience method that invokes method
* {@link #getStacktrace(Throwable, boolean, String)} with second parameter set to {@code 'true'}.
*
* @param e {@link Throwable} from which stacktrace should be retrieved
* @param relevantPackage {@link String} that contains the prefix specifying which lines are relevant. It is recommended to be in the following format
* "packag_name1.[package_name2.[...]]."
* @return String that contains the stacktrace
* @see #getStacktrace(Throwable, boolean, String)
*/
public static String getStacktrace(Throwable e, String relevantPackage) {
return getStacktrace(e, true, relevantPackage);
}
/**
* This method traverses through Singular stacktrace and skips irrelevant lines from it replacing them by line "..." The resulting shortened
* stacktrace is appended into {@link StringBuilder} The stacktrace is viewed as consisting possibly of several parts. If stacktrace contains
* {@code "caused by"} or {@code "Suppressed"} section, each such section for the purposes of this utility is called "Singular stacktrace". For
* more detailed explanation see method {@link #getStacktrace(Throwable, boolean, String)}
*
* @param result {@link StringBuilder} to which the resultant stacktrace will be appended
* @param relPack {@link String} that contains relevant package prefix
* @param reader {@link BufferedReader} that contains the source from where the stacktrace may be read line by line. Current position in the reader
* is assumed to be at the beginning of the second line of the current singular stacktrace, following the line with the name of the
* exception and error message
* @param line {@link String} that contains the first line of the current singular stacktrace i.e. the line with the name of the exception and
* error message
* @return The first string of the next singular stacktrace or null if current singular stacktrace is the last one in the stacktrace
* @throws IOException if any error occurs.
* @see #getStacktrace(Throwable, boolean, String)
*/
private static String traverseSingularStacktrace(StringBuilder result, String relPack, BufferedReader reader, String line)
throws IOException {
result.append(line).append("\n");
// Flag that holds the status for the current line if it should be printed or not
boolean toBePrinted = true;
// Flag that holds information on previous line whether or not it starts with relevant prefix package
boolean relevantPackageReached = false;
// Flag that specifies if the current line starts with relevant prefix or not
boolean isCurLineRelevantPack = false;
// Flag that specifies that skipping line should be printed as next line
boolean skipLineToBePrinted = false;
// Main cycle reading lines until reaching the end of stacktrace
while ((line = reader.readLine()) != null) {
String trimmedLine = line.trim();
if (trimmedLine.startsWith(STANDARD_STAKTRACE_PREFIX)) {
/*
* This "if" branch deals with lines that are standard satacktrace lines atarting with "at "
*/
//Check if the current line starts with thge prefix (after the "at " part)
isCurLineRelevantPack = trimmedLine.substring(STANDARD_STAKTRACE_PREFIX.length()).startsWith(relPack);
if (!relevantPackageReached && isCurLineRelevantPack) {
/*
* If the current line starts with the prefix but previous line did not we change the printing status. This case deals with the
* first found line with the prefix and in this case it actually "changes" the flag "toBePrinted" from "true" to "true", but also
* it deals with the line with the prefix found after the first section of lines with the prefix was treated and was followed by
* some lines without prefix and then again the line with the prefix was found. That is why this "if" branch has to be before the
* actual printing. In this case flag "toBePrinted" is changed from "false" to "true". Also if previous line was the first line
* without prefix and we were supposed to print a skip line here we cancel that by setting flag "skipLineToBePrinted" back to
* false
*/
relevantPackageReached = true;
toBePrinted = true;
skipLineToBePrinted = false;
}
// Add (or "print" the line into result if it is considered to be relevant
if (toBePrinted) {
result.append(line).append("\n");
} else if (skipLineToBePrinted) {
result.append(SKIPPING_LINES_STRING).append("\n");
skipLineToBePrinted = false;
}
/*
* Check if the previous line was with the prefix but current one is not. If this is the case, change the value of the "toBePrinted"
* flag to false switch the value of the flag skipLineToBePrinted to true to indicate that next line that should be printed into the
* result is the skip line ("...")to indicate that some lines are skipped. Note that the current line already was added to the result
* which is by design as one "irrelevant" line is printed after a section of "relevant" lines. See documentation for method
* getStacktrace(Throwable e, boolean cutTBS, String relevantPackage) for details. Also note that here we don't actually print the
* skip line but just set the flag "skipLineToBePrinted" because we don't know if the next line in the stacktrace may be actually a
* relevant line and then the skip line won't be needed to be printed.
*/
if (relevantPackageReached && !isCurLineRelevantPack) {
relevantPackageReached = false;
toBePrinted = false;
skipLineToBePrinted = true;
}
} else {
/*
* This "else" branch deals with lines in the stacktrace that either start next singular stacktrace or are the last line in current
* singular stacktrace and it is of the form "... X more" where X is a number.
*/
if (trimmedLine.startsWith(CAUSE_STAKTRACE_PREFIX) || trimmedLine.startsWith(SUPPRESED_STAKTRACE_PREFIX)) {
/*
* If this is the first line of next singular stacktrace we break out of the current cycle and return this line for the next
* iteration which will invoke this method again
*/
break;
/*
* If it is last line in current singular stacktrace that starts with "..." then we print it if needed based on the value of the
* flag "toBePrinted" or in the case if we needed to add our line "..." instead we just print the original line as it has also the
* number of skipped lines
*/
} else if (toBePrinted || skipLineToBePrinted) {
result.append(line).append("\n");
//Just in case
skipLineToBePrinted = false;
}
}
}
return line;
}
/**
* This a getter method for global value of relevant package prefix property
*
* @return String that holds the prefix value.
*/
public static String getRelevantPackage() {
return RELEVANT_PACKAGE;
}
/**
* This is a setter method for relevant package prefix property for method {@link #getStacktrace(Throwable, boolean)} Once the value has been set
* the convenience method {@link #getStacktrace(Throwable, boolean)} could be used instead of method
* {@link #getStacktrace(Throwable, boolean, String)}
*
* @param relevantPackage {@link String} that contains the prefix specifying which lines are relevant. It is recommended to be in the following format
* "packag_name1.[package_name2.[...]]."
* @see #getStacktrace(Throwable, boolean, String)
*/
public static void setRelevantPackage(String relevantPackage) {
RELEVANT_PACKAGE = relevantPackage;
}
private static void warn(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.warn(message + getStacktrace(t));
} else {
logger.warn(message, t);
}
}
private static void error(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.error(message + getStacktrace(t));
} else {
logger.error(message, t);
}
}
private static void debug(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.debug(message + getStacktrace(t));
} else {
logger.debug(message, t);
}
}
private static void fatal(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.error(message + getStacktrace(t));
} else {
logger.error(message, t);
}
}
private static void info(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.info(message + getStacktrace(t));
} else {
logger.info(message, t);
}
}
private static void trace(String message, Throwable t) {
if (RELEVANT_PACKAGE != null && !RELEVANT_PACKAGE.isEmpty()) {
logger.trace(message + getStacktrace(t));
} else {
logger.trace(message, t);
}
}
}