Open Source Java library with stacktrace filtering, Silent String parsing and Version comparison

Over some time in different jobs, I came across a need for several utilities that I couldn't find available at the time. And I saw that I needed them several times over and over again. So I wrote my own small library that I found very useful. So I just published it as an open-source java library.

Here is the Github link

Javadoc online is available here

Also, this library is available on Maven Central. Here are the Maven artifacts (the version 1.5.0.8 is the latest at the time of writing of this article but might change in the future. To check for the latest version search for artifact «MgntUtils» at http://search.maven.org/):

<dependency>
     <groupId>com.github.michaelgantman</groupId>
     <artifactId>MgntUtils</artifactId>
     <version>1.5.0.8</version>
</dependency>

 <dependency>
     <groupId>com.github.michaelgantman</groupId>
     <artifactId>MgntUtils</artifactId>
     <version>1.5.0.8</version>
     <classifier>javadoc</classifier>
</dependency>

<dependency>
     <groupId>com.github.michaelgantman</groupId>
     <artifactId>MgntUtils</artifactId>
     <version>1.5.0.8</version>
     <classifier>sources</classifier>
</dependency>

Below is just a short explanation of what is there. The library comes with a nicely written (I hope) JavaDoc with a detailed description. So here is the list of features:

Stacktrace noise filter


In my experience, this feature was the most generic and useful for me. Stacktrace is a lifesaver when debugging or trying to figure out what went wrong in your application. However, when working with logs on the server side you can come across huge stacktrace that contains the longest useless tail of various frameworks and Application Server related packages. And somewhere in this pile, there are several lines of a relevant trace and they may be in different segments separated by useless information. It becomes a nightmare to search for a relevant stuff. Here is a link that describes the same problem with real-life examples (not for the fainthearted ) https://dzone.com/articles/filtering-stack-trace-hell. So In my library, There is a class is called TextUtils and it has method getStacktrace() with several overloaded signatures. It takes a Throwable instance and allows to set a package prefix of the packages that are relevant.

Also, the same utility (starting from version 1.5.0.3) has method getStacktrace() that takes CharSequence interface instead of Throwable and thus allows to filter and shorten stacktrace stored as a string the same way as a stacktrace extracted from Throwable. So, essentially stacktraces could be filtered «on the fly» at run time or later on from any text source such as a log. (Just to clarify — the utility does not support parsing and modifying the entire log file. It supports filtering just a stacktrace that as passed as a String. So if anyone wants to filter exceptions in the log file they would have to parse the log file and extract stacktrace(s) as separate strings and then can use this utility to filter each individual stacktrace).

Here is a usage example. Let's say your company's code always resides in packages that start with «com.plain.*» So you set such a prefix and do this

logger.info(TextUtils.getStacktrace(e,true,"com.plain."));

this will filter out very smartly all the useless parts of the trace leaving you with very concise stacktrace. Also, I found it very convenient to pre-set the prefix and then just use the convenience method

TextUtils.getStacktrace(e);

It will do the same. To preset the prefix just use method

setRelevantPackage("com.plain.");

If you would like to pre-set this value by configuration then starting with the library version 1.1.0.1 you can set Environment Variable "MGNT_RELEVANT_PACKAGE" or System Property "mgnt.relevant.package" to value "com.plain." and the property will be set to that value without you invoking method TextUtils.setRelevantPackage(«com.plain.»); explicitly in your code. Note that System property value would take precedence over the environment variable if both were set. Just a reminder that with System property you can add it in your command line using -D flag:

"-Dmgnt.relevant.package=com.plain."

Also, if you use a version earlier then 1.1.0.1 of this library or don't want to use Environment variable or system property mentioned above and you are using Spring environment you can preset the prefix by adding the following segment to your Spring configuration:

<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>

Javadoc explains everything in detail. But here is a little teaser: you will get a following stacktrace:

at com.plain.BookService.listBooks()
at com.plain.BookService$$FastClassByCGLIB$$e7645040.invoke()
at net.sf.cglib.proxy.MethodProxy.invoke()
...
at com.plain.LoggingAspect.logging()
at sun.reflect.NativeMethodAccessorImpl.invoke0()
...
at com.plain.BookService$$EnhancerByCGLIB$$7cb147e4.listBooks()
at com.plain.web.BookController.listBooks()

instead of

at com.plain.BookService.listBooks()
at com.plain.BookService$$FastClassByCGLIB$$e7645040.invoke()
at net.sf.cglib.proxy.MethodProxy.invoke()
at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed()
at com.plain.LoggingAspect.logging()
at sun.reflect.NativeMethodAccessorImpl.invoke0()
at sun.reflect.NativeMethodAccessorImpl.invoke()
at sun.reflect.DelegatingMethodAccessorImpl.invoke()
at java.lang.reflect.Method.invoke()
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs()
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod()
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.interceptor.AbstractTraceInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke()
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed()
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept()
at com.plain.BookService$$EnhancerByCGLIB$$7cb147e4.listBooks()
at com.plain.web.BookController.listBooks()

Silent String parsing into Integer, Long, etc


The same class TextUtils provides methods to parse String silently (without exception ever being thrown) into types of Double, Long, Integer, Short and Byte. The methods are called parseStringToLong() parseStringToInteger() etc. They all take 4 arguments:

  1. A String to be parsed
  2. An implementation of Number (Double, Long, Integer, Short or Byte) to serve as default value if any parsing problem occurred
  3. A String to be printed as an error message into the log if the first argument is null (may be null, and then no error is printed)
  4. A String to be printed as an error message if NumberFormatException occurred (may be null, and then no error is printed)

Parsing String to Time Interval


In the same class TextUtils, there is a method parseStringToTimeInterval(String value). This method parses a string that is expected to hold some time interval value — a numeric value with optional time unit suffix. For example, string «38s» will be parsed as 38 seconds, «24m» — 24 minutes «4h» — 4 hours, «3d» — 3 days and «45» as 45 milliseconds. Supported suffixes are "s" for seconds, "m" for minutes, "h" for hours and "d" for days. String without a suffix is considered to hold a value in milliseconds. Suffixes are case insensitive. If provided String contains an unsupported suffix or holds negative numeric value or zero or holds a non-numeric value — then IllegalArgumentException is thrown. This method returns TimeInterval class — a class also defined in this library. Essentially, it holds two properties with relevant getters and setters: long «value» and java.util.concurrent.TimeUnit. But in addition to getters and setters this class has methods toMillis(), toSeconds(), toMinutes(), toHours() toDays(). Those methods return long value in specified time scale (The same way as corresponding methods in class java.util.concurrent.TimeUnit)

This method may be very useful for parsing time interval properties such as timeouts or waiting periods from configuration files. It eliminates unneeded calculations from different time scales to milliseconds back and forth. Consider that you have a methodInvokingInterval property that you need to set for 5 days. So in order to set the milliseconds value, you will need to calculate that 5 days is 432000000 milliseconds (obviously not an impossible task but annoying and error-prone) and then anyone else who sees the value 432000000 will have to calculate it back to 5 days which is frustrating. But using this method you will have a property value set to «5d» and invoking the code

long milliseconds = TextUtils.parsingStringToTimeInterval("5d").toMillis();

will solve your conversion problem. Obviously, this is not an overly complex feature, but it could add simplicity and clarity in your configuration files and save some frustration and «stupid» miscalculation into milliseconds bugs.

Compare Versions


This utility allows to convert String to version and vise-versa and to compare versions and version ranges properly. Often if you need to compare versions you just compare Strings. So lets say version «1.5.3» will be greater than version «1.3.1». However, if you will compare Strings «1.4.2» and «1.12.3» the String «1.4.2» will erroneously be greater. So this Utility takes care of such a problem and in addition, introduces VersionRange class and allows operations on version ranges. (See methods compareVersions in class TextUtils and class VersionRange)

String Unicode converter


Class StringUnicodeEncoderDecoder has methods that can convert a String (in any language) into a sequence of Unicode characters and vise-versa. For example, a string «Hello World» will be converted into

"\u0048\u0065\u006c\u006c\u006f\u0020\u0057\u006f\u0072\u006c\u0064"

and may be restored back.

Lifecycle management (Self-instantiating factories)


This feature is a package that contains some small infrastructure that simplifies and automates working with Factories that provide concrete implementations of an Interface. The package contains just 2 classes: BaseEntityFactory and BaseEntity. In short, what this infrastructure does is that if you create a factory that extends BaseEntityFactory and some Interface with all its concrete implementations extending BaseEntity then each of your concrete implementation class instances will be automatically inserted into your factory. You won't have to worry about how and when to populate your factory. The infrastructure will do it for you when the constructor of your concrete implementation class is invoked. So all you will have to do is to create any number of concrete implementation classes and make sure that for each one constructor is invoked. After that, you can use your factory to get any of your concrete implementation classes anywhere in your code. This is a short explanation. There are a few more details but not that many. For a detailed description of this package please refer to Javadoc API of package com.mgnt.lifecycle.management description. Also, the source code contains package com.mgnt.lifecycle.management.example that holds well commented and detailed code example on how to use this feature.

As for practical usage, I found it very useful for the Spring framework. Remember that Spring instantiates all it's defined beans during its initialization. So within the Spring context, if we simply declare our concrete implementations as Spring beans, Spring would instantiate them for us, thus initializing their factory automatically. This could be very convenient. Imagine that you have some bean that has a property of the type of user-defined Interface that has several implementations, but which actual implementation would be needed is determined at runtime. So at that time, you can use method getInstance(java.lang.String) of your Factory to access needed implementation. This will allow you not to inject ALL your concrete instantiations into your bean and you won't have to use Spring BeanFactory to access a Spring defined bean as that would violate non-intrusiveness of Spring (meaning you can write components which have no dependency on Spring). Also, If at some later stage you will need to add more concrete implementations, all you will have to do is to add your implementation classes to your code and declare them to be Spring beans. The rest will be done by Spring and this infrastructure!

File Utils


This utility allows to read a file as a String with or without specifying character set or just read it as a byte array. This utility uses nio package and Buffers for faster performance. Max file size for this Utility is 2G.

Time Util


This is just a single convenience method that provides the same functionality as Thread.sleep() but it is silent. I.e. it «swallows» IterruptedException. Also, it takes 2 arguments: time to sleep and measurement unit. So instead of

try {
     Thread.sleep(2000);
} catch (InterruptedException ie) {
}

You can just write:

TimeUtils.sleepFor(2, TimeUnit.SECONDS);

I thought it was cute

So this is it. Feel free to download this library. It comes with MIT license — one of the most permissive licenses I know of. Source code, Javadoc and binary version is available at the link above. The code was compiled by JDK 8 but has no features of version 8 in it. It should work with java 7 most definitely, I suspect it will work with java 6 and even 5, but haven't tested it with those versions. Feel free to use it as you see fit, tell others about it if you like it and feel free to drop me a note at michael_gantman@yahoo.com if you have any feedback.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 0

Only users with full accounts can post comments. Log in, please.