How I write the Objectos Libraries, Part 1
edit 2022-05-31: please note that this post no longer represent my process for writing the Objectos libraries. It is only kept here for historical reasons.
Introduction
I have recently released version 3.1.0^^1^^ of the Objectos suite of Java libraries. If you search the Maven central repository for the artifact ID of one of the libraries, say objectos-git, you will notice that it returns a number of different group IDs. At time of writing the group IDs are:
-
br.com.objectos.oss-java17
-
br.com.objectos.oss-java11
-
br.com.objectos.oss-java8
-
br.com.objectos.oss-java7
-
br.com.objectos.oss-java6
-
br.com.objectos.oss-java16
Each group ID targets a different Java version. This is indicated by the last part of the group ID's name. So to use an Objectos library in your program, you select the group ID that better matches the runtime in which the program will run. For example, if you have a Java 11^^2^^ runtime you declare the dependency like so:
<dependency>
<groupId>br.com.objectos.oss-java11</groupId>
<artifactId>objectos-git</artifactId>
<version>3.1.0</version>
</dependency>
Although each group ID targets a different Java version, all artifacts of all of the group IDs have the following in common:
-
they all provide the same API^^3^^; and
-
they all come from a single source of truth.
On the other hand, in addition to targeting different Java versions, different group IDs have the following additional differences:
-
different group IDs may provide different implementations for the same class;
-
artifacts from groups
oss-java11
and higher are modular^^4^^, that is, the JAR files contains a compiledmodule-info
class file; and -
each group ID is compiled and tested with its specific JDK,
oss-java17
is compiled and tested with JDK 17,oss-java11
is compiled and tested with JDK 11 and so on.
To achieve this, I use a combination of stock tools:
-
M2Eclipse; and
And tools I developed:
-
Objectos Git, an open-source git (partial) implementation in pure Java; and
-
Objectos Latest, an open-source Java annotation processor that generates Java code in order to support the Objectos development.
In this series I will give you an in-depth look on the tools, the setup and the processes I use to develop and release the Objectos Java libraries. I have been using it since the release of Objectos 1.0.0 on July 2021. It allows me, as a solo Java library developer, to develop, test and release the Objectos suite of Java libraries while keeping the overhead tolerable.
Our running example
To illustrate this series, we will design, develop, test, build and release^^5^^ a simple Java library. We will produce two different JAR files of the same library; one will target Java 17 and the second one will target Java 8.
The library does not need to do something particularly useful, it just needs to serve as a good example. In this case in particular it will do somethig useful. In fact, it is something on the roadmap of the Objectos Core Array library.
Comparing two arrays of bytes
I was recently reading the javadocs for the
SSLEngine
class. There, in a code example, it checks if a string value coming from the network
is equal to some expected value. It does so by first decoding the string into a array of
bytes and then comparing the bytes to an expected byte array value. To compare the arrays,
it uses a method I did not know existed, the
Arrays.compare(byte[], byte[])
method.
Reading the javadocs for the Arrays.compare
method, we can see that it was introduced in Java 9
and that it has some relation to the
Arrays.mismatch(byte[], byte[])
method. In fact, reading the source code of the
former method,
we can see that it makes use of the internal
ArraySupport.mismatch
method which, in turn, uses the internal
ArraySupport.vectorizedMismatch
method (when the array length is greater than 7). From the
ArraySupport
javadocs:
The mismatch method implementation,
vectorizedMismatch
, leverages vector-based techniques to access and compare the contents of two arrays. (...) For a byte[] array, 8 bytes (64 bits) can be accessed and compared as a unit rather than individually, which increases the performance when the method is compiled by the HotSpot VM.
If I am not mistaken, I compare arrays of bytes in the Objectos Git library.
Except I do not use the optimized Arrays.mismatch
method. Instead, I do the naive
solution of comparing the arrays one byte at a time by looping through the values of the array.
A wrapper around Arrays.mismatch(byte[], byte[])
If I plan for the Objectos Git library to be compatible with Java 6, 7 or 8, I cannot use
the Arrays.mismatch
method as it was only added in Java 9. However, If I do not use it,
users on Java 9+ will miss a performance improvement^^6^^.
The solution that Objectos provides in this case is to introduce an indirection. Users on all
Java versions call another method say ByteArrays.mismatch
:
byte[] a = // first array
byte[] b = // second array
int result = ByteArrays.mismatch(a, b);
Somehow (this is what this post will discuss) there will be two different implementations for the method:
-
an implementation targeting Java 17 which will just call
Arrays.mismatch
straight away; and -
an implementation targeting Java 8 which will use the "compare the arrays one byte at time" solution^^7^^.
The Required tools
If you do not wish to code along, you can probably skip to the next section. Otherwise, please read on.
To try and get some degree of reproducibility for the examples in this post (and in this series), we will use the same versions of the same tools. In other words, I am pinning the versions of the tools we will use for this post.
This is, of course, optional; you can use the tools already available on your machine. However, all of the shell listings use the paths for the downloaded tools.
Also I should note I am on an amd64 Linux machine:
$ uname --all
Linux eto001 5.10.76-gentoo-r1 #1 SMP PREEMPT Sun Nov 21 17:09:16 -03 2021 x86_64 AMD Phenom(tm) II X4 955 Processor AuthenticAMD GNU/Linux
I did not try this on a Windows machine nor on a Mac machine. That is the reason I emphasized the some degree of reproducibility earlier. I am sorry for that.
Creating a working directory
Let's first create a working directory for our project:
$ mkdir o7
$ cd o7
$ pwd
/home/mendo/o7
Any non-absolute path name given from this point forward will be relative to this path.
All files will go into this directory, including those downloaded or generated by tools (hopefully). It will be easier to clean up after ourselves after we finish.
Getting and installing
From the last part of the previous section, we decided on building two different implementations for our library. One implementation for Java 17 and another for Java 8. Therefore, we will need two different JDKs. We will also need Maven 3. The exact versions are:
Let's get them all at once:
$ wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_linux-x64_bin.tar.gz \
https://corretto.aws/downloads/resources/8.322.06.2/amazon-corretto-8.322.06.2-linux-x64.tar.gz \
https://dlcdn.apache.org/maven/maven-3/3.8.4/binaries/apache-maven-3.8.4-bin.tar.gz
We now extract the archives and create some symlinks for easier access:
$ tar xzf openjdk-17.0.2_linux-x64_bin.tar.gz
$ tar xzf amazon-corretto-8.322.06.2-linux-x64.tar.gz
$ tar xzf apache-maven-3.8.4-bin.tar.gz
$ ln -s jdk-17.0.2/ jdk17
$ ln -s amazon-corretto-8.322.06.2-linux-x64/ jdk8
$ ln -s apache-maven-3.8.4/ mvn3
And we check everything:
$ jdk17/bin/java -version
openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)
$ jdk8/bin/java -version
openjdk version "1.8.0_322"
OpenJDK Runtime Environment Corretto-8.322.06.2 (build 1.8.0_322-b06)
OpenJDK 64-Bit Server VM Corretto-8.322.06.2 (build 25.322-b06, mixed mode)
$ JAVA_HOME=jdk17/ mvn3/bin/mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: /home/mendo/o7/mvn3
Java version: 17.0.2, vendor: Oracle Corporation, runtime: /home/mendo/o7/jdk-17.0.2
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.10.76-gentoo-r1", arch: "amd64", family: "unix"
We have all of the required tools checked. We can now remove the downloaded files:
$ rm openjdk*.tar.gz amazon*.tar.gz apache*.tar.gz
$ ls -l
total 12
drwxr-xr-x 7 mendo mendo 4096 jan 18 17:23 amazon-corretto-8.322.06.2-linux-x64
drwxr-xr-x 6 mendo mendo 4096 jan 20 09:15 apache-maven-3.8.4
drwxr-xr-x 8 mendo mendo 4096 jan 20 09:17 jdk-17.0.2
lrwxrwxrwx 1 mendo mendo 11 jan 20 09:18 jdk17 -> jdk-17.0.2/
lrwxrwxrwx 1 mendo mendo 37 jan 20 09:18 jdk8 -> amazon-corretto-8.322.06.2-linux-x64/
lrwxrwxrwx 1 mendo mendo 19 jan 20 09:18 mvn3 -> apache-maven-3.8.4/
Java 17 library implementation
In this section we will design, write, test and build an implementation of our library.
This first tentative implementation will target the highest Java version we want to support,
Java 17. We will use Maven as our build tool. Let's start with the library's pom.xml
file.
For those who wish to code along, please know that we will download all of the required files later in this section.
The pom.xml file
If you wish to follow along, get the file here.
The first thing we are going to do is to instruct the maven-compiler-plugin
that this is
a Java 17 project. We will set, at the POM project level, the plugin's properties
to the desired values:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
Please note that, depending on your requirements, the
maven.compiler.release
property is probably a better and more compact solution. However, we will also build this library
using a Java 8 compiler in a later step and this option was only introduced in JDK 9.
On the coding side, we will start by writing test cases. So we will add dependencies for testing.
I am used to using TestNG, so will add that. Also, since we will be
dealing with byte arrays, we can use
Objectos Testing Random
for generating random byte array values. This is a Java 17 project so we will use the
oss-java17
group ID for the Objectos testing library. We will also set convenience properties
for the versions of dependencies. The versions will be set to the latest at time of writing.
<properties>
<objectos.groupId>br.com.objectos.oss-java17</objectos.groupId>
<objectos.version>3.1.0</objectos.version>
<testng.version>7.5</testng.version>
</properties>
...
<dependencies>
<dependency>
<groupId>${objectos.groupId}</groupId>
<artifactId>objectos-testing-random</artifactId>
<version>${objectos.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
Finally, to ensure some degree of reproducibility, we will set the character encoding and will set and pin the version for each of the standard Maven plugins. As with the dependencies, the versions of the plugins will be set to the latest at time of writing. I usually refer to this page for the latest versions.
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<pluginManagement>
<plugins>
...
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.9.0</version>
</plugin>
...
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</pluginManagement>
</build>
Next we will design our tests.
Designing our tests
We will use the javadocs for the
Arrays.mismatch(byte[], byte[])
method as our specification. It states:
Finds and returns the index of the first mismatch between two byte arrays, otherwise return -1 if no mismatch is found. The index will be in the range of 0 (inclusive) up to the length (inclusive) of the smaller array.
From the otherwise sentence above follows that it should return -1 if the arrays are equal to each other. Let's note it in a quasi code:
test(-1, arr(), arr());
test(-1, arr(1), arr(1));
test(-1, arr(1, 2), arr(1, 2));
test(-1, arr(1, 2, 3), arr(1, 2, 3));
The first argument of test
method invocation is the expected result of our method.
The remaining arguments are the byte array values. They will be created by some method
named arr
which accept int values.
The last sentence gives the range of the possible return values: from zero to the length of the smaller array. It is "the index of the first mismatch between two byte arrays". So it should return zero when the first element of the larger array is the first mismatch:
test(0, arr(), arr(1));
test(0, arr(), arr(1, 2));
test(0, arr(1), arr());
test(0, arr(1, 2), arr());
test(0, arr(3), arr(5));
test(0, arr(3), arr(5, 6));
test(0, arr(3, 4), arr(5, 6));
test(0, arr(3, 4, 5), arr(5, 6));
And it should return 1 when the second element of the larger array is the first mismatch:
test(1, arr(1, 2), arr(1));
test(1, arr(1, 2, 3), arr(1));
test(1, arr(9), arr(9, 8, 7));
And it should return 2 when the third element of the larger array is the first mismatch:
test(2, arr(1, 2, 3), arr(1, 2));
test(2, arr(1, 2, 3, 4), arr(1, 2));
test(2, arr(1, 2, 3, 4), arr(1, 2, 9));
test(2, arr(9, 8), arr(9, 8, 7));
And so on.
As we saw in an earlier section, the optimized algorithm is only used for arrays lengths greater than 7. We will add a parameterized test for this. The parameter is the array length.
With the length len
we create a random byte array and we immediately create a copy of the same length.
We proceed and test that there is no mismatch. Again in quasi code:
a = random(len);
b = copyOf(a, len);
test(-1, a, a);
test(-1, a, b);
test(-1, b, a);
We then create a larger copy of the original array. We check that the mismatch is equal to the smaller array length:
b = copyOf(a, len + 1);
test(len, a, b);
test(len, b, a);
We then force the first element of the copied array to be different and check that the mismatch is at index zero:
b[0] = a[0] + 1;
test(0, a, b);
test(0, b, a);
We then move the mismatch to the next index. To simplify, we will do it only for the last element of the smaller array:
b[0] = a[0];
b[len - 1] = a[len - 1] + 1;
test(len - 1, a, b);
test(len - 1, b, a);
And this should be enough for now.
ByteArraysTest.java
The full version of the file is here.
To keep the overhead of maintaining multiple builds of the same library tolerable, we will, ideally, use the same tests for both builds of our library. Therefore, in our tests, we must only use Java 8 language features and API. Since our tests will be quite simple, this is almost a non-issue in this case. But it is something important to keep in mind.
To write the ByteArraysTest.java file we have to translate the quasi code snippets from the previous section to actual Java code.
To simplify the directory hierarchy, we will name the library's package simply library
.
The test
method must call our library's method and verify the result:
private void test(int expected, byte[] a, byte[] b) {
int result;
result = ByteArrays.mismatch(a, b);
Assert.assertEquals(result, expected);
}
The arr
method simply creates a byte array from an int array by casting the values:
private byte[] arr(int... values) {
byte[] arr;
arr = new byte[values.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = (byte) values[i];
}
return arr;
}
For the parameterized test, we will use a TestNG
@DataProvider
annotated method. It will provide lengths from 1 to 64:
@DataProvider
public Object[][] mismatchParam() {
Object[][] result;
result = new Object[63][];
for (int i = 0; i < result.length; i++) {
result[i] = new Object[] {i + 1};
}
return result;
}
Finally, the random
and the copyOf
methods will be respectively implemented using:
ByteArrays.java
The full version of the file is here.
Of course, if we try and build our library it would fail as there is no implementation for the
ByteArrays
class.
For this particular implementation, we will just invoke to the Arrays.mismatch
method:
package library;
import java.util.Arrays;
public final class ByteArrays {
private ByteArrays() {}
public static int mismatch(byte[] a, byte[] b) {
return Arrays.mismatch(a, b);
}
}
module-info.java
The full version of the file is here.
Let's make the library modular by creating a module-info.java
file:
module library {
exports library;
}
Getting all of the files
Let's get all of the required files for our build:
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v1/index.html
Verify that we got all of the files:
$ find projects/ -type f
projects/library/src/main/java/module-info.java
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml
Maven local repository
Before we actually run Maven to build and test our library, let's first create an alternate local repository. The local repository is the place where Maven stores all the artifacts it downloads.
This is, of course, optional. But it will allow cleaning up the Maven downloaded artifacts after we finish.
To instruct Maven to use our custom local repository, we will set the
maven.repo.local
property from the command line. You can also set the local repository through the
settings.xml
file.
Let's create the directory:
$ mkdir --parents m2/repository
Maven build
Now we can run Maven using our alternate local repository:
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-resources-plugin/3.2.0/maven-resources-plugin-3.2.0.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-resources-plugin/3.2.0/maven-resources-plugin-3.2.0.pom (8.1 kB at 34 kB/s)
(...)
[INFO]
[INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ library ---
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/maven-plugin-api/3.1.0/maven-plugin-api-3.1.0.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/apache/maven/maven-plugin-api/3.1.0/maven-plugin-api-3.1.0.pom (3.0 kB at 22 kB/s)
(...)
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running library.ByteArraysTest
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.535 s - in library.ByteArraysTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 64, Failures: 0, Errors: 0, Skipped: 0
(...)
[INFO] --- maven-install-plugin:3.0.0-M1:install (default-install) @ library ---
Downloading from rio-repository: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.7.1/plexus-component-annotations-1.7.1.pom
Downloaded from rio-repository: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.7.1/plexus-component-annotations-1.7.1.pom (770 B at 5.4 kB/s)
(...)
[INFO] Installing /home/mendo/o7/projects/library/target/library-1-SNAPSHOT.jar to /home/mendo/o7/m2/repository/br/com/objectos/www/library/1-SNAPSHOT/library-1-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repository/br/com/objectos/www/library/1-SNAPSHOT/library-1-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:05 min
[INFO] Finished at: 2022-01-24T11:55:47-03:00
[INFO] ------------------------------------------------------------------------
The ellipsis (...)
indicate suppressed lines from the output.
The messages from the maven-install-plugin
^^8^^ indicate that Maven picked up the local
repository we configured. In any case, let's check our local repository:
$ find m2/repository/ -type f
m2/repository/javax/inject/javax.inject/1/javax.inject-1.pom
m2/repository/javax/inject/javax.inject/1/javax.inject-1.pom.sha1
m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar.sha1
m2/repository/javax/inject/javax.inject/1/_remote.repositories
m2/repository/javax/inject/javax.inject/1/javax.inject-1.jar
(...)
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.pom.sha1
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.jar
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.pom
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/_remote.repositories
m2/repository/br/com/objectos/oss-java17/objectos-core-object/3.1.0/objectos-core-object-3.1.0.jar.sha1
OK, the downloaded artifacts were stored in the local repository.
Java 8 library implementation
So far we have successfully created, tested and built the Java 17 version of our library. If we try to use in a Java 8 application it will fail as:
-
the library has been compiled with a Java 17 target; and
-
the library uses Java API not available in Java8.
To illustrate the last point, let's try to build our library with JDK 8. First, we create a
copy of the original project just so we can safely remove the module-info.java
file:
$ cp --archive projects/library library-java8
$ rm library-java8/src/main/java/module-info.java
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
--define maven.compiler.target=1.8 \
--define maven.compiler.source=1.8 \
--define maven.repo.local=m2/repository \
--file library-java8/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/mendo/o7/library-java8/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/library-java8/src/main/java/library/ByteArrays.java:[9,18] cannot find symbol
symbol: method mismatch(byte[],byte[])
location: class java.util.Arrays
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.876 s
[INFO] Finished at: 2022-01-24T12:48:47-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/library-java8/src/main/java/library/ByteArrays.java:[9,18] cannot find symbol
[ERROR] symbol: method mismatch(byte[],byte[])
[ERROR] location: class java.util.Arrays
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
As expected, the build fails with a compilation error as the Arrays.mismatch
method
does not exist in Java 8. We can cleanup the copy we made:
$ rm --recursive library-java8/
Problems to solve for Java 8
So what we want now is to be able to, from the same project, build two different artifacts: one that can be used in a Java 17 application and another that can be used in a Java 8 application. For this, there are a number of problems we need to solve.
First, from a ease of use point of view, the selection of which version to build should be, ideally, some flag or property that can be easily set from the command line:
# Builds the java17 version"
mvn 'some flag for java17' install
# Builds the java8 version"
mvn 'some flag for java8' install
Second, as we want to have a single project that can produce two different artifacts, we need a way to "hide" the Java 17 specific source code from the Java 8 compiler.
Third, our ByteArrays.mismatch
method is a static one and both versions of the library
must provide the same method signature. How can we provide different implementations
to a static method while keeping the signature the same?
The solutions I am currently using are, in order:
-
the
build-helper-maven-plugin
; and -
dispatch to a singleton.
Maven profiles can solve our first problem as they can be easily activated from the
command line using the option --activate-profiles
(or with its short form -P
).
The build-helper-maven-plugin
allows for adding more source directories to a build execution.
So a Java 17 build can "see" more source directories than the Java 8 one.
Finally, a singleton could be used as follows:
// public API
public class ByteArrays {
public static int mismatch(byte[] a, byte[] b) {
return MismatchSingleton.INSTANCE.mismatch(a, b);
}
}
// the java file will be generated at compile time.
final class MismatchSingleton {
static final Mismatch INSTANCE = // MismatchJava8 or 17.INSTANCE;
}
// the java file resides at src/main/java
abstract class Mismatch {
abstract int mismatch(byte[] a, byte[] b);
}
// the java file resides at src/main/java8
final class MismatchJava8 extends Mismatch {
static final Mismatch INSTANCE = new MismatchJava8();
final int mismatch(byte[] a, byte[] b) {
// java 8 impl...
}
}
// the java file resides at src/main/java17
final class MismatchJava17 extends Mismatch {
static final Mismatch INSTANCE = new MismatchJava17();
final int mismatch(byte[] a, byte[] b) {
// java 17 impl...
}
}
In the Java 8 build, the compiler will only "see" the Java 8 implementation and the
MismatchSingleton
java file will be generated as:
final class MismatchSingleton {
static final Mismatch INSTANCE = MismatchJava8.INSTANCE;
}
In the Java 17 build, the compiler will see all of the implementations and the MismatchSingleton
java file will be generated as:
final class MismatchSingleton {
static final Mismatch INSTANCE = MismatchJava17.INSTANCE;
}
This requires code generation which is done at compile time by an annotation processor provided by the Objectos Latest library.
Refactoring our library: adding Maven profiles
So, in order to support the creation of an additional binary artifact, the one for Java 8, we will modify our project and introduce two Maven profiles. Each profile will target a different Java version.
In this first iteration, we will just move some of the properties from the POM project level to the POM project profile level. In particular, we will move:
-
the
maven-compiler-plugin
configuration properties; and -
the Objectos group ID property.
Like so:
<profiles>
<profile>
<id>java17</id>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<objectos.groupId>br.com.objectos.oss-java17</objectos.groupId>
</properties>
</profile>
<profile>
<id>java8</id>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<objectos.groupId>br.com.objectos.oss-java8</objectos.groupId>
</properties>
</profile>
</profiles>
You can find the full version of the file here.
Let's download the new pom.xml
file build the project with the Java 17 profile we have
just created:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v2/index.html
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 2-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.227 s
[INFO] Finished at: 2022-01-24T15:18:59-03:00
[INFO] ------------------------------------------------------------------------
OK, the Java 17 build was successful.
The Java 8 profile should fail as we have not changed any of the source files. So we will
not run it. But let's do a Maven run without a profile and assert that it fails.
It should fail as one of the dependencies, namely objectos-testing-random
will not
have a groupId
defined when no profiles are active (remember we just moved the group ID
property to our profiles):
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[ERROR] 'dependencies.dependency.groupId' for ${objectos.groupId}:objectos-testing-random:jar with value '${objectos.groupId}' does not match a valid id pattern. @ line 89, column 13
@
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]
[ERROR] The project br.com.objectos.www:library:2-SNAPSHOT (/home/mendo/o7/projects/library/pom.xml) has 1 error
[ERROR] 'dependencies.dependency.groupId' for ${objectos.groupId}:objectos-testing-random:jar with value '${objectos.groupId}' does not match a valid id pattern. @ line 89, column 13
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
In a proper project it is probably a bad idea for the build to fail if no profiles are activated. But this error confirms that the Maven profile we have just created is indeed working.
Refactoring our library: adding the build-helper-maven-plugin
So far, by activating different profiles, we can instruct Maven to compile our Java files in two different ways, one targeting Java 17 and the other targeting Java 8. We still cannot actually compile in the latter way as the source code makes use of API not available in JDK 8.
To solve this, we will split the Java source files into three different directories:
-
src/main/java for code common to both targets;
-
src/main/java8 also for code common to both targets, but written specifically to support the Java 8 target; and
-
src/main/java17 for code exclusive to the Java 17 target.
For completeness and documentation purposes, we will apply the same principle to the test source directories. Note that in our current example this is not (currently) required; we will share the same tests for both targets. Sometimes this might not be possible and we might need to also split the test source files.
To do it, we will introduce the
build-helper-maven-plugin
to our Maven build. Its documentation says:
(The build-helper-maven-plugin) contains various small independent goals to assist with the Maven build lifecycle.
We are interested in two of these goals:
The full version of the modified pom.xml can be found here.
So we add the build-helper-maven-plugin
to our profiles. Let's first set its version in the
plugin management section:
<build>
<pluginManagement>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
Next, we configure it in the Java 17 profile. We will add both the java17
and the java8
source sets:
<profiles>
<profile>
<id>java17</id>
...
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java17</source>
<source>src/main/java8</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/java17</source>
<source>src/test/java8</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Lastly, we configure it for the Java 8 profile. We will add only the java8
source sets:
<profiles>
<profile>
<id>java8</id>
...
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/java8</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/test/java8</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Once again, we are only adding the test source directories for completeness; it is not (currently) required in our current example.
Let's get the update version of our files:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v3/index.html
And verify what we got:
$ find projects/ -type f
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java17/module-info.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml
We can see that the module-info.java
has been moved to the Java 17 specific source folder.
This makes sense as it is an invalid Java file for the Java 8 compiler.
Let's check if the build-helper-maven-plugin
is working as expected in the Java 17 build:
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 3-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO]
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ library ---
[INFO]
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
Downloading from rio: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.5.5/plexus-component-annotations-1.5.5.pom
Downloaded from rio: http://rio/nexus/content/groups/public/org/codehaus/plexus/plexus-component-annotations/1.5.5/plexus-component-annotations-1.5.5.pom (815 B at 2.4 kB/s)
(...)
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java17 added.
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
[INFO]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 15.353 s
[INFO] Finished at: 2022-01-25T08:32:15-03:00
[INFO] ------------------------------------------------------------------------
OK, the Java 17 build passed and we confirmed the build-helper-maven-plugin
did its work.
Let's check that the Java 8 still fails with a compilation error:
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 3-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /home/mendo/o7/projects/library/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/ByteArrays.java:[24,18] cannot find symbol
symbol: method mismatch(byte[],byte[])
location: class java.util.Arrays
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.357 s
[INFO] Finished at: 2022-01-25T08:35:37-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/ByteArrays.java:[24,18] cannot find symbol
[ERROR] symbol: method mismatch(byte[],byte[])
[ERROR] location: class java.util.Arrays
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
OK, it failed as expected.
In the next section we will try and fix this compilation error.
Refactoring our library: adding the Mismatch singleton
In a earlier section, we laid out the singleton solution we will use. In this section we will refactor our project to introduce the singleton to the Java 17 build.
The link in each class name points to the respective Java file.
In the src/main/java
directory, we create the
Mismatch
abstract class:
package library;
abstract class Mismatch {
abstract int mismatch(byte[] a, byte[] b);
}
In the src/main/java17
directory, we create the
MismatchJava17
implementation:
package library;
import java.util.Arrays;
final class MismatchJava17 extends Mismatch {
static final MismatchJava17 INSTANCE = new MismatchJava17();
@Override
final int mismatch(byte[] a, byte[] b) {
return Arrays.mismatch(a, b);
}
}
In the src/main/java
directory, we create the
MismatchSingleton
which works as a symlink to MismatchJava17
:
package library;
final class MismatchSingleton {
static final Mismatch INSTANCE = MismatchJava17.INSTANCE;
}
Finally, in the src/main/java
directory, we alter the
ByteArrays
class to refer to the symlink (MismatchSingleton
in this analogy)
and not to the target path (MismatchJava17
in this analogy):
package library;
public final class ByteArrays {
private ByteArrays() {}
public static int mismatch(byte[] a, byte[] b) {
return MismatchSingleton.INSTANCE.mismatch(a, b);
}
}
Let's get the new version of our project:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v4/index.html
And we verify that we got all of the files:
$ find projects/ -type f
projects/library/src/main/java/library/MismatchSingleton.java
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/test/java/library/ByteArraysTest.java
projects/library/pom.xml
Let's check if the Java 17 build passes:
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 4-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.789 s
[INFO] Finished at: 2022-01-25T09:29:43-03:00
[INFO] ------------------------------------------------------------------------
OK, Java 17 build successful.
Finally, let's check that the Java 8 build now fails with a different compilation error. It should
fail as the MismatchJava17
type referenced by the MismatchSingleton
class does not exist
in the Java 8 source set; it exists only in the Java 17 source set:
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 4-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /home/mendo/o7/projects/library/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/MismatchSingleton.java:[19,43] cannot find symbol
symbol: variable MismatchJava17
location: class library.MismatchSingleton
[INFO] 1 error
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.395 s
[INFO] Finished at: 2022-01-25T09:31:48-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.9.0:compile (default-compile) on project library: Compilation failure
[ERROR] /home/mendo/o7/projects/library/src/main/java/library/MismatchSingleton.java:[19,43] cannot find symbol
[ERROR] symbol: variable MismatchJava17
[ERROR] location: class library.MismatchSingleton
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
OK, it failed as expected.
In the next section we will add the Java 8 concrete class for the Mismatch
abstract
class.
MismatchJava8.java
We are now done with our refactorings. We can now do the actual Java 8 implementation of our library.
As mentioned in a earlier section, the Java 8 version will use
the "compare the arrays one byte at time" solution.
So let's create the java file for the
MismatchJava8
class. Here is a possible implementation:
package library;
import java.util.Objects;
final class MismatchJava8 extends Mismatch {
static final MismatchJava8 INSTANCE = new MismatchJava8();
@Override
final int mismatch(byte[] a, byte[] b) {
Objects.requireNonNull(a, "a == null");
Objects.requireNonNull(b, "b == null");
int length;
length = Math.min(a.length, b.length);
for (int i = 0; i < length; i++) {
if (a[i] != b[i]) {
return i;
}
}
if (a.length == b.length) {
return -1;
} else {
return length;
}
}
}
Before we test the Maven build, we must edit the
MismatchSingleton
java file for it to use the newly created type:
package library;
final class MismatchSingleton {
static final Mismatch INSTANCE = MismatchJava8.INSTANCE;
}
Let's get the new version of our project:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v5/index.html
And we verify that we got all of the files:
$ find projects/ -type f | sort
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java/library/MismatchSingleton.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java
Let's test our Java 8 version now:
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 5-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- build-helper-maven-plugin:3.3.0:add-source (add-source) @ library ---
[INFO] Source directory: /home/mendo/o7/projects/library/src/main/java8 added.
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.802 s
[INFO] Finished at: 2022-01-25T10:29:02-03:00
[INFO] ------------------------------------------------------------------------
OK, the build was successful.
Improving our build process
So far we managed to build, from a single project, two different JAR artifacts:
-
a JAR file built and tested with JDK 17; and
-
a JAR file built and tested with JDK 8.
The process, however, is still lacking:
-
we have to manually edit the MismatchSingleton.java file in order to switch between implementations;
-
in order to build from the command line we have to inform both the JDK we want to use and the Maven profile we want to activate;
-
all of the Maven produced artifacts, like classes and JAR files are currently going to the same location. By this I mean that there is no easy way to identify which JDK created an specific set of classes or JAR files; and
-
each time we invoke Maven, apart from the goals and phases which are common arguments, we have to inform many parameters; we could save a few keystrokes by writing a small bash script.
In the following sections we will incrementally improve our build process aiming to resolve these issues.
Adding objectos-latest
In the section where we implemented the MismatchJava8
class
we had to manually edit the MismatchSingleton.java
file in order to switch
from the Java 17 implementation to the Java 8 one. This is not practical in a day-to-day
operation. Let's automate this process using
Objectos Latest.
Objectos Latest, which might be a slight misnomer, is an annotation processor that generates a Java file based on the subclasses of an annotated type. It selects, among the subclasses declared in the same package of the annotated type, the one that represents the latest version. More precisely, it expects the subclasses' names to end with an integer number. Then, among all of the available subclasses, it selects the one having the greatest of such numbers.
To get started with Objectos Latest, let's declare it in our project's POM file. Here is the full version of the updated POM file if you wish to follow along.
We will declare it in the POM 'common' section as both profiles will require it. We will declare it in two different parts:
-
as an
annotationProcessorPaths
option to themaven-compiler-plugin
. This is to instruct the Maven compiler plugin to only search for annotation processors in these elements; and -
as an optional dependency of the whole project. This is to make the Objectos Latest annotations available to the project.
<project>
...
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>${objectos.groupId}</groupId>
<artifactId>objectos-latest</artifactId>
<version>${objectos.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>${objectos.groupId}</groupId>
<artifactId>objectos-latest</artifactId>
<version>${objectos.version}</version>
<optional>true</optional>
</dependency>
...
</dependencies>
</project>
The Java 17 implementation of our library is modular. In order to annotate our classes
with annotations from the Objectos Latest library, we need to declare that our library's
module depend on the Objectos Latest module. We do so by declaring this dependency
in the module-info.java
file, like so:
module library {
exports library;
requires static br.com.objectos.latest;
}
We declared the dependency as
static
as the annotations (and their processing for that matter) are required at compile time only.
Now we have to annotate our classes. We annotate the superclass
Mismatch
with the
@Singleton
annotation, like so:
package library;
import br.com.objectos.latest.Singleton;
@Singleton
abstract class Mismatch {
abstract int mismatch(byte[] a, byte[] b);
}
Next, we annotate the field of the
MismatchJava17
class that is referencing the singleton instance with the
@Singleton.Field
annotation:
package library;
import br.com.objectos.latest.Singleton;
import java.util.Arrays;
final class MismatchJava17 extends Mismatch {
@Singleton.Field
static final MismatchJava17 INSTANCE = new MismatchJava17();
//...
}
And we do the same with the
MismatchJava8
class:
package library;
import br.com.objectos.latest.Singleton;
import java.util.Objects;
final class MismatchJava8 extends Mismatch {
@Singleton.Field
static final MismatchJava8 INSTANCE = new MismatchJava8();
//...
}
Finally, as Objectos Latest will generate the Java file for the MismatchSingleton
class,
we need to remove the current existing file from our project.
Let's get the new version of our project:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v6/index.html
And we verify that we got all of the files:
$ find projects/ -type f | sort
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java
Note that the MismatchSingleton.java file is not there anymore.
Let's check the Java 17 build:
$ JAVA_HOME=jdk17/ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 6-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java17/objectos-latest/3.1.0/objectos-latest-3.1.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java17/objectos-latest/3.1.0/objectos-latest-3.1.0.pom (1.8 kB at 1.7 kB/s)
(...)
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.125 s
[INFO] Finished at: 2022-01-25T15:01:32-03:00
[INFO] ------------------------------------------------------------------------
Nice, the build was successful. Let's check the generated file:
$ cat projects/library/target/generated-sources/annotations/library/MismatchSingleton.java
package library;
import br.com.objectos.latest.Generated;
@Generated("br.com.objectos.latest.processor.SingletonProcessor")
final class MismatchSingleton {
static final library.MismatchJava17 INSTANCE = MismatchJava17.INSTANCE;
private MismatchSingleton() {}
}
And let's check the Java 8 build:
$ JAVA_HOME=jdk8/ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 6-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java8/objectos-latest/3.1.0/objectos-latest-3.1.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/br/com/objectos/oss-java8/objectos-latest/3.1.0/objectos-latest-3.1.0.pom (0 B at 0 B/s)
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.678 s
[INFO] Finished at: 2022-01-25T15:04:29-03:00
[INFO] ------------------------------------------------------------------------
The Java 8 build was also successful. Let's check the generated file:
$ cat projects/library/target/generated-sources/annotations/library/MismatchSingleton.java
package library;
import br.com.objectos.latest.Generated;
@Generated("br.com.objectos.latest.processor.SingletonProcessor")
final class MismatchSingleton {
static final library.MismatchJava8 INSTANCE = MismatchJava8.INSTANCE;
private MismatchSingleton() {}
}
OK, the annotation processor from the Objectos Latest library is working properly.
Adding maven-toolchains-plugin
In this section, we will introduce the
maven-toolchains-plugin
to our build process.
To understand why this plugin might be required, suppose we were targeting our library to work in a Java 7 environment. This means we would need to build our library with JDK 7. According to the Maven download page, the minimum JDK version it can execute on is JDK 7. So we could just get ourselves a JDK 7 and, as we have been doing, build our project with something like:
JAVA_HOME=jdk7/ mvn3/bin/mvn ...
It should work without major issues (I think). Now suppose you require, in your build process, say a plugin that manages the license headers in your source files. It seems this particular plugin requires at least JDK 8 to execute. If we were to introduce this plugin to our build, our build would fail; Maven would be running on a Java 7 JVM and would not be able to load and/or parse the bytecode of the plugin.
The solution in this case is to run Maven with the latest JDK and let Maven select, at runtime,
which JDK it should use to compile Java code, run tests or to generate Javadocs. Maven provides
this functionality through the maven-toolchains-plugin
.
It should follow from this introduction that adding this plugin to our current example is not strictly required; we are not targeting Java 7 nor are we using a plugin that is incompatible with Java 8. We will do it anyways for completeness and documentation purposes; at time of writing, the actual Objectos Libraries target Java 6, 7, 8, 11 and 17.
To get started with the maven-toolchains-plugin
let's create a
toolchains.xml
file. This file is required and defines and configures the available JDKs that can be used by
the plugin. You can find the full version of the file
here.
Maven by default looks for this file at ~/.m2/toolchains.xml
. We will, however, create it at
our working directory as to not mess up the standard file.
There are two required sections that need configuring:
-
the provides section should contain metadata to identify the toolchain itself. In theory this section can hold any number of arbitrary tags; and
-
the configuration section informs the location of the particular JDK.
So let's add the two JDKs we are using:
<toolchains>
<toolchain>
<type>jdk</type>
<provides>
<version>1.8</version>
</provides>
<configuration>
<jdkHome>jdk8/</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
<version>17</version>
</provides>
<configuration>
<jdkHome>jdk17/</jdkHome>
</configuration>
</toolchain>
</toolchains>
In a proper toolchains file it would be advisable to use absolute path names.
Next, we must declare the plugin it in our project's POM file. In the following link you will find the full version of the updated POM file.
First, let's declare the plugin in our pluginManagement
and plugins
sections:
<project>
...
<build>
<pluginManagement>
<plugins>
...
<plugin>
<artifactId>maven-toolchains-plugin</artifactId>
<version>3.0.0</version>
</plugin>
...
</plugins>
</pluginManagement>
<plugins>
...
<plugin>
<artifactId>maven-toolchains-plugin</artifactId>
<executions>
<execution>
<id>toolchains-jdk</id>
<goals>
<goal>toolchain</goal>
</goals>
</execution>
</executions>
<configuration>
<toolchains>
<jdk>
<version>${maven.toolchains.jdk-version}</version>
</jdk>
</toolchains>
</configuration>
</plugin>
</plugins>
</build>
...
</project>
In the plugin's configuration section, we instruct it to look for, in our toolchains.xml
file,
a JDK providing a version tag with a specific value. The value itself is parameterized.
Let's set its actual value in each of our Maven profiles:
<profiles>
<profile>
<id>java17</id>
<properties>
...
<maven.toolchains.jdk-version>17</maven.toolchains.jdk-version>
...
</properties>
...
</profile>
<profile>
<id>java8</id>
<properties>
...
<maven.toolchains.jdk-version>1.8</maven.toolchains.jdk-version>
...
</properties>
...
</profile>
</profiles>
Let's get the new version of our project:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v7/index.html
And we verify if we got all of the files:
$ find m2/ projects/ -type f ! -path "m2/repository*" | sort
m2/toolchains.xml
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java
And now let's run Maven using our system's JDK. On my system, this is the version I have:
$ java -version
openjdk version "17.0.2" 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)
In theory, for this example, any JDK 7+ should work. And now we run Maven, first the Java 17 profile:
$ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from rio: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-toolchains-plugin/3.0.0/maven-toolchains-plugin-3.0.0.pom
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/plugins/maven-toolchains-plugin/3.0.0/maven-toolchains-plugin-3.0.0.pom (3.6 kB at 3.4 kB/s)
(...)
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='17' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk17/]
[INFO]
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
(...)
[INFO]
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk17/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.101 s
[INFO] Finished at: 2022-01-26T11:32:01-03:00
[INFO] ------------------------------------------------------------------------
We can see from the output that both the maven-compiler-plugin
and the maven-surefire-plugin
picked up the correct JDK. Let's confirm it by running now the Java 8 profile:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
clean install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='1.8' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk8/]
[INFO]
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk8/]
(...)
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk8/]
(...)
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.970 s
[INFO] Finished at: 2022-01-26T11:39:02-03:00
[INFO] ------------------------------------------------------------------------
Once again, both the maven-compiler-plugin
and the maven-surefire-plugin
picked up the correct JDK.
So Maven itself is running on our system's JDK. For the Java 17 build, it selects the
JDK located at the jdk17/
directory. For the Java 8 build, it selects the JDK
located at the jdk8/
directory.
Splitting the build directory
To build our project we are always running the Maven clean
phase. In our current setup,
although not strictly required in theory, it is the safest option. To illustrate this fact,
let's see what happens if build the two profiles without running clean
between runs.
Suppose we are debugging some issue and we have to switch between builds. Let's run the Java 8 profile. For this first run, we invoke the clean and test phases:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
clean test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.902 s
[INFO] Finished at: 2022-01-26T18:16:41-03:00
[INFO] ------------------------------------------------------------------------
OK, the build was successful as expected.
Now, let's run the Java 17 profile, just the test phase:
$ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.716 s
[INFO] Finished at: 2022-01-26T18:17:13-03:00
[INFO] ------------------------------------------------------------------------
OK, Maven recompiled everything and the build was successful.
Finally, let's switch back to the Java 8 profile, just the test phase:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 7-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-surefire-plugin:3.0.0-M5:test (default-test) @ library ---
[INFO] Toolchain in maven-surefire-plugin: JDK[jdk8/]
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.945 s
[INFO] Finished at: 2022-01-26T18:18:55-03:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.0.0-M5:test (default-test) on project library: There are test failures.
[ERROR]
[ERROR] Please refer to /home/mendo/o7/projects/library/target/surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] There was an error in the forked process
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[ERROR] org.apache.maven.surefire.booter.SurefireBooterForkException: There was an error in the forked process
[ERROR] library/ByteArraysTest has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.fork(ForkStarter.java:733)
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:305)
[ERROR] at org.apache.maven.plugin.surefire.booterclient.ForkStarter.run(ForkStarter.java:265)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeProvider(AbstractSurefireMojo.java:1314)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.executeAfterPreconditionsChecked(AbstractSurefireMojo.java:1159)
[ERROR] at org.apache.maven.plugin.surefire.AbstractSurefireMojo.execute(AbstractSurefireMojo.java:932)
(...)
[ERROR]
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
The build failed. The classes were not recompiled; a Java 8 JVM tried to run test classes that were compiled with JDK 17.
One possible way to solve this issue is to split the Maven build directory. More precisely,
one possible solution is to instruct Maven to use different build directories for
each of our profiles. By default, Maven creates and uses a directory named target
as the build
directory. Maven creates this directory as a child of the directory where the pom.xml
file is.
To use another build directory, we have to update the build
section of each of our profiles
in our POM file.
Here is the full version of the updated POM file. The modified sections are shown below:
<profiles>
<profile>
<id>java17</id>
...
<build>
<directory>${project.basedir}/target-java17</directory>
...
</build>
</profile>
<profile>
<id>java8</id>
...
<build>
<directory>${project.basedir}/target-java8</directory>
...
</build>
</profile>
</profiles>
All right, let's get all of the files:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v8/index.html
Here is a listing of all of the files:
$ find projects/ -type f | sort
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java
OK, now let's repeat the sequence of builds that failed before. We start building our Java 8 profile:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
clean test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk8/]
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 3 source files to /home/mendo/o7/projects/library/target-java8/classes
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.220 s
[INFO] Finished at: 2022-01-27T07:07:29-03:00
[INFO] ------------------------------------------------------------------------
From the output of the maven-compiler-plugin
we can see that it picked up our
new build directory location.
Next we build our Java 17 profile:
$ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] --- maven-compiler-plugin:3.9.0:compile (default-compile) @ library ---
[INFO] Toolchain in maven-compiler-plugin: JDK[jdk17/]
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to /home/mendo/o7/projects/library/target-java17/classes
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.049 s
[INFO] Finished at: 2022-01-27T07:10:29-03:00
[INFO] ------------------------------------------------------------------------
And we build the Java 8 profile again, this time without the clean phase:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repository \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
test
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.551 s
[INFO] Finished at: 2022-01-27T07:11:59-03:00
[INFO] ------------------------------------------------------------------------
OK, build was successful.
Splitting the local repository
In this section we will update our build process so that each profile uses a different local repository.
Currently, any produced JAR file is being installed in our local repository located at the
m2/repository
directory. If we do a mvn install
with the Java 8 profile immediately
followed by a mvn install
with the Java 17 profile our local repository will contain only
the Java 17 binaries.
To partially^^9^^ solve this, in a manner analogous to the previous section, we will instruct Maven to use different local repository locations.
Let's first remove our current local repository:
$ rm --recursive m2/repository/
Let's install the Java 17 profile to the m2/repo-java17
directory.
Maven will create the directory so there is no need to create it ourselves:
$ mvn3/bin/mvn \
--activate-profiles java17 \
--define maven.repo.local=m2/repo-java17 \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-shared-utils/3.1.0/maven-shared-utils-3.1.0.jar (164 kB at 835 kB/s)
Downloaded from rio: http://rio/nexus/content/groups/public/commons-io/commons-io/2.5/commons-io-2.5.jar (209 kB at 1.0 MB/s)
[INFO] Installing /home/mendo/o7/projects/library/target-java17/library-8-SNAPSHOT.jar to /home/mendo/o7/m2/repo-java17/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repo-java17/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 51.893 s
[INFO] Finished at: 2022-01-27T08:39:03-03:00
[INFO] ------------------------------------------------------------------------
This first build takes longer as Maven has to re-download all of the plugins and dependencies.
We can see that the artifact was installed at the m2/repo-java17
directory.
Let's now install the Java 8 profile to the m2/repo-java8
directory:
$ mvn3/bin/mvn \
--activate-profiles java8 \
--define maven.repo.local=m2/repo-java8 \
--file projects/library/pom.xml \
--toolchains m2/toolchains.xml \
install
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 8-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
(...)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-artifact-transfer/0.10.0/maven-artifact-transfer-0.10.0.jar (128 kB at 680 kB/s)
Downloaded from rio: http://rio/nexus/content/groups/public/org/apache/maven/shared/maven-shared-utils/3.1.0/maven-shared-utils-3.1.0.jar (164 kB at 871 kB/s)
[INFO] Installing /home/mendo/o7/projects/library/target-java8/library-8-SNAPSHOT.jar to /home/mendo/o7/m2/repo-java8/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.jar
[INFO] Installing /home/mendo/o7/projects/library/pom.xml to /home/mendo/o7/m2/repo-java8/br/com/objectos/www/library/8-SNAPSHOT/library-8-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 50.424 s
[INFO] Finished at: 2022-01-27T08:42:20-03:00
[INFO] ------------------------------------------------------------------------
And we can see that the artifact was installed at the m2/repo-java8
directory.
Creating convenience shell scripts
So far, each time that we invoke Maven, we have been setting all of the options using their long form. This is intentional. However, in a day-to-day operation, it might become error-prone.
Let's create a simple shell script for each of our profiles. They will reside in a bin
directory and both have the following general form:
#!/bin/bash
P="javaX"
OPTS=(
--activate-profiles ${P}
--define maven.repo.local=m2/repo-${P}
--file projects/library/pom.xml
--toolchains m2/toolchains.xml
)
mvn3/bin/mvn "${OPTS[@]}" "$@"
And here are the full versions of the scripts:
We get all of the files:
$ rm --recursive projects/
$ wget --cut-dirs=3 \
--no-host-directories \
--no-parent \
--recursive \
--reject "index.html*" \
https://www.objectos.com.br/blog/how-i-write-the-objectos-libraries/v9/index.html
We should have the following:
$ find bin/ projects/ -type f | sort
bin/mvn17
bin/mvn8
projects/library/pom.xml
projects/library/src/main/java/library/ByteArrays.java
projects/library/src/main/java/library/Mismatch.java
projects/library/src/main/java17/library/MismatchJava17.java
projects/library/src/main/java17/module-info.java
projects/library/src/main/java8/library/MismatchJava8.java
projects/library/src/test/java/library/ByteArraysTest.java
Just to be safe, confirm the contents of the scripts:
$ cat bin/mvn*
Make the scripts executable:
$ chmod +x bin/mvn*
And build. First the Java 17 profile:
$ bin/mvn17 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 9-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='17' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk17/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.306 s
[INFO] Finished at: 2022-01-28T07:52:17-03:00
[INFO] ------------------------------------------------------------------------
Then the Java 8 profile:
$ bin/mvn8 verify
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< br.com.objectos.www:library >---------------------
[INFO] Building library 9-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-toolchains-plugin:3.0.0:toolchain (toolchains-jdk) @ library ---
[INFO] Required toolchain: jdk [ version='1.8' ]
[INFO] Found matching toolchain for type jdk: JDK[jdk8/]
(...)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.285 s
[INFO] Finished at: 2022-01-28T07:53:20-03:00
[INFO] ------------------------------------------------------------------------
Next on this series
In this first post of the series, we produced two different binaries of our library, one binary targeting Java 8 and the other targeting Java 17. Each binary is built and tested using their specific JDK. Both binaries are produced from a single Maven project.
In the next posts of this series:
-
we will consume the different binaries in two distinct applications. Each application will run on a different JDK;
-
using Objectos Git, we will create an application to copy commits from the main Git repository where the library is stored to two distinct profile specific Git repositories. The application will also, during each copy operation, modify the POM file of our library so that the group ID is changed; and
-
we will setup the Eclipse IDE to support our development process.
Footnotes
[1]: I will write about the Objectos versioning in a future post. For now, I would like you to know that, inspired by the JDK, I adopt a time-based release model. The difference is that I release a new version of the Objectos libraries every quarter while the JDK is released every six months. Therefore, version 3.1.0 is just the fourth version released publicly. By this I mean that, although it is a 'major' version and although it is being used in production, it is still a young project. For all practical purposes, its API should still be considered beta. Please be warned. back to article{#fn1}
[2]: You should also use the oss-java11
group ID if you are running Java runtimes versions 12 up to
16 (inclusive). Note, however, that the libraries are not tested on those environments and are,
therefore, unsupported. To be precise, at time of writing, no runtime is supported at all.
By this I mean that software is offered "AS IS" as per
Apache 2.0 License.
Proper commercial support and LTS versions are on the roadmap. Please
e-mail me
if you are interested in commercial support now.
back to article{#fn2}
[3]: They all provide the same core API. A higher group ID might offer additional API on top of the lower group ID. In other words, you can easily move from a lower group ID to a higher group ID (provided you also upgrade your Java runtime). The other way around, not so much. back to article{#fn3}
[4]: I should note that the artifacts from lower group IDs (oss-java6
, oss-java7
and oss-java8
) do not declare the
Automatic-Module-Name
JAR manifest attribute. This is intentional. It is intended to discourage the use of
these group IDs in the module-path. If your application is modular then you MUST use group ID
oss-java11
or greater.
back to article{#fn4}
[5]: To be precise, we will not actually release the library to Maven central, I will just give you the directions. back to article{#fn5}
[6]: To be precise, at time of writing, Objectos Git will not benefit from this. Git loose object prefix like 'commit ' have less than 8 bytes in length. However, two libraries on the roadmap namely Objectos HTTP and Objectos SMTP will probably benefit from this. back to article{#fn6}
[7]: I could create an implementation similar to the ArraySupport.vectorizedMismatch
using
Unsafe. A sort of indirect backport. Maybe in a future post and/or a future release of the
Objectos libraries.
back to article{#fn7}
[8]: I am aware of the benefits of mvn verify
. However, I often use the --resume-from
option and MNG-4660
is not released yet as it currently targets Maven 4. So I use mvn install
more often.
back to article{#fn8}
[9]: This is a partial solution because both the Java 8 artifact and the Java 17 artifact still have the same Maven coordinates. That is, both artifacts share the same group ID, the same artifact ID and the same version. We will address this issue in a upcoming post. back to article{#fn9}