Ensure nullness correctness using Checker Framework.

Change-Id: Ie5a7749194313664a206e44597091a62afca9bdb
diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index 1dbd1fa..8a3741b 100644
--- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -26,6 +26,7 @@
 import java.util.function.UnaryOperator;
 import org.apiguardian.api.API;
 import org.apiguardian.api.API.Status;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -75,12 +76,11 @@
    *     data.
    * @throws IllegalArgumentException if the serialized GVariant is ill-formed
    */
-  public abstract T decode(ByteBuffer byteSlice);
+  public abstract @NotNull T decode(ByteBuffer byteSlice);
 
   abstract byte alignment();
 
-  @Nullable
-  abstract Integer fixedSize();
+  abstract @Nullable Integer fixedSize();
 
   final boolean hasFixedSize() {
     return fixedSize() != null;
@@ -103,7 +103,7 @@
    * @return a new, decorated {@link Decoder}.
    * @see java.util.stream.Stream#map
    */
-  public final <U> Decoder<U> map(Function<T, U> function) {
+  public final <U> Decoder<U> map(Function<@NotNull T, @NotNull U> function) {
     return new MappingDecoder<>(function);
   }
 
@@ -348,12 +348,12 @@
 
     @Override
     @Nullable
-    Integer fixedSize() {
+    public Integer fixedSize() {
       return null;
     }
 
     @Override
-    public List<U> decode(ByteBuffer byteSlice) {
+    public @NotNull List<U> decode(ByteBuffer byteSlice) {
       List<U> elements;
 
       var elementSize = elementDecoder.fixedSize();
@@ -406,12 +406,13 @@
     }
 
     @Override
+    @Nullable
     public Integer fixedSize() {
       return entryArrayDecoder.fixedSize();
     }
 
     @Override
-    public Map<K, V> decode(ByteBuffer byteSlice) {
+    public @NotNull Map<K, V> decode(ByteBuffer byteSlice) {
       List<Map.Entry<K, V>> entries = entryArrayDecoder.decode(byteSlice);
       return entries.stream().collect(toMap(Entry::getKey, Entry::getValue));
     }
@@ -433,7 +434,7 @@
     }
 
     @Override
-    public byte[] decode(ByteBuffer byteSlice) {
+    public byte @NotNull [] decode(ByteBuffer byteSlice) {
       // A simple C-style array.
       byte[] elements = new byte[byteSlice.limit() / ELEMENT_SIZE];
       byteSlice.get(elements);
@@ -461,7 +462,7 @@
     }
 
     @Override
-    public Optional<U> decode(ByteBuffer byteSlice) {
+    public @NotNull Optional<U> decode(ByteBuffer byteSlice) {
       if (!byteSlice.hasRemaining()) {
         return Optional.empty();
       } else {
@@ -498,12 +499,12 @@
     }
 
     @Override
-    public Integer fixedSize() {
+    public @Nullable Integer fixedSize() {
       return tupleDecoder.fixedSize();
     }
 
     @Override
-    public U decode(ByteBuffer byteSlice) {
+    public @NotNull U decode(ByteBuffer byteSlice) {
       Object[] recordConstructorArguments = tupleDecoder.decode(byteSlice);
 
       try {
@@ -536,7 +537,7 @@
     }
 
     @Override
-    public Integer fixedSize() {
+    public @Nullable Integer fixedSize() {
       int position = 0;
       for (var componentDecoder : componentDecoders) {
         var fixedComponentSize = componentDecoder.fixedSize();
@@ -556,7 +557,7 @@
     }
 
     @Override
-    public Object[] decode(ByteBuffer byteSlice) {
+    public Object @NotNull [] decode(ByteBuffer byteSlice) {
       int framingOffsetSize = byteCount(byteSlice.limit());
 
       var objects = new Object[componentDecoders.length];
@@ -616,13 +617,13 @@
     }
 
     @Override
-    public Integer fixedSize() {
+    public @Nullable Integer fixedSize() {
       return tupleDecoder.fixedSize();
     }
 
     @Override
     @SuppressWarnings("unchecked")
-    public Map.Entry<K, V> decode(ByteBuffer byteSlice) {
+    public Map.@NotNull Entry<K, V> decode(ByteBuffer byteSlice) {
       Object[] components = tupleDecoder.decode(byteSlice);
       return new SimpleImmutableEntry<>((K) components[0], (V) components[1]);
     }
@@ -642,7 +643,7 @@
     }
 
     @Override
-    public Variant decode(ByteBuffer byteSlice) {
+    public @NotNull Variant decode(ByteBuffer byteSlice) {
       for (int i = byteSlice.limit() - 1; i >= 0; --i) {
         if (byteSlice.get(i) != 0) {
           continue;
@@ -678,7 +679,7 @@
     }
 
     @Override
-    public Boolean decode(ByteBuffer byteSlice) {
+    public @NotNull Boolean decode(ByteBuffer byteSlice) {
       return byteSlice.get() != 0;
     }
   }
@@ -696,7 +697,7 @@
     }
 
     @Override
-    public Byte decode(ByteBuffer byteSlice) {
+    public @NotNull Byte decode(ByteBuffer byteSlice) {
       return byteSlice.get();
     }
   }
@@ -714,7 +715,7 @@
     }
 
     @Override
-    public Short decode(ByteBuffer byteSlice) {
+    public @NotNull Short decode(ByteBuffer byteSlice) {
       return byteSlice.getShort();
     }
   }
@@ -732,7 +733,7 @@
     }
 
     @Override
-    public Integer decode(ByteBuffer byteSlice) {
+    public @NotNull Integer decode(ByteBuffer byteSlice) {
       return byteSlice.getInt();
     }
   }
@@ -750,7 +751,7 @@
     }
 
     @Override
-    public Long decode(ByteBuffer byteSlice) {
+    public @NotNull Long decode(ByteBuffer byteSlice) {
       return byteSlice.getLong();
     }
   }
@@ -768,7 +769,7 @@
     }
 
     @Override
-    public Double decode(ByteBuffer byteSlice) {
+    public @NotNull Double decode(ByteBuffer byteSlice) {
       return byteSlice.getDouble();
     }
   }
@@ -793,7 +794,7 @@
     }
 
     @Override
-    public String decode(ByteBuffer byteSlice) {
+    public @NotNull String decode(ByteBuffer byteSlice) {
       byteSlice.limit(byteSlice.limit() - 1);
       return charset.decode(byteSlice).toString();
     }
@@ -801,9 +802,9 @@
 
   private class MappingDecoder<U> extends Decoder<U> {
 
-    private final Function<T, U> function;
+    private final Function<@NotNull T, @NotNull U> function;
 
-    MappingDecoder(Function<T, U> function) {
+    MappingDecoder(Function<@NotNull T, @NotNull U> function) {
       this.function = function;
     }
 
@@ -818,7 +819,7 @@
     }
 
     @Override
-    public U decode(ByteBuffer byteSlice) {
+    public @NotNull U decode(ByteBuffer byteSlice) {
       return function.apply(Decoder.this.decode(byteSlice));
     }
   }
@@ -842,7 +843,7 @@
     }
 
     @Override
-    public T decode(ByteBuffer byteSlice) {
+    public @NotNull T decode(ByteBuffer byteSlice) {
       var transformedBuffer = function.apply(byteSlice.asReadOnlyBuffer().order(byteSlice.order()));
       return Decoder.this.decode(transformedBuffer);
     }
@@ -867,7 +868,7 @@
     }
 
     @Override
-    public T decode(ByteBuffer byteSlice) {
+    public @NotNull T decode(ByteBuffer byteSlice) {
       var newByteSlice = byteSlice.duplicate();
       newByteSlice.order(byteOrder);
       return Decoder.this.decode(newByteSlice);
@@ -898,7 +899,9 @@
       if (!Objects.equals(thenDecoder.fixedSize(), elseDecoder.fixedSize())) {
         throw new IllegalArgumentException(
             "incompatible sizes in predicate branches: then=%s, else=%s"
-                .formatted(thenDecoder.fixedSize(), elseDecoder.fixedSize()));
+                .formatted(
+                    Objects.requireNonNullElse(thenDecoder.fixedSize(), "(null)"),
+                    Objects.requireNonNullElse(elseDecoder.fixedSize(), "(null)")));
       }
     }
 
@@ -913,7 +916,7 @@
     }
 
     @Override
-    public U decode(ByteBuffer byteSlice) {
+    public @NotNull U decode(ByteBuffer byteSlice) {
       var b = selector.test(byteSlice);
       byteSlice.rewind();
       return b ? thenDecoder.decode(byteSlice) : elseDecoder.decode(byteSlice);
diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
index d61b9d1..cc9674d 100644
--- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
+++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Signature.java
@@ -12,6 +12,7 @@
 import java.util.Objects;
 import org.apiguardian.api.API;
 import org.apiguardian.api.API.Status;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * A GVariant signature string.
@@ -73,7 +74,7 @@
   }
 
   @Override
-  public boolean equals(Object o) {
+  public boolean equals(@Nullable Object o) {
     return (o instanceof Signature signature)
         && Objects.equals(signatureString, signature.signatureString);
   }
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
index 3a8185c..cfe3635 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/ByteString.java
@@ -10,6 +10,7 @@
 import java.util.HexFormat;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * A wrapper for a {@code byte[]} that implements {@link #equals(Object)}, {@link #hashCode()}, and
@@ -31,7 +32,7 @@
   }
 
   @Override
-  public boolean equals(Object o) {
+  public boolean equals(@Nullable Object o) {
     return (o instanceof ByteString byteString) && Arrays.equals(bytes, byteString.bytes);
   }
 
diff --git a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
index 52b970f..50da203 100644
--- a/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
+++ b/jgvariant-ostree/src/main/java/eu/mulk/jgvariant/ostree/DeltaSuperblock.java
@@ -70,20 +70,20 @@
               Decoder.ofByteArray().map(DeltaSuperblock::parseDeltaNameList),
               Decoder.ofArray(DeltaMetaEntry.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN),
               Decoder.ofArray(DeltaFallback.decoder()).withByteOrder(ByteOrder.LITTLE_ENDIAN))
-          .map(
-              deltaSuperblock -> {
-                // Fix up the endianness of the 'entries' and 'fallbacks' fields, which have
-                // unspecified byte order.
-                var endiannessMetadatum =
-                    deltaSuperblock.metadata().fields().get("ostree.endianness");
-                if (endiannessMetadatum != null
-                    && endiannessMetadatum.value() instanceof Byte endiannessByte
-                    && endiannessByte == (byte) 'B') {
-                  return deltaSuperblock.byteSwapped();
-                } else {
-                  return deltaSuperblock;
-                }
-              });
+          .map(DeltaSuperblock::byteSwappedIfBigEndian);
+
+  private DeltaSuperblock byteSwappedIfBigEndian() {
+    // Fix up the endianness of the 'entries' and 'fallbacks' fields, which have
+    // unspecified byte order.
+    var endiannessMetadatum = metadata().fields().get("ostree.endianness");
+    if (endiannessMetadatum != null
+        && endiannessMetadatum.value() instanceof Byte endiannessByte
+        && endiannessByte == (byte) 'B') {
+      return byteSwapped();
+    } else {
+      return this;
+    }
+  }
 
   private DeltaSuperblock byteSwapped() {
     return new DeltaSuperblock(
diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
index 5c1dd79..d8ad271 100644
--- a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
+++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java
@@ -17,6 +17,7 @@
 import org.junit.jupiter.api.Test;
 
 @TestWithResources
+@SuppressWarnings("initialization.field.uninitialized")
 class OstreeDecoderTest {
 
   @GivenBinaryResource("/ostree/summary")
diff --git a/jgvariant-parent/pom.xml b/jgvariant-parent/pom.xml
index fe0c1ed..92b7e07 100644
--- a/jgvariant-parent/pom.xml
+++ b/jgvariant-parent/pom.xml
@@ -74,6 +74,7 @@
     <jetbrains-annotations.version>22.0.0</jetbrains-annotations.version>
     <junit-jupiter.version>5.8.2</junit-jupiter.version>
     <xz.version>1.9</xz.version>
+    <checker-framework.version>3.21.1</checker-framework.version>
   </properties>
 
   <distributionManagement>
@@ -102,6 +103,18 @@
         <version>${apiguardian.version}</version>
       </dependency>
 
+      <!-- Static analysis -->
+      <dependency>
+        <groupId>org.checkerframework</groupId>
+        <artifactId>checker</artifactId>
+        <version>${checker-framework.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.checkerframework</groupId>
+        <artifactId>checker-qual</artifactId>
+        <version>${checker-framework.version}</version>
+      </dependency>
+
       <!-- OSTree compression support -->
       <dependency>
         <groupId>org.tukaani</groupId>
@@ -131,6 +144,22 @@
     </dependencies>
   </dependencyManagement>
 
+  <dependencies>
+    <!-- Static analysis -->
+    <dependency>
+      <groupId>org.checkerframework</groupId>
+      <artifactId>checker</artifactId>
+      <scope>provided</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>org.checkerframework</groupId>
+      <artifactId>checker-qual</artifactId>
+      <scope>provided</scope>
+      <optional>true</optional>
+    </dependency>
+  </dependencies>
+
   <build>
 
     <pluginManagement>
@@ -166,8 +195,9 @@
             <fork>true</fork>
             <compilerArgs>
               <arg>-XDcompilePolicy=simple</arg>
-              <arg>-Xplugin:ErrorProne</arg>
+              <arg>-Xplugin:ErrorProne -Xep:InvalidParam:OFF</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
+              <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
               <arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
@@ -184,7 +214,15 @@
                 <artifactId>error_prone_core</artifactId>
                 <version>${errorprone.version}</version>
               </path>
+              <path>
+                <groupId>org.checkerframework</groupId>
+                <artifactId>checker</artifactId>
+                <version>${checker-framework.version}</version>
+              </path>
             </annotationProcessorPaths>
+            <annotationProcessors>
+              <annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
+            </annotationProcessors>
           </configuration>
         </plugin>