Decoder: Make anonymous classes into named inner classes.

Change-Id: I3150b262512cafe42f139d56c9ecee165da4e1df
diff --git a/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
index 8134d45..bb479ff 100644
--- a/src/main/java/eu/mulk/jgvariant/core/Decoder.java
+++ b/src/main/java/eu/mulk/jgvariant/core/Decoder.java
@@ -96,52 +96,118 @@
    * @return a new {@link Decoder}.
    */
   public static <U> Decoder<List<U>> ofArray(Decoder<U> elementDecoder) {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return elementDecoder.alignment();
-      }
+    return new ArrayDecoder<>(elementDecoder);
+  }
 
-      @Override
-      @Nullable
-      Integer fixedSize() {
-        return null;
-      }
+  /**
+   * Creates a {@link Decoder} for a {@code Maybe} type.
+   *
+   * @param elementDecoder a {@link Decoder} for the contained element.
+   * @param <U> the element type.
+   * @return a new {@link Decoder}.
+   */
+  public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
+    return new MaybeDecoder<>(elementDecoder);
+  }
 
-      @Override
-      public List<U> decode(ByteBuffer byteSlice) {
-        List<U> elements;
+  /**
+   * Creates a {@link Decoder} for a {@code Structure} type.
+   *
+   * @param recordType the {@link Record} type that represents the components of the structure.
+   * @param componentDecoders a {@link Decoder} for each component of the structure.
+   * @param <U> the {@link Record} type that represents the components of the structure.
+   * @return a new {@link Decoder}.
+   */
+  public static <U extends Record> Decoder<U> ofStructure(
+      Class<U> recordType, Decoder<?>... componentDecoders) {
+    return new StructureDecoder<>(recordType, componentDecoders);
+  }
 
-        var elementSize = elementDecoder.fixedSize();
-        if (elementSize != null) {
-          // A simple C-style array.
-          elements = new ArrayList<>(byteSlice.limit() / elementSize);
-          for (int i = 0; i < byteSlice.limit(); i += elementSize) {
-            var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
-            elements.add(element);
-          }
-        } else {
-          // An array with aligned elements and a vector of framing offsets in the end.
-          int framingOffsetSize = byteCount(byteSlice.limit());
-          int lastFramingOffset =
-              getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
-          int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
+  /**
+   * Creates a {@link Decoder} for the {@link Variant} type.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Variant> ofVariant() {
+    return new VariantDecoder();
+  }
 
-          elements = new ArrayList<>(elementCount);
-          int position = 0;
-          for (int i = 0; i < elementCount; i++) {
-            int framingOffset =
-                getIntN(
-                    byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
-            elements.add(
-                elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
-            position = align(framingOffset, alignment());
-          }
-        }
+  /**
+   * Creates a {@link Decoder} for the {@code Boolean} type.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Boolean> ofBoolean() {
+    return new BooleanDecoder();
+  }
 
-        return elements;
-      }
-    };
+  /**
+   * Creates a {@link Decoder} for the 8-bit {@ode byte} type.
+   *
+   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
+   * result of this method.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Byte> ofByte() {
+    return new ByteDecoder();
+  }
+
+  /**
+   * Creates a {@link Decoder} for the 16-bit {@code short} type.
+   *
+   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
+   * result of this method.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Short> ofShort() {
+    return new ShortDecoder();
+  }
+
+  /**
+   * Creates a {@link Decoder} for the 32-bit {@code int} type.
+   *
+   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
+   * result of this method.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Integer> ofInt() {
+    return new IntegerDecoder();
+  }
+
+  /**
+   * Creates a {@link Decoder} for the 64-bit {@code long} type.
+   *
+   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
+   * result of this method.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Long> ofLong() {
+    return new LongDecoder();
+  }
+
+  /**
+   * Creates a {@link Decoder} for the {@code double} type.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<Double> ofDouble() {
+    return new DoubleDecoder();
+  }
+
+  /**
+   * Creates a {@link Decoder} for the {@link String} type.
+   *
+   * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
+   * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
+   *
+   * @return a new {@link Decoder}.
+   */
+  public static Decoder<String> ofString(Charset charset) {
+    return new StringDecoder(charset);
   }
 
   private static int align(int offset, byte alignment) {
@@ -159,350 +225,345 @@
     return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4;
   }
 
-  /**
-   * Creates a {@link Decoder} for a {@code Maybe} type.
-   *
-   * @param elementDecoder a {@link Decoder} for the contained element.
-   * @param <U> the element type.
-   * @return a new {@link Decoder}.
-   */
-  public static <U> Decoder<Optional<U>> ofMaybe(Decoder<U> elementDecoder) {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return elementDecoder.alignment();
-      }
+  private static class ArrayDecoder<U> extends Decoder<List<U>> {
 
-      @Override
-      @Nullable
-      Integer fixedSize() {
-        return null;
-      }
+    private final Decoder<U> elementDecoder;
 
-      @Override
-      public Optional<U> decode(ByteBuffer byteSlice) {
-        if (!byteSlice.hasRemaining()) {
-          return Optional.empty();
-        } else {
-          if (!elementDecoder.hasFixedSize()) {
-            // Remove trailing zero byte.
-            byteSlice.limit(byteSlice.limit() - 1);
-          }
-
-          return Optional.of(elementDecoder.decode(byteSlice));
-        }
-      }
-    };
-  }
-
-  /**
-   * Creates a {@link Decoder} for a {@code Structure} type.
-   *
-   * @param recordType the {@link Record} type that represents the components of the structure.
-   * @param componentDecoders a {@link Decoder} for each component of the structure.
-   * @param <U> the {@link Record} type that represents the components of the structure.
-   * @return a new {@link Decoder}.
-   */
-  public static <U extends Record> Decoder<U> ofStructure(
-      Class<U> recordType, Decoder<?>... componentDecoders) {
-    var recordComponents = recordType.getRecordComponents();
-    if (componentDecoders.length != recordComponents.length) {
-      throw new IllegalArgumentException(
-          "number of decoders (%d) does not match number of structure components (%d)"
-              .formatted(componentDecoders.length, recordComponents.length));
+    ArrayDecoder(Decoder<U> elementDecoder) {
+      this.elementDecoder = elementDecoder;
     }
 
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
-      }
+    @Override
+    public byte alignment() {
+      return elementDecoder.alignment();
+    }
 
-      @Override
-      public Integer fixedSize() {
-        int position = 0;
-        for (var componentDecoder : componentDecoders) {
-          var fixedComponentSize = componentDecoder.fixedSize();
-          if (fixedComponentSize == null) {
-            return null;
-          }
+    @Override
+    @Nullable
+    Integer fixedSize() {
+      return null;
+    }
 
-          position = align(position, componentDecoder.alignment());
-          position += fixedComponentSize;
+    @Override
+    public List<U> decode(ByteBuffer byteSlice) {
+      List<U> elements;
+
+      var elementSize = elementDecoder.fixedSize();
+      if (elementSize != null) {
+        // A simple C-style array.
+        elements = new ArrayList<>(byteSlice.limit() / elementSize);
+        for (int i = 0; i < byteSlice.limit(); i += elementSize) {
+          var element = elementDecoder.decode(byteSlice.slice(i, elementSize));
+          elements.add(element);
         }
-
-        if (position == 0) {
-          return 1;
-        }
-
-        return align(position, alignment());
-      }
-
-      @Override
-      public U decode(ByteBuffer byteSlice) {
+      } else {
+        // An array with aligned elements and a vector of framing offsets in the end.
         int framingOffsetSize = byteCount(byteSlice.limit());
+        int lastFramingOffset =
+            getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize));
+        int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize;
 
-        var recordConstructorArguments = new Object[recordComponents.length];
-
+        elements = new ArrayList<>(elementCount);
         int position = 0;
-        int framingOffsetIndex = 0;
-        int componentIndex = 0;
-        for (var componentDecoder : componentDecoders) {
-          position = align(position, componentDecoder.alignment());
+        for (int i = 0; i < elementCount; i++) {
+          int framingOffset =
+              getIntN(
+                  byteSlice.slice(lastFramingOffset + i * framingOffsetSize, framingOffsetSize));
+          elements.add(elementDecoder.decode(byteSlice.slice(position, framingOffset - position)));
+          position = align(framingOffset, alignment());
+        }
+      }
 
-          var fixedComponentSize = componentDecoder.fixedSize();
-          if (fixedComponentSize != null) {
+      return elements;
+    }
+  }
+
+  private static class MaybeDecoder<U> extends Decoder<Optional<U>> {
+
+    private final Decoder<U> elementDecoder;
+
+    MaybeDecoder(Decoder<U> elementDecoder) {
+      this.elementDecoder = elementDecoder;
+    }
+
+    @Override
+    public byte alignment() {
+      return elementDecoder.alignment();
+    }
+
+    @Override
+    @Nullable
+    Integer fixedSize() {
+      return null;
+    }
+
+    @Override
+    public Optional<U> decode(ByteBuffer byteSlice) {
+      if (!byteSlice.hasRemaining()) {
+        return Optional.empty();
+      } else {
+        if (!elementDecoder.hasFixedSize()) {
+          // Remove trailing zero byte.
+          byteSlice.limit(byteSlice.limit() - 1);
+        }
+
+        return Optional.of(elementDecoder.decode(byteSlice));
+      }
+    }
+  }
+
+  private static class StructureDecoder<U extends Record> extends Decoder<U> {
+
+    private final RecordComponent[] recordComponents;
+    private final Class<U> recordType;
+    private final Decoder<?>[] componentDecoders;
+
+    StructureDecoder(Class<U> recordType, Decoder<?>... componentDecoders) {
+      var recordComponents = recordType.getRecordComponents();
+
+      if (componentDecoders.length != recordComponents.length) {
+        throw new IllegalArgumentException(
+            "number of decoders (%d) does not match number of structure components (%d)"
+                .formatted(componentDecoders.length, recordComponents.length));
+      }
+
+      this.recordComponents = recordComponents;
+      this.recordType = recordType;
+      this.componentDecoders = componentDecoders;
+    }
+
+    @Override
+    public byte alignment() {
+      return (byte) Arrays.stream(componentDecoders).mapToInt(Decoder::alignment).max().orElse(1);
+    }
+
+    @Override
+    public Integer fixedSize() {
+      int position = 0;
+      for (var componentDecoder : componentDecoders) {
+        var fixedComponentSize = componentDecoder.fixedSize();
+        if (fixedComponentSize == null) {
+          return null;
+        }
+
+        position = align(position, componentDecoder.alignment());
+        position += fixedComponentSize;
+      }
+
+      if (position == 0) {
+        return 1;
+      }
+
+      return align(position, alignment());
+    }
+
+    @Override
+    public U decode(ByteBuffer byteSlice) {
+      int framingOffsetSize = byteCount(byteSlice.limit());
+
+      var recordConstructorArguments = new Object[recordComponents.length];
+
+      int position = 0;
+      int framingOffsetIndex = 0;
+      int componentIndex = 0;
+      for (var componentDecoder : componentDecoders) {
+        position = align(position, componentDecoder.alignment());
+
+        var fixedComponentSize = componentDecoder.fixedSize();
+        if (fixedComponentSize != null) {
+          recordConstructorArguments[componentIndex] =
+              componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
+          position += fixedComponentSize;
+        } else {
+          if (componentIndex == recordComponents.length - 1) {
+            // The last component never has a framing offset.
+            int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
             recordConstructorArguments[componentIndex] =
-                componentDecoder.decode(byteSlice.slice(position, fixedComponentSize));
-            position += fixedComponentSize;
+                componentDecoder.decode(byteSlice.slice(position, endPosition - position));
+            position = endPosition;
           } else {
-            if (componentIndex == recordComponents.length - 1) {
-              // The last component never has a framing offset.
-              int endPosition = byteSlice.limit() - framingOffsetIndex * framingOffsetSize;
-              recordConstructorArguments[componentIndex] =
-                  componentDecoder.decode(byteSlice.slice(position, endPosition - position));
-              position = endPosition;
-            } else {
-              int framingOffset =
-                  getIntN(
-                      byteSlice.slice(
-                          byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
-                          framingOffsetSize));
-              recordConstructorArguments[componentIndex] =
-                  componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
-              position = framingOffset;
-              ++framingOffsetIndex;
-            }
+            int framingOffset =
+                getIntN(
+                    byteSlice.slice(
+                        byteSlice.limit() - (1 + framingOffsetIndex) * framingOffsetSize,
+                        framingOffsetSize));
+            recordConstructorArguments[componentIndex] =
+                componentDecoder.decode(byteSlice.slice(position, framingOffset - position));
+            position = framingOffset;
+            ++framingOffsetIndex;
           }
-
-          ++componentIndex;
         }
 
-        try {
-          var recordComponentTypes =
-              Arrays.stream(recordType.getRecordComponents())
-                  .map(RecordComponent::getType)
-                  .toArray(Class<?>[]::new);
-          var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
-          return recordConstructor.newInstance(recordConstructorArguments);
-        } catch (NoSuchMethodException
-            | InstantiationException
-            | IllegalAccessException
-            | InvocationTargetException e) {
-          throw new IllegalStateException(e);
-        }
+        ++componentIndex;
       }
-    };
+
+      try {
+        var recordComponentTypes =
+            Arrays.stream(recordType.getRecordComponents())
+                .map(RecordComponent::getType)
+                .toArray(Class<?>[]::new);
+        var recordConstructor = recordType.getDeclaredConstructor(recordComponentTypes);
+        return recordConstructor.newInstance(recordConstructorArguments);
+      } catch (NoSuchMethodException
+          | InstantiationException
+          | IllegalAccessException
+          | InvocationTargetException e) {
+        throw new IllegalStateException(e);
+      }
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the {@link Variant} type.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Variant> ofVariant() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 8;
-      }
+  private static class VariantDecoder extends Decoder<Variant> {
 
-      @Override
-      @Nullable
-      Integer fixedSize() {
-        return null;
-      }
+    @Override
+    public byte alignment() {
+      return 8;
+    }
 
-      @Override
-      public Variant decode(ByteBuffer byteSlice) {
-        // TODO
-        throw new UnsupportedOperationException("not implemented");
-      }
-    };
+    @Override
+    @Nullable
+    Integer fixedSize() {
+      return null;
+    }
+
+    @Override
+    public Variant decode(ByteBuffer byteSlice) {
+      // TODO
+      throw new UnsupportedOperationException("not implemented");
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the {@code Boolean} type.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Boolean> ofBoolean() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 1;
-      }
+  private static class BooleanDecoder extends Decoder<Boolean> {
 
-      @Override
-      public Integer fixedSize() {
-        return 1;
-      }
+    @Override
+    public byte alignment() {
+      return 1;
+    }
 
-      @Override
-      public Boolean decode(ByteBuffer byteSlice) {
-        return byteSlice.get() != 0;
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 1;
+    }
+
+    @Override
+    public Boolean decode(ByteBuffer byteSlice) {
+      return byteSlice.get() != 0;
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the 8-bit {@ode byte} type.
-   *
-   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
-   * result of this method.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Byte> ofByte() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 1;
-      }
+  private static class ByteDecoder extends Decoder<Byte> {
 
-      @Override
-      public Integer fixedSize() {
-        return 1;
-      }
+    @Override
+    public byte alignment() {
+      return 1;
+    }
 
-      @Override
-      public Byte decode(ByteBuffer byteSlice) {
-        return byteSlice.get();
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 1;
+    }
+
+    @Override
+    public Byte decode(ByteBuffer byteSlice) {
+      return byteSlice.get();
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the 16-bit {@code short} type.
-   *
-   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
-   * result of this method.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Short> ofShort() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 2;
-      }
+  private static class ShortDecoder extends Decoder<Short> {
 
-      @Override
-      public Integer fixedSize() {
-        return 2;
-      }
+    @Override
+    public byte alignment() {
+      return 2;
+    }
 
-      @Override
-      public Short decode(ByteBuffer byteSlice) {
-        return byteSlice.getShort();
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 2;
+    }
+
+    @Override
+    public Short decode(ByteBuffer byteSlice) {
+      return byteSlice.getShort();
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the 32-bit {@code int} type.
-   *
-   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
-   * result of this method.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Integer> ofInt() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 4;
-      }
+  private static class IntegerDecoder extends Decoder<Integer> {
 
-      @Override
-      public Integer fixedSize() {
-        return 4;
-      }
+    @Override
+    public byte alignment() {
+      return 4;
+    }
 
-      @Override
-      public Integer decode(ByteBuffer byteSlice) {
-        return byteSlice.getInt();
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 4;
+    }
+
+    @Override
+    public Integer decode(ByteBuffer byteSlice) {
+      return byteSlice.getInt();
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the 64-bit {@code long} type.
-   *
-   * <p><strong>Note:</strong> It is often useful to apply {@link #withByteOrder(ByteOrder)} to the
-   * result of this method.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Long> ofLong() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 8;
-      }
+  private static class LongDecoder extends Decoder<Long> {
 
-      @Override
-      public Integer fixedSize() {
-        return 8;
-      }
+    @Override
+    public byte alignment() {
+      return 8;
+    }
 
-      @Override
-      public Long decode(ByteBuffer byteSlice) {
-        return byteSlice.getLong();
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 8;
+    }
+
+    @Override
+    public Long decode(ByteBuffer byteSlice) {
+      return byteSlice.getLong();
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the {@code double} type.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<Double> ofDouble() {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 8;
-      }
+  private static class DoubleDecoder extends Decoder<Double> {
 
-      @Override
-      public Integer fixedSize() {
-        return 8;
-      }
+    @Override
+    public byte alignment() {
+      return 8;
+    }
 
-      @Override
-      public Double decode(ByteBuffer byteSlice) {
-        return byteSlice.getDouble();
-      }
-    };
+    @Override
+    public Integer fixedSize() {
+      return 8;
+    }
+
+    @Override
+    public Double decode(ByteBuffer byteSlice) {
+      return byteSlice.getDouble();
+    }
   }
 
-  /**
-   * Creates a {@link Decoder} for the {@link String} type.
-   *
-   * <p><strong>Note:</strong> While GVariant does not prescribe any particular encoding, {@link
-   * java.nio.charset.StandardCharsets#UTF_8} is the most common choice.
-   *
-   * @return a new {@link Decoder}.
-   */
-  public static Decoder<String> ofString(Charset charset) {
-    return new Decoder<>() {
-      @Override
-      public byte alignment() {
-        return 1;
-      }
+  private static class StringDecoder extends Decoder<String> {
 
-      @Override
-      @Nullable
-      Integer fixedSize() {
-        return null;
-      }
+    private final Charset charset;
 
-      @Override
-      public String decode(ByteBuffer byteSlice) {
-        byteSlice.limit(byteSlice.limit() - 1);
-        return charset.decode(byteSlice).toString();
-      }
-    };
+    public StringDecoder(Charset charset) {
+      this.charset = charset;
+    }
+
+    @Override
+    public byte alignment() {
+      return 1;
+    }
+
+    @Override
+    @Nullable
+    Integer fixedSize() {
+      return null;
+    }
+
+    @Override
+    public String decode(ByteBuffer byteSlice) {
+      byteSlice.limit(byteSlice.limit() - 1);
+      return charset.decode(byteSlice).toString();
+    }
   }
 }