NAF
Nuts Application Framework
The core API foundation for modular and dynamic Java applications

Genesis
Nuts Application Framework (NAF) is a lightweight, modular framework designed to simplify the development of modern Java applications. Built on top of the Nuts runtime, NAF provides a unified foundation for configuration, logging, dependency injection, modularity, and extensibility, all while remaining Java 8 compatible and able to leverage the latest Java features when available.
NAF is non-invasive by design: it
integrates seamlessly with existing frameworks and libraries such as
Spring, Spring Boot,
SLF4J, and more, allowing you to adopt its
conventions and APIs without rewriting or restructuring your
application. Whether you're building CLI tools, desktop
applications, or services, NAF
emphasizes clarity,
productivity, and maintainability, giving you a consistent
foundation and powerful conventions without imposing complexity or
locking you in.
Maven Integration
NAF can be added to your project with one single JAR dependency, fully compatible with Maven repositories. Once included, it brings runtime dependency resolution, artifact management, and dynamic execution to your application without additional setup. Just add the dependency, and NAF takes care of the rest — from fetching artifacts to managing versions, all seamlessly integrated with your existing Maven workflow.
<dependencies>
<dependency><groupId>net.thevpc.nuts</groupId>
<artifactId>nuts</artifactId>
<version>0.8.6</version>
</dependency>
</dependencies>
<repositories>
<repository><id>thevpc</id><url>https://maven.thevpc.net</url></repository>
</repositories>
Easy Onboarding
One line to start. Full runtime at your fingertips. Get started instantly with NAF — no setup, no configuration, just run. Behind that single line lies a powerful, fully-featured runtime: dependency management, sessions, workspaces, and automation tools ready for anything your application needs. NAF makes modern Java development simple, composable, and ready to scale.
Nuts.require();
NOut.println("Hello Nuts!");
NMsg in a nutshell
NMsg
allows you to create structured messages with
dynamic placeholders, automatically rendering
common types such as booleans,
numbers, paths,
dates, and enums in colors for
improved readability. You can also apply custom styles to
placeholders or entire messages, and even nest messages within
messages to build complex, rich, and expressive content. This makes
NMsg
a versatile tool for clear, context-aware, and
visually informative output in any application.
// Simple example with automatic coloring
NMsg.ofC("User %s set flag %s on path %s",
"Alice", true, NPath.of("/tmp/data.txt")
);
// Custom styling example
NMsg.ofC("Task %s completed with status %s",
"Upload",
NText.ofStyled("OK", NTextStyle.primary1())
);
// Nested messages example
NMsg.ofV("User $user completed ${task}",
NMaps.of(
"user", "Alice",
"task", NMsg.ofV("task %s in %s", "Upload",
NText.ofStyled("123ms", NTextStyle.secondary1()))
)
);
Placeholder Formats
NMsg
supports multiple placeholder formats to create
flexible and dynamic messages. You can use C-style placeholders
(%s
, %d
) for traditional printf-style
formatting, Java-style placeholders ({}
) similar to
MessageFormat or SLF4J, and variable substitution ($name
)
for named placeholders. The variable substitution format can take
parameters, maps, or functions, making it easy to build messages
dynamically and adaptively for your application.
NMsg.ofC("Hello %s, you have %d new notifications", "Alice", 5);
NMsg.ofJ("Downloading {} from {}", "report.pdf", "server-01");
NMsg.ofV("User $user on ${app}", Map.of("user", "Alice", "app", "NAF"));
NMsg.ofV("Threshold=$th, Date=$date", name -> switch (name) {
case "th" -> 0.85;
case "date" -> LocalDate.now();
default -> null;
});
Text Rendering Formats
NMsg
NMsg also provides multiple text rendering formats
to make messages visually expressive. You can use plain messages
that print as-is, styled messages to highlight errors, warnings, or
other emphasis, code blocks for monospaced text with optional
language hints, and NTF (Nuts Text Format) for lightweight markup
supporting bold, italic, colors, and other rich formatting. These
formats, combined with placeholders, allow NMsg to produce rich,
dynamic, and visually informative output in any context.
NMsg.ofPlain("This is a plain message");
NMsg.ofStyled("Error: file not found", NTextStyle.error());
NMsg.ofCode("java", "System.out.println(\"Hello NAF\");");
NMsg.ofNtf("##bold## ##:/:italic## ```java public class{} ```");
Universal Path
NPath gives you a unified way to work with both local and remote paths, letting you read, write, and manipulate files from HTTP, SSH, or the local filesystem with the same simple API. It also provides convenient access to standard system and user directories, including home, working, configuration, and application paths, making cross-platform file handling seamless and consistent across environments.
// manipulate local and remote paths
String content=NPath.of("http://myserver/myfile.txt").readString();
String[] lines=NPath.of("ssh://myserver/myfile.txt").lines().toArray(String[]::new);
// access local standard folders
NPath.ofUserHome(); // User home directory
NPath.ofUserDirectory(); // Current working directory
// User store path (~/.config/nuts on linux, C:\Users\{user}\AppData\Local\nuts on windows)
NPath.ofUserStore(NStoreType.CONFIG);
// User store path (/opt/nuts/ on linux, C:\Program Files\nuts on windows)
NPath.ofSystemStore(NStoreType.BIN); // System store path
Commandline
NCmdLine lets you parse and handle command-line arguments with full flexibility, while keeping your code clean and readable. Define flags, options, and non-option arguments, all with automatic type handling and default values. With NAF, building sophisticated CLI tools becomes straightforward, letting you focus on functionality instead of parsing logic.
NCmdLine cmdLine = NApp.of().getCmdLine(); // or from somewhere else
NRef<Boolean> boolOption = NRef.of(false);
NRef<String> stringOption = NRef.ofNull();
List<String> others = new ArrayList<>();
while (cmdLine.hasNext()) {
cmdLine.matcher()
.with("-o", "--option").matchFlag((v) -> boolOption.set(v.booleanValue()))
.with("-n", "--name").matchEntry((v) -> stringOption.set(v.stringValue()))
.withNonOption().matchAny((v) -> stringOption.set(v.image()))
.requireDefaults()
;
}
NOut.println(NMsg.ofC("boolOption=%s stringOption=%s others=%s", boolOption, stringOption, others));
Structured Console Output with NTF
NAF
supports NTF
(Nuts Text
Format), enabling rich, structured, and styled console
output. You can embed variables, apply colors or styles, and produce
messages that are both human-readable and machine-friendly. This
makes your CLI output more expressive, interactive, and easier to
interpret.
NOut.println(
NMsg.ofV("Hello ${user}, your task status is ${status}",
NMaps.of(
"user", "Alice",
"status", NText.ofStyled("OK", NTextStyle.primary1())
)
);
NOut.println(
NMsg.ofC("Hello %s, your task status is %s",
"Bob"
NText.ofStyled("KO", NTextStyle.danger())
)
);
NOut.println(
NMsg.ofC("Hello %s, your task status is ##:danger KO##",
"Meriam"
"KO"
)
);
Unified Logging with NLog
NLog
combines structured messages (NMsg
),
styled output with colors and NTF
formatting, and
seamless integration with JUL or
SLF4J. Messages carry an Intent,
similar to logging markers, which allows you to classify, filter, or
style logs at runtime. Whether you’re building CLI tools, scripts,
or full applications, NLog
makes logs expressive, rich,
and easy to understand.
NLog.of("myapp").info(
NMsg.ofV("Hello %s, task %s completed with status %s",
"user","Alice","status",
NText.ofStyled("OK", NTextStyle.primary1()), exception
)
.withIntent(NMsgIntent.CONFIG) // Classifies this log, can be used for filtering or styling
.withThrowable(exception) // Attaches the exception
.withDurationMs(123) // Optional: report task duration
);
Scoped, Context-Aware Logging (Beyond MDC)
Traditional MDC allows you to store key-value pairs for the current thread, but managing nested or dynamic contexts often becomes cumbersome. NLog builds on and extends this concept by allowing nested logging scopes, dynamic placeholders, message prefixes, and custom log handlers — all without touching global configuration or rewriting classes. Logs automatically inherit the correct context, making them structured, expressive, and runtime-aware. With NLog, you get the convenience of MDC plus the power of non-invasive, modular, and scoped logging.
class MyApp {
public void test() {
NLogs.of().runWith(
NLogContext.of()
.withMessagePrefix(NMsg.ofC("[My Application]")) // Prefix added to every log message in this scope
.withPlaceholder("user", "Adam") // Placeholder available to all logs in this scope
.withLog(message -> NOut.println(NMsg.ofC("[SCOPED] %s", message))), // Custom log handler: redirect all messages to stdout with a [SCOPED] prefix
() -> {
OtherClass.doThis(); // Perform actions that generate logs
}
);
}
}
class OtherClass {
private static void doThis() {
// Standard logger: picks up class context but not the scoped stdout handler
NLog.of(LogTest.class).log(NMsg.ofC("hello %s", NMsg.placeholder("user")));
// Scoped logger: inherits context from outer scope (user=Adam) and uses the custom stdout handler
NLog.ofScoped(OtherClass.class).log(NMsg.ofV("hello $user"));
}
}
NElement — Structured Data, Any Format
With NElement
, Nuts
lets you build, parse,
and format structured data effortlessly. From plain objects to
JSON, XML, or
TSON, you can read and write files, parse into Java
objects, or print with optional NTF
color formatting —
all in a runtime-friendly way.
// Build an element
NElement document = NElement.ofObjectBuilder()
.set("app-id", NApp.of().getId().get())
.set("error", messageString)
.build();
// Parse JSON into Java object
Person person = NElementParser.ofJson().parse(NPath.of("person.json"), Person.class);
// Format and write XML to file
NElementFormat.ofPlainXml(document).println(NPath.of("person.xml"));
// Print TSON to terminal with colors
NElementFormat.ofNtfTson(document).println();
NTextArt — Turn Text into Art
Bring your CLI, logs, or console output to life with NTextArt. Render text as classic ASCII banners, pixel-style visuals, or even image-like representations, with multiple renderers at your fingertips — all workspace-aware and fully embeddable.
NTextArt art = NTextArt.of();
NText text = NText.of("hello world");
NOut.println(art.getTextRenderer("figlet:standard").get().render(text));
NOut.println(art.getImageRenderer("pixel:standard").get()
.setFontSize(20) .setOutputColumns(60) .render(text));
// _ _ _ _ _
// | | | | | | | | | |
// | |__ ___ | | | | ___ __ __ ___ _ __ | | __| |
// | '_ \ / _ \ | | | | / _ \ \ \ /\ / / / _ \ | '__| | | / _` |
// | | | | | __/ | | | | | (_) | \ V V / | (_) | | | | | | (_| |
// |_| |_| \___| |_| |_| \___/ \_/\_/ \___/ |_| |_| \__,_|
//
// █ ░██ ███ ███ █
// █ █ ▓█ ░█ █
// █ █▒ ░██ █ ▓█ █▒ █ █ █▓ ██░█░ ░█ ▒█ █
// █▓▓█░░█ █░ █ ▓█ ░█ ▓▒ █ █ █░█ ▓▓ ██▓▓ ░█ █░▒█
// █ █░█████ █ ▓█ ▓█ ▒█ █▓█ ▓▒█ ░█ ██ ░█ ░█ █
// █ █░ █ ██ ▓█ █ █░ ██░█▓ █ █▒ ██ ░█░ ░█░██
// ▓ ▓░ ░▓▓ ▓▓ ▓▓░ ▓░ ░▓ ▓ ▓▒ ▒▒ ░▓▓ ░▓ ▓
NTextArt — Turn Text into Art
Beyond ASCII banners and pixel-style visuals, NTextArt
also supports structured data rendering. You can display tables with
aligned rows and columns, and hierarchical trees where each node can
contain multi-line or styled text — even full tables. Branches,
indentation, and node formatting are handled automatically, making
it easy to visualize complex hierarchical or tabular data directly
in the terminal with a clean, readable layout.
class MyNode implements NTreeNode {
int value;
public MyNode(int value) { this.value = value;}
@Override
public NText value() {
return art.getTableRenderer().get().render(NTableModel.of().addRow(NText.of(value)));
}
@Override
public List<NTreeNode> children() {
return (value < 3) ? Arrays.<Integer>asList(value + 1, value + 2).stream().map(MyNode::new).collect(Collectors.toList())
: Collections.emptyList();
}
}
NTreeNode tree = new MyNode(1);
NOut.println(art.getTreeRenderer().get().render(tree));
// ╭─╮
// │1│
// ╰─╯
// ├── ╭─╮
// │ │2│
// │ ╰─╯
// │ ├── ╭─╮
// │ │ │3│
// │ │ ╰─╯
// │ └── ╭─╮
// │ │4│
// │ ╰─╯
// └── ╭─╮
// │3│
// ╰─╯
NExec & NPs — Unified Process Execution
NAF offers a complete abstraction for process execution and monitoring. With NExec, you can run system commands, remote jobs, or even Maven artifacts seamlessly. With NPs, you can introspect and control running processes across platforms. Together, they form a portable, workspace-aware execution toolkit that abstracts OS differences, integrates with Nuts’ runtime, and makes both starting and supervising processes trivial — whether locally or remotely.
// Run a system command and capture output
String out = NExec.of("ls").system().grabAll().run().getGrabbedOutString();
// Run a Maven artifact (auto-resolves if missing)
NExec.of("netbeans-launcher").run();
// List all Java processes on the local machine
NPs.of().setPlatformFamily(NPlatformFamily.JAVA)
.getResultList()
.forEach(NOut::println);
// Kill a process by ID (if supported on the platform)
NPs.of().killProcess("12345");
// Inspect processes on a remote host
NPs.of().at("ssh://myuser@myserver").getResultList();
NCp — Copy with Options & Validation
NCp provides a unified, flexible API to copy resources of various types — files, streams, URLs, or paths — while supporting advanced features like logging, progress monitoring, and validation. You can start with a simple copy, then gradually enable tracing, progress feedback, or integrity checks, all with the same intuitive API.
// --- Basic copy: simple source to target ---
NCp.of()
.from(source) // set source (File, Path, InputStream, etc.)
.to(download_path.resolve(source.getName())) // set target path
.addOptions(NPathOption.LOG, NPathOption.TRACE) // enable logging and tracing
.run(); // execute copy
// --- Copy with progress monitoring ---
NCp.of()
.from(from)
.to(to)
.addOptions(NPathOption.LOG, NPathOption.TRACE)
// --- add validation validation (e.g., SHA-1 integrity check) ---
.setValidator(new NCpValidator() {
@Override
public void validate(InputStream in) throws IOException {
checkSHA1Hash(id.builder().setFace(NConstants.QueryFaces.CONTENT_HASH).build(),
in, "artifact binaries");
}
})
.setProgressMonitor(event -> {
NOut.println(event.getProgress()); // display progress
return true; // continue copy
})
.run();
NCompress / NUncompress — Flexible Compression & Extraction
NAF provides unified APIs for compressing and uncompressing files, directories, or streams, supporting multiple formats (e.g., ZIP). You can monitor progress, skip root directories, or apply custom options for logging and safety. NCompress and NUncompress make it easy to handle resource packaging in a consistent, OS-aligned manner, while fully integrating with Nuts’ filesystem abstractions.
// --- Compress a file or directory into a ZIP ---
NCompress.of()
.addSource(example) // source to compress
.setTarget(example.resolveSibling(
example.getNameParts(NPathExtensionType.SHORT).getBaseName() + ".zip")) // target zip path
.setPackaging("zip") // compression format
.run(); // execute compression
// --- Uncompress a ZIP file to a folder ---
NUncompress.of()
.from(zipTo) // source zip file
.to(folderTo) // target directory
.setSkipRoot(true) // optionally skip the root folder in zip
.progressMonitor(new OpNInputStreamProgressMonitor(
module.rt().addOperation("Unzipping " + i))) // progress monitoring
.run(); // execute uncompression
NDigest – Hashing Made Simple
NDigest is a fluent I/O command to compute hash digests of one or multiple sources. It supports files, streams, URLs, byte arrays, or Nuts descriptors, and allows combining multiple sources into a single digest. Built on top of Java's MessageDigest, it simplifies hashing in Nuts workflows, integrates seamlessly with download validation, and supports MD5, SHA1, SHA256, or any algorithm provided by MessageDigest.
// Simple SHA256 digest of a file
String hash = NDigest.of()
.sha256()
.addSource(path)
.computeString();
// Digest multiple sources
String combined = NDigest.of()
.sha1()
.addSource(file1)
.addSource(file2)
.addSource(url)
.computeString();
// Digest and get raw bytes
byte[] bytes = NDigest.of()
.md5()
.addSource(stream)
.computeBytes();
NInputSource & NInputSourceBuilder – Flexible Input Abstraction
NInputSource represents a flexible, metadata-aware source of content in NAF. It can wrap files, paths, URLs, byte arrays, streams, readers, or other providers, offering a unified API for reading, digesting, or streaming content. NInputSourceBuilder allows fine-grained configuration of sources, such as expected length, progress monitoring, interruptibility, multi-read capability, or non-blocking streams. Together, they decouple I/O handling from concrete types and simplify building robust I/O pipelines.
NInputSource src = NInputSource.of(file)
.readBytes(); // get content
NInputSource multi = NInputSource.ofMultiRead(src); // reusable input
NInputSourceBuilder b = NInputSourceBuilder.of(stream)
.setMetadata(metadata)
.setInterruptible(true)
.setCloseBase(true);
InputStream in = b.createInputStream();
NInputSource source = b.createInputSource();
Progress Monitoring with NProgressMonitor
NProgressMonitor provides a flexible and context-aware way to track progress in tasks. You can create incremental, split, and nested monitors, log events to any output, and automatically propagate the current progress context using NProgressMonitor.of(). This allows embedded or nested operations to report progress seamlessly without explicit parameter passing.
// 1. Simple incremental progress
NProgressMonitor mon = NProgressMonitors.of().ofSysOut();
mon.start();
for (int i = 0; i < 10; i++) {
mon.setProgress(i * 1.0 / 10, NMsg.ofC("Step %.1f", i * 1.0 / 10));
}
mon.complete();
// 2. Split progress for subtasks
NProgressMonitor[] split = mon.split(2);
split[0].incremental(5).complete();
split[1].incremental(5).complete();
// 3. Context-aware progress
NProgressHandler handler = event -> NOut.println(event);
NProgressMonitors.of().of(handler).runWith(() -> {
NProgressMonitor inner = NProgressMonitor.of();
inner.start();
inner.setProgress(0.5, NMsg.ofC("Halfway done"));
inner.complete();
});
Rate Limiting with NRateLimitValue
NRateLimitValue
lets you control how often actions can
be executed.
For example, you can allow only 10 actions every 2 minutes,
using a strategy like sliding window.
This makes it easy to protect APIs, services, or expensive
operations from overuse.
// Example 1: Basic sliding window rate limit
NRateLimitedValue limiter = NRateLimitedValue.ofBuilder("example")
.withLimit("calls", 10).per(Duration.ofMinutes(2))
.withStrategy(NRateLimitDefaultStrategy.SLIDING_WINDOW)
.build();
for (int i = 0; i < 15; i++) {
NRateLimitValueResult res = limiter.take();
if (res.success()) {
NOut.println("Action " + i + " allowed at " + Instant.now());
} else {
NOut.println("Action " + i + " rejected. Retry after "
+ res.getRetryAfter().orElse(Duration.ZERO));
}
}
Running Limited Actions with claimAndRun
Instead of consuming tokens immediately, claimAndRun
will wait until the limiter can provide a slot before executing your code. This is useful when you want the action to eventually run rather than fail.
You can also define multiple independent limits (per minute, per day, etc.) in a single limiter, and claimAndRun
will respect all limits before executing.
/// Example 2: Using claimAndRun
limiter.claimAndRun(() -> {
NOut.println("Doing a limited action...");
});
// Adding multiple limits at once
NRateLimitedValue limiter2 = NRateLimitedValue.ofBuilder("api-calls")
.withLimit("per-minute", 60).per(Duration.ofMinutes(1))
.withLimit("per-day", 1000).per(Duration.ofDays(1))
.build();
limiter2.claimAndRun(() -> {
NOut.println("API call allowed");
});
Lazy, One-Time Evaluation with NStableValue
NStableValue
stores a value that is computed lazily — the supplier is not
invoked until the first call to get()
. Once evaluated, the value remains stable
and is reused for all subsequent accesses. This is perfect for expensive computations,
constants, or resources that should only be initialized once.
// Example 1: Lazy initialization
NStableValue<Double> stableRandom = NStableValue.of(Math::random);
// Value is computed on first access
NOut.println("First value = " + stableRandom.get());
// Subsequent accesses return the same value
NOut.println("Same value = " + stableRandom.get());
// Check evaluation status
NOut.println("Evaluated? " + stableRandom.isEvaluated());
NOut.println("Valid? " + stableRandom.isValid());
Caching with NCachedValue
NCachedValue
helps you cache expensive computations or
resources.
It evaluates a Supplier
once, stores the result, and
reuses it until
the cache expires or is invalidated.
You can configure expiry policies to automatically refresh values.
// Example 1: Cache with expiry
NCachedValue<Double> cachedRandom = NCachedValue.of(Math::random)
.setExpiry(Duration.ofSeconds(5));
// First call computes and caches the value
NOut.println("First value = " + cachedRandom.get());
// Subsequent calls reuse the cached value (within 5 seconds)
NOut.println("Cached value = " + cachedRandom.get());
// Invalidate to force recomputation
cachedRandom.invalidate();
NOut.println("New value after invalidate = " + cachedRandom.get());
Resilient Caching with Retries
Sometimes a computation may fail (for example, a remote call).
NCachedValue
can automatically retry, retain the last good value
on failure, and recover gracefully.
This makes it ideal for unstable resources or intermittent network services.
// Example 2: Cache with retries and fallback
AtomicInteger counter = new AtomicInteger();
NCachedValue<Integer> cached = NCachedValue.of(() -> {
int attempt = counter.incrementAndGet();
if (attempt % 2 == 0) {
throw new RuntimeException("Simulated failure");
}
return attempt;
})
.setRetry(3, Duration.ofMillis(100)) // retry up to 3 times
.retainLastOnFailure(true); // keep last value if failure occurs
// First call computes and caches
NOut.println("Value = " + cached.get());
// Next call may fail internally but still returns last good value
NOut.println("Resilient value = " + cached.get());
Expression Parsing & Evaluation with NExpr
NExpr is a powerful expression parser and evaluator designed to handle dynamic, runtime expressions within the Nuts ecosystem. It allows declaration of constants, variables, and functions, supports custom operators, and can evaluate expressions in a context-aware way. This makes it ideal for dynamic queries, runtime computation, scripting, and configuration-driven logic in Java applications — all while remaining type-safe, extensible, and embeddable.
NExprMutableDeclarations d = NExprs.of().newMutableDeclarations();
d.declareConstant("pi", Math.PI);
d.declareFunction("sin", (name, args, ctx) -> {
NExprNodeValue a = args.get(0);
return Math.sin(asDouble(a.eval(ctx), rendererContext));
});
NOptional<NExprNode> ne = d.parse("sin(x*pi)");
if (ne.isPresent()) {
NExprNode node = ne.get();
NDoubleFunction fct = x -> {
NOptional<Object> r = node.eval(new NExprEvaluator() {
@Override
public NOptional<NExprVar> getVar(String varName, NExprDeclarations ctx) {
return "x".equals(varName)
? Optional.of(x)
: NOptional.ofNamedEmpty("var " + varName);
}
});
return NTxExprHelper.asDouble(r, rendererContext);
};
double result = fct.apply(0.5); // evaluates sin(0.5*pi)
}
Working with Workspaces and Session
With NAF, what starts as a simple one-line setup can grow into a fully controlled runtime environment. Create multiple workspaces, configure sessions with custom flags, manage output formats, and orchestrate complex automation — all while keeping your code clean and portable. NAF gives you both simplicity for quick experiments and full power for advanced applications.
NWorkspace ws = Nuts.openWorkspace("--workspace="/path/to/ws");
ws.runWith(() -> {
NSession.of()
.copy()
.setDry(true)
.setOutputFormat(NContentType.JSON)
.runWith(() -> {
NOut.println(Map.of("status", "ok"));
});
});
Safe Locking with NLock
NLock
provides a flexible, high-level way to lock resources across threads
and even processes. You can create locks from objects, paths, or resource IDs,
and execute code safely while the lock is held. This ensures that critical sections
are executed exclusively and consistently.
// Example 1: Create a lock from an object
NLock lock = NLock.ofPath(NPath.of("/path/to/resource.txt");
// Check lock status
Nout.println("Is locked? " + lock.isLocked());
// Run code while holding the lock
lock.runWith(() -> {
NOut.println("Executing critical section...");
});
// Check if current thread holds the lock
Nout.println("Held by current thread? " + lock.isHeldByCurrentThread());
Advanced Locking and Timed Execution
NLock
also supports locks tied to workspace resources or IDs, allowing
inter-process synchronization. You can execute tasks immediately, with a timeout,
or safely retrieve results using callWith
.
NId resourceId = NId.of("net.thevpc.nuts:nuts#0.8.6");
// Lock tied to workspace resource
NLock idLock = NLock.ofIdPath(resourceId);
// Execute a task and get result
// The lock is process-safe and prevents other processes from entering the critical section
String result = idLock.callWith(() -> {
NOut.println("Working with locked resource...");
return "done";
}, 5, TimeUnit.SECONDS).orNull();
NOut.println("Result = " + result);
// Run immediately if lock is free
boolean executed = idLock.runWithImmediately(() -> {
NOut.println("Quick task executed under lock");
});
NOut.println("Was executed? " + executed);