1 Application Framework
nuts is a package manager that can be embedded in your application and hence present a solid Application Framework and tooling to make the applicable more robust and more portable.
1.1 Nuts Application Framework
Using Nuts Application Framework (NAF)
Using nuts is transparent as we have seen so far. It is transparent both at build time and runtime. However, nuts can provide our application a set of unique helpful features, such as install and uninstall hooks, comprehensive command line support and so on.
To create your first NAF application, you will need to add nuts as a dependency and change your pom.xml
as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>net.thevpc.nuts</groupId>
<artifactId>nuts-api</artifactId>
<version>0.8.5</version>
</dependency>
<dependency>
<groupId>jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.4.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<nuts.application>true</nuts.application>
</properties>
</project>
Please take note that we have added a property nuts.application=true
. Actually this is not mandatory, but this will help nuts package manager detect that this application uses NAF before downloading its jar (the information will be available in the .xml
descriptor on the remote repository).
package com.mycompany.app;
import java.io.File;
import jxl.Workbook;
import jxl.write.WritableWorkbook;
public class App implements NApplication {
public static void main(String[] args) {
// just create an instance and call runAndExit in the main method
// this method ensures that exist code is well propagted
// from exceptions to caller processes
new App().runAndExit(args);
}
@Override
public void run(NSession session) {
NCmdLine cmd = session.getAppCmdLine();
File file = new File("file.xls");
while (cmd.hasNext()) {
switch (cmd.getKey().getString()) {
case "--file": {
NArg a = cmd.nextEntry().get();
file = new File(a.getStringValue());
break;
}
case "--fill": {
// process other options here ...
break;
}
default: {
s.configureLast(cmd);
}
}
}
try {
WritableWorkbook w = Workbook.createWorkbook(file);
s.out().printf("Workbook just created at %s%n", file);
} catch (Exception ex) {
ex.printStackTrace(s.err());
}
}
@Override // this method is not required, implement when needed
public void onInstallApplication(NSession s) {
s.out().printf("we are installing My Application : %s%n", s.getAppId());
}
@Override // this method is not required, implement when needed
public void onUninstallApplication(NSession s) {
s.out().printf("we are uninstalling My Application : %s%n", s.getAppId());
}
@Override // this method is not required, implement when needed
public void onUpdateApplication(NSession s) {
s.out().printf("we are updating My Application : %s%n", s.getAppId());
}
}
Now we can install or uninstall the application and see the expected messages.
nuts -y install com.mycompany.app:my-app
nuts -y uninstall com.mycompany.app:my-app
1.2 Your first Application using nuts
Running your application with Nuts
Lets take, step by step, an example of an application that you will run using nuts package manager
First we can create the project using your favourite IDE or using simply mvn command
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-simple -DarchetypeVersion=1.4 -DinteractiveMode=false
We will have a fully generated java project
~/> tree
.
└── my-app
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── mycompany
│ └── app
│ └── App.java
└── test
└── java
└── com
└── mycompany
└── app
└── AppTest.java
Now we will add some dependencies to the project. Let's add jexcelapi:jxl#2.4.2
and update pom.xml
consequently.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.4.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
Now we update the App.java file
package com.mycompany.app;
import java.io.File;
import jxl.Workbook;
import jxl.write.WritableWorkbook;
public class App {
public static void main(String[] args) {
try {
WritableWorkbook w = Workbook.createWorkbook(new File("any-file.xls"));
System.out.println("Workbook just created");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
finally we compile the app:
mvn clean install
Of course, we won't be able to run the application yet. Would we? For this app to work there are several ways, all of them are complicated and require modifying the pom.xml
and even modifying the output jar. we can for instance generate an output lib directory and update the META-INF file using maven-dependency-plugin. (see https://maven.apache.org/plugins/maven-shade-plugin ; https://www.baeldung.com/executable-jar-with-maven). We could also use maven-assembly-plugin to include the dependencies into the jar itself ('what the fat' jar!). Another alternative is to use an uglier solution with maven-shade-plugin and blend libraries into the main jar. In all cases we need as well to configure maven-jar-plugin to specify the main class file.
I am not exposing all solutions here. You can read this article for more details (https://www.baeldung.com/executable-jar-with-maven) but trust me, they all stink.Instead of that we will use nuts. In that case, actually we are already done, the app is already OK! We do not need to specify the main class neither are we required to bundle jxl and its dependencies. We only need to run the app. That's it.
Basically, you can install the application using its identifier com.mycompany.app:my-app
. The latest version will be resolved.
nuts install com.mycompany.app:my-app
nuts my-app
This will install the application and run it on the fly. Dependencies will be detected, resolved and downloaded. The application is installed from local maven repository. It needs to be deployed to a public repository for it to be publicly accessible, however.
We can also choose not to install the app and bundle it as a jar. No need for a public repository in that case:
nuts -y com my-app-1.0.0-SNAPSHOT.jar
As we can see, nuts provides the simplest and the most elegant way to deploy your application.
One question though. what happens if we define multiple main methods (in multiple public classes). It is handled as well by nuts seamlessly. It just asks, at runtime, for the appropriate class to run.
Using Nuts Application Framework
Using nuts is transparent as we have seen so far. It is transparent both at build time and runtime. However, nuts can provide our application a set of unique helpful features, such as install and uninstall hooks, comprehensive command line support and so on.
To create your first NAF application, you will need to add nuts as a dependency and change your pom.xml
as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>net.thevpc.nuts</groupId>
<artifactId>nuts-api</artifactId>
<version>0.8.5</version>
</dependency>
<dependency>
<groupId>jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.4.2</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<nuts.application>true</nuts.application>
</properties>
</project>
Please take note that we have added a property nuts.application=true
. Actually this is not mandatory, but this will help nuts package manager detect that this application uses NAF before downloading its jar (the information will be available in the .xml
descriptor on the remote repository).
package com.mycompany.app;
import java.io.File;
import jxl.Workbook;
import jxl.write.WritableWorkbook;
public class App implements NApplication {
public static void main(String[] args) {
// just create an instance and call runAndExit in the main method
new App().runAndExit(args);
}
@Override
public void run(NSession session) {
NCmdLine cmd = session.getAppCmdLine();
File file = new File("file.xls");
while (cmd.hasNext()) {
switch (cmd.getKey().getString()) {
case "--file": {
NArg a = cmd.nextEntry().get();
file = new File(a.getStringValue());
break;
}
case "--fill": {
// process other options here ...
break;
}
default: {
s.configureLast(cmd);
}
}
}
try {
WritableWorkbook w = Workbook.createWorkbook(file);
s.out().printf("Workbook just created at %s%n", file);
} catch (Exception ex) {
ex.printStackTrace(s.err());
}
}
@Override
public void onInstallApplication(NSession s) {
s.out().printf("we are installing My Application : %s%n", s.getAppId());
}
@Override
public void onUninstallApplication(NSession s) {
s.out().printf("we are uninstalling My Application : %s%n", s.getAppId());
}
@Override
public void onUpdateApplication(NSession s) {
s.out().printf("we are updating My Application : %s%n", s.getAppId());
}
}
Now we can install or uninstall the application and see the expected messages.
nuts -y install com.mycompany.app:my-app
nuts -y uninstall com.mycompany.app:my-app
1.3 Nuts Descriptor Integration
Nuts Descriptor Integration
- Seamless integration
- Maven Solver
Nuts and Maven
nuts.executable=<true|false>
: when true the artifact is an executable (contains main class)nuts.application=<true|false>
: when true the artifact is an executable application (implements NutsApplication)nuts.gui=<true|false>
: when true the requires a gui environment to executenuts.term=<true|false>
: when true the artifact is a command line executablenuts.icons=<icon-path-string-array>
: an array (separated with ',' or new lines) of icon paths (url in the NPath format)nuts.genericName=<genericNameString>
: a generic name for the application like 'Text Editor'nuts.categories=<categories-string-array>
: an array (separated with ',' or new lines) of categories. the categories should be compatible with Free Desktop Menu specification (https://specifications.freedesktop.org/menu-spec/menu-spec-1.0.html)
nuts.<os>-os-dependencies
: list (':',';' or line separated) of short ids of dependencies that shall be appended to classpath only if running on the given os (see NutsOsFamily). This is a ways more simple than using the builtin ' profile' concept of Maven (which is of course supported as well)nuts.<arch>-arch-dependencies
: list (':',';' or line separated) of short ids of dependencies that shall be appended to classpath only if running on the given hardware architecture (see NutsArchFamily). This is a ways more simple than using the builtin 'profile' concept of Maven (which is of course supported as well)nuts.<os>-os-<arch>-arch-dependencies
: list (':',';' or line separated) of short ids of dependencies that shall be appended to classpath only if running on the given hardware architecture and os family
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>your-group</groupId>
<artifactId>your-project</artifactId>
<version>1.2.3</version>
<packaging>jar</packaging>
<properties>
<!--properties having special meanings in Nuts-->
<maven.compiler.target>1.8</maven.compiler.target>
<!--properties specific to nuts for developers extending nuts-->
<nuts.runtime>true</nuts.runtime> <!--if you implement a whole new runtime-->
<nuts.extension>true</nuts.extension> <!--if you implement an extension-->
<!--other properties specific to nuts-->
<nuts.genericName>A Generic Name</nuts.genericName>
<nuts.executable>true</nuts.executable>
<nuts.application>true</nuts.application>
<nuts.gui>true</nuts.gui>
<nuts.term>true</nuts.term>
<nuts.categories>
/Settings/YourCategory
</nuts.categories>
<nuts.icons>
classpath://net/yourpackage/yourapp/icon.svg
classpath://net/yourpackage/yourapp/icon.png
classpath://net/yourpackage/yourapp/icon.ico
</nuts.icons>
<nuts.windows-os-dependencies>
org.fusesource.jansi:jansi
com.github.vatbub:mslinks
</nuts.windows-os-dependencies>
<nuts.windows-os-x86_32-arch-dependencies>
org.fusesource.jansi:jansi
com.github.vatbub:mslinks
</nuts.windows-os-x86_32-arch-dependencies>
</properties>
<dependencies>
</dependencies>
</project>
Nuts and Java MANIFEST.MF
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: vpc
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_302
Nuts-Id: groupid:artifactid#version
Nuts-Dependencies: org.fusesource.jansi:jansi#1.2?os=windows;com.github.vatbub:mslinks#1.3?os=windows
Nuts-Name: Your App Name
Nuts-Generic-Name: Your App Generic Name
Nuts-Description: Your App Description
Nuts-Categories: /Settings/YourCategory;/Settings/YourCategory2
Nuts-Icons: classpath://net/yourpackage/yourapp/icon.svg;classpath://net/yourpackage/yourapp/icon.png
Nuts-Property-YourProp: YourValue
Comment: if the Nuts-Id could not be found, best effort will be used from the following
Automatic-Module-Name: yourgroupid.yourartifactid.YourClass
Main-Class: groupid.artifactid.YourClass
Implementation-Version: 1.2.3
Nuts and Java 9 (jdeps)
Nuts supports Automatic-Module-Name.
Automatic-Module-Name: yourgroupid.yourartifactid.YourClass
Nuts and Gradle (TODO)
1.4 NOptional
nuts introduces a concept very similar to java's Optional but with better extension builtin mechanisms and helper methods : NOptional
NOptional is extensively used in Nuts Package Manager itself.
Non Null Assertion
Java has a builtin null Check mechanism but it does not enable customized messages or exceptions. Optional are described as per Java's (c) Documentation "A container object which may or may not contain a non-null value". NOptional is more of an Object Wrapper than addes several useful null related operators like '??' '?.' and '!' in typescript.
if(stringWord==null){
throw new Exception("missing user name");
}
stringWord!.toUpperCase()
if(stringWord==null){
throw new IllegalArgumentException("missing user name");
}
stringWord.toUpperCase()
NOptional.ofNamed(stringWord,"user name").get().toUpperCase();
// will throw an IllegalArgumentException|NIllegalArgumentException with "missing user name" message;
var roadNumber=road.number??10;
Number roadNumber=road.number!=null?road.number:10;
Number roadNumber=NOptional.of(road.number).orElse(10);
var roadNumber=app?.person?.address?.road?.number;
Number roadNumber=(app!=null && app.person!=null && app.person.road!=null)? app.person.address.road.number:null;
Number roadNumber=NOptional.of(app).then(v->v.person).then(v->v.road).then(v->v.number).orNull();
var roadNumber=app?.person?.address!.road?.number??0;
Address address=(app!=null && app.person!=null)?app.person.address:null;
if(address==null){
throw new IllegalArgumentException("missing address");
}
Number roadNumber=(address!=null
&& address.road!=null)
? address.road.number:0;
Number roadNumber=NOptional.of(app).then(v->v.person).then(v->v.address).get().then(v->v.road).then(v->v.number).orElse(0);
1.5 NPath
nuts introduces a concept very similar to java's URL but with better extension builtin mechanisms and helper methods : NPath
supported formats/protocols are:file format
/path/to/to/resource
or\\path\\to\\resource
file URL
/path/to/to/resource
or/path/to/resource
http/https URLs (or any other Java supported URL)
//some-url
or//some-url
classpath
/path/to/to/resource
(you must provide the used classpath upon creation)resource Path
//groupId1:artifactId1#version1;groupId2:artifactId2#version2/path/to/resource
or//(groupId1:artifactId1#version1;groupId2:artifactId2#version2)/path/to/resource
in that case the resource is looked up in any of the artifact classpaths (including dependencies)
1.6 NStream
nuts introduces a concept very similar to java's Stream but with better extension builtin mechanisms and helper methods : NStream
NStream is actually a wrapper to java's Stream, Iterator and Iterable and you can create a stream using .of(...)
methods.
NStream is extensively used in Search Command.