Note
The version displayed here may be outdated, click on the badge to view the latest version
Try the highest version seen
mgutils
is a Java library that provides a collection of utilities designed to simplify and enhance your Java development experience. The utilities provided range from data manipulation, multithreading, logging, and more. This library is designed to be lightweight, efficient, and easy to integrate into any Java project.
The library requires Java 8 or above.
- Go to the Maven Central page of this library.
- Copy the snippet for your package manager. E.g. for Maven you can copy code looking like this:
<dependency>
<groupId>dev.b37.libs</groupId>
<artifactId>mgutils</artifactId>
<version>CURRENT_VERSION_HERE</version>
</dependency>
- Paste this fragment into your dependency list. In case of Maven, it's the
<dependencies>
section inpom.xml
.
- Create a new release on this repository. The new version will be published automatically.
- You can check out the
Actions
tab to ensure.
or
- Go to the
Actions
tab on this repository. - Choose the
Maven package
action. - Press
Run workflow
. - Check out
All workflows
to see the deployment process.
Follow this guide: https://central.sonatype.org/publish/publish-maven
Let's see the most useful classes in this library.
dev.b37.mgutils.logging.ScopedLogger
Tired of digging through messy logs to make sense of your application’s behavior? ScopedLogger is here to neatly organize your logs and give them the clarity they need! (с) ChatGPT
ScopedLogger
is a class that augments traditional logging by adding scope to logging operations. This functionality helps group related log messages together by attaching a scope name
and a unique scope ID
to each log message. This is particularly useful when tracking the flow of control in the logs, especially in cases where there are nested scopes.
It's important to note that ScopedLogger
is a decorator class on the slf4j Logger
interface. This means that ScopedLogger
is always created using a "basic"/"outer" logger and just changes its behavior. Any Logger
can be used as a base, even another ScopedLogger
(which lets you create nested ScopedLogger
s).
public class Example {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public Post fetchPost(int postId) {
Logger logger = ScopedLogger.of(log, "fetchPost:"); // 1. Creating a ScopedLogger
logger.trace("fetching postId={}", postId); // 2. Using it like any other Logger!
Response response = http.request("http://localhost:8080/posts/" + postId);
logger.trace("got the response {}", response);
Post post = response.getBody();
logger.trace("got the post {}", post);
for (Comment comment : post.getComments()) {
Logger commentLogger = ScopedLogger.of(logger, "fetchComment:", comment.getId()); // 3. Create nested `ScopedLogger`s if you wish
commentLogger.trace("fetching commentId={}", comment.getId());
Response commentResponse = http.request("http://localhost:8080/posts/" + postId);
commentLogger.trace("got the response {}", response);
commentsHelper.populateComment(comment, commentResponse.getBody());
commentLogger.trace("populated comment");
}
logger.trace("successfully fetched the post");
return post;
}
}
Calling Example#fetchPost
will output something like:
TRACE [48y9zeqq2c2d] fetchPost: fetching postId=2690201
TRACE [48y9zeqq2c2d] fetchPost: got the response Response@3769fecf
TRACE [48y9zeqq2c2d] fetchPost: got the post Post@5d244b79
TRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: fetching commentId=52
TRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: got the response Response@14cb4e66
TRACE [48y9zeqq2c2d] fetchPost: [52] fetchComment: populated comment
TRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: fetching commentId=63
TRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: got the response Response@4a7b18fd
TRACE [48y9zeqq2c2d] fetchPost: [63] fetchComment: populated comment
TRACE [48y9zeqq2c2d] fetchPost: successfully fetched the post
Second calling Example#fetchPost
will output logs with different random scopeId
:
TRACE [juo0n1nal8m5] fetchPost: fetching postId=2690201
TRACE [juo0n1nal8m5] fetchPost: got the response Response@3769fecf
...
TRACE [juo0n1nal8m5] fetchPost: [63] fetchComment: populated comment
TRACE [juo0n1nal8m5] fetchPost: successfully fetched the post
The identifiers used by ScopedLogger
are called scopeName
and scopeId
. In the case above, in logger
, the scopeName
is "fetchPost:"
and the scopeName
is "48y9zeqq2c2d"
(randomly generated).
The scopeName
typically represents a method or block of code, while the scopeId
is a unique identifier created for each new instance of a ScopedLogger
at the time of a new invocation of a block of code represented by the scope name
. When a ScopedLogger
is created from another ScopedLogger
, all the scope names and IDs are included in the log messages, which assists in tracking nested and interdependent log entries.
Note that scopeId
can be set manually, like in the comments loop in the code above. Also, null
value can be passed to the scopeId
parameter. In this case, scopeId
won't be shown. If you don't pass the scopeId
, it's randomly generated using ScopedLogger.createScopeId()
by default.
It's possible to create ScopedLogger
s another way as well. You can use ScopedLoggerFactory
to create ScopedLogger
s with the same source logger:
public class Example {
private final ScopedLogger logs = new ScopedLogger(LoggerFactory.getLogger(this.getClass()));
public Post fetchPost(int postId) {
Logger logger = logs.createLogger("fetchPost:");
logger.trace("fetching postId={}", postId);
// <...>
}
}
It works the same way, but you avoid using static methods and specifying the same logger every time.
dev.b37.mgutils.concurrent.TaskInvoker
The TaskInvoker
class is designed to execute a specific set of tasks, distributing them among threads using an ExecutorService. Tasks can be submitted for execution, but the execution doesn't start immediately. Instead, all tasks are stored and later executed when the completeAll()
method is called. This method also waits for all tasks to finish and returns the results. TaskInvoker
supports the submission of both Runnable
and Callable
tasks, with or without return values.
It can be considered as an alternative to the ExecutorService#invokeAll
method without having to create a Collection
of tasks and results explicitly.
Let's see some examples.
We'll use this doStuff
method as one that performs some long task.
String doStuff(int number) throws Exception {
// Here's a task returning some data
Thread.sleep(150);
return "Number " + number;
}
Using plain ExecutorService
:
ExecutorService executor = Executors.newFixedThreadPool(50);
List<Callable<String>> tasks = new ArrayList<>();
for (int i = 0; i < 60; i++) {
int number = i;
tasks.add(() -> doStuff(number));
}
List<Future<String>> resultFutures;
try {
resultFutures = executor.invokeAll(tasks);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
List<String> results = resultFutures.stream()
.map(stringFuture -> {
try {
return stringFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
Using TaskInvoker
:
ExecutorService executor = Executors.newFixedThreadPool(50);
TaskInvoker<String> invoker = new TaskInvoker<>(executor);
for (int i = 0; i < 60; i++) {
int number = i;
invoker.submit(() -> doStuff(number));
}
List<String> results = invoker.completeAll();
Also, tasks in TaskInvoker
can be cancelled using the cancelAll
method. Here's an example:
ExecutorService executor = Executors.newFixedThreadPool(5);
TaskInvoker<Void> invoker = new TaskInvoker<>(executor);
Queue<String> results = new ConcurrentLinkedQueue<>();
AtomicInteger counter = new AtomicInteger(0); // just to know when to cancel the tasks
final int MAX_COUNT = 100;
for (int i = 0; i < 100; i++) {
int number = i;
invoker.submit(() -> {
if (counter.getAndIncrement() == 6) {
invoker.cancelAll(); // cancelling the remaining tasks
}
results.add(doStuff(number));
});
}
try {
invoker.completeAll();
} catch (CancellationException e) {
// an exception will be thrown
}
// the results collection contains less than 100 items
As soon as completeAll()
is called, the remaining tasks are immediately marked as cancelled and an attempt to execute them by TaskInvoker
will lead to CancellationException
.
Since completeAll()
throws an exception when the tasks are cancelled, we have to collect the results manually, if needed. Note that it's also possible to pass a function without a returning value to invoker.submit(...)
, that is not possible with executor.invokeAll
(Callable<Void>
still requires returning null
).
dev.b37.mgutils.collections.MapBuilder
Created as an alternative for Java Map.ofEntries
which is not available in Java 8. But unlike Map.ofEntries
, MapBuilder
is designed for creating mutable Map
s as well as populating existing ones.
There is also an alternative to Java Map.entry
- MapBuilder.entry
, which is also unavailable in Java 8. It creates an instance of Map.Entry
using a custom implementation. Unlike Map.entry
, MapBuilder.entry
allows using null
keys and values.
Here's an example:
public void testMapBuilder() {
// creating new map
ConcurrentHashMap<String, Object> response = MapBuilder.create(ConcurrentHashMap::new, // specifying custom map implementation
MapBuilder.entry("code", 200),
MapBuilder.entry("status", "OK"),
MapBuilder.entry("data", MapBuilder.create( // using the default (HashMap) implementation
MapBuilder.entry("person", MapBuilder.create(LinkedHashMap::new, // specifying custom map implementation
MapBuilder.entry("id", 42125124),
MapBuilder.entry("name", "John")
))
))
);
// populating existing map, returns the same map
// `response` is modified
// `sameMap` and `response` refer to the same object
ConcurrentMap<String, Object> sameMap = MapBuilder.populate(response,
Map.entry("version", "1.4.2"),
Map.entry("hasData", response.get("data") != null));
// using alternative syntax
Map<String, Object> response2 = new MapBuilder<String, Object>(ConcurrentHashMap::new)
.put("code", 200)
.put("data", new MapBuilder<>()
.put("personId", 42125124)
.build())
.build();
}
dev.b37.mgutils.concurrent.execution.cached.CachedInvoker
no description yet