text: /*!
      \file generator_java.cpp
      \brief Fast binary encoding Java generator implementation
      \author Ivan Shynkarenka
      \date 20.04.2018
      \copyright MIT License
  */
  
  #include "generator_java.h"
  
  namespace FBE {
  
  void GeneratorJava::Generate(const std::shared_ptr<Package>& package)
  {
      GenerateFBEPackage("fbe");
      GenerateFBEPair("fbe");
      GenerateFBEUUIDGenerator("fbe");
      GenerateFBEBuffer("fbe");
      GenerateFBEModel("fbe");
      GenerateFBEFieldModel("fbe");
      GenerateFBEFieldModel("fbe", "Boolean", "boolean", "", "1", "false");
      GenerateFBEFieldModel("fbe", "Byte", "byte", "", "1", "(byte)0");
      GenerateFBEFieldModel("fbe", "Char", "char", "(byte)", "1", "'\\u0000'");
      GenerateFBEFieldModel("fbe", "WChar", "char", "(int)", "4", "'\\u0000'");
      GenerateFBEFieldModel("fbe", "Int8", "byte", "", "1", "(byte)0");
      GenerateFBEFieldModel("fbe", "Int16", "short", "", "2", "(short)0");
      GenerateFBEFieldModel("fbe", "Int32", "int", "", "4", "0");
      GenerateFBEFieldModel("fbe", "Int64", "long", "", "8", "0l");
      GenerateFBEFieldModel("fbe", "Float", "float", "", "4", "0.0f");
      GenerateFBEFieldModel("fbe", "Double", "double", "", "8", "0.0d");
      GenerateFBEFieldModel("fbe", "UUID", "UUID", "", "16", "UUIDGenerator.nil()");
      GenerateFBEFieldModelDecimal("fbe");
      GenerateFBEFieldModelTimestamp("fbe");
      GenerateFBEFieldModelBytes("fbe");
      GenerateFBEFieldModelString("fbe");
      if (Final())
      {
          GenerateFBESize("fbe");
          GenerateFBEFinalModel("fbe");
          GenerateFBEFinalModel("fbe", "Boolean", "boolean", "", "1", "false");
          GenerateFBEFinalModel("fbe", "Byte", "byte", "", "1", "(byte)0");
          GenerateFBEFinalModel("fbe", "Char", "char", "(byte)", "1", "'\\u0000'");
          GenerateFBEFinalModel("fbe", "WChar", "char", "(int)", "4", "'\\u0000'");
          GenerateFBEFinalModel("fbe", "Int8", "byte", "", "1", "(byte)0");
          GenerateFBEFinalModel("fbe", "Int16", "short", "", "2", "(short)0");
          GenerateFBEFinalModel("fbe", "Int32", "int", "", "4", "0");
          GenerateFBEFinalModel("fbe", "Int64", "long", "", "8", "0l");
          GenerateFBEFinalModel("fbe", "Float", "float", "", "4", "0.0f");
          GenerateFBEFinalModel("fbe", "Double", "double", "", "8", "0.0d");
          GenerateFBEFinalModel("fbe", "UUID", "UUID", "", "16", "UUIDGenerator.nil()");
          GenerateFBEFinalModelDecimal("fbe");
          GenerateFBEFinalModelTimestamp("fbe");
          GenerateFBEFinalModelBytes("fbe");
          GenerateFBEFinalModelString("fbe");
      }
      if (Sender())
      {
          GenerateFBESender("fbe");
          GenerateFBEReceiver("fbe");
      }
      if (JSON())
          GenerateFBEJson("fbe");
  
      GeneratePackage(package);
  }
  
  void GeneratorJava::GenerateHeader(const std::string& source)
  {
      std::string code = R"CODE(// Automatically generated by the Fast Binary Encoding compiler, do not modify!
  // https://github.com/chronoxor/FastBinaryEncoding
  // Source: _INPUT_
  // Version: _VERSION_
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_INPUT_"), source);
      code = std::regex_replace(code, std::regex("_VERSION_"), version);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  }
  
  void GeneratorJava::GenerateFooter()
  {
  }
  
  void GeneratorJava::GenerateImports(const std::string& package)
  {
      // Generate package name
      WriteLine();
      WriteLineIndent("package " + package + ";");
  
      // Generate common import
      WriteLine();
      WriteLineIndent("import java.io.*;");
      WriteLineIndent("import java.lang.*;");
      WriteLineIndent("import java.lang.reflect.*;");
      WriteLineIndent("import java.math.*;");
      WriteLineIndent("import java.nio.ByteBuffer;");
      WriteLineIndent("import java.nio.charset.*;");
      WriteLineIndent("import java.time.*;");
      WriteLineIndent("import java.util.*;");
  }
  
  void GeneratorJava::GenerateImports(const std::shared_ptr<Package>& p)
  {
      // Generate common import
      GenerateImports(*p->name);
  
      // Generate FBE import
      WriteLine();
      WriteLineIndent("import fbe.*;");
  
      // Generate packages import
      if (p->import)
          for (const auto& import : p->import->imports)
              WriteLineIndent("import " + *import + ".*;");
  }
  
  void GeneratorJava::GenerateFBEPackage(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Create FBE package path
      CppCommon::Directory::CreateTree(path);
  }
  
  void GeneratorJava::GenerateFBEPair(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Pair.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding pair wrapper
  public final class Pair<K, V>
  {
      private final K key;
      private final V value;
  
      // Initialize the pair with given key and value
      public Pair(K key, V value)
      {
          this.key = key;
          this.value = value;
      }
  
      // Get the pair key
      public K getKey() { return key; }
      // Get the pair value
      public V getValue() { return value; }
  
      // Create a new pair
      public static <K, V> Pair<K, V> create(K key, V value) { return new Pair<K, V>(key, value); }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEUUIDGenerator(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "UUIDGenerator.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding UUID generator
  public final class UUIDGenerator
  {
      // Gregorian epoch
      private static final long GregorianEpoch = -12219292800000L;
  
      // Lock and random generator
      private static final Object _lock = new Object();
      private static final Random _generator = new Random();
  
      // Node & clock sequence bytes
      private static long _node = makeNode();
      private static long _nodeAndClockSequence = makeNodeAndClockSequence();
  
      // Last UUID generated timestamp
      private static long _last = GregorianEpoch;
  
      private static long makeNode()
      {
          return _generator.nextLong() | 0x0000010000000000L;
      }
  
      private static long makeNodeAndClockSequence()
      {
          long clock = _generator.nextLong();
  
          long lsb = 0;
          // Variant (2 bits)
          lsb |= 0x8000000000000000L;
          // Clock sequence (14 bits)
          lsb |= (clock & 0x0000000000003FFFL) << 48;
          // 6 bytes
          lsb |= _node;
          return lsb;
      }
  
      // Generate nil UUID0 (all bits set to zero)
      public static UUID nil() { return new UUID(0, 0); }
  
      // Generate sequential UUID1 (time based version)
      public static UUID sequential()
      {
          long now = System.currentTimeMillis();
  
          // Generate new clock sequence bytes to get rid of UUID duplicates
          synchronized(_lock)
          {
              if (now <= _last)
                  _nodeAndClockSequence = makeNodeAndClockSequence();
              _last = now;
          }
  
          long nanosSince = (now - GregorianEpoch) * 10000;
  
          long msb = 0L;
          msb |= (0x00000000FFFFFFFFL & nanosSince) << 32;
          msb |= (0x0000FFFF00000000L & nanosSince) >>> 16;
          msb |= (0xFFFF000000000000L & nanosSince) >>> 48;
          // Sets the version to 1
          msb |= 0x0000000000001000L;
  
          return new UUID(msb, _nodeAndClockSequence);
      }
  
      // Generate random UUID4 (randomly or pseudo-randomly generated version)
      public static UUID random() { return UUID.randomUUID(); }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEBuffer(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Buffer.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding buffer based on dynamic byte array
  public class Buffer
  {
      private byte[] _data;
      private long _size;
      private long _offset;
  
      // Is the buffer empty?
      public boolean isEmpty() { return (_data == null) || (_size == 0); }
      // Get bytes memory buffer
      public byte[] getData() { return _data; }
      // Get bytes memory buffer capacity
      public long getCapacity() { return _data.length; }
      // Get bytes memory buffer size
      public long getSize() { return _size; }
      // Get bytes memory buffer offset
      public long getOffset() { return _offset; }
  
      // Initialize a new expandable buffer with zero capacity
      public Buffer() { attach(); }
      // Initialize a new expandable buffer with the given capacity
      public Buffer(long capacity) { attach(capacity); }
      // Initialize a new buffer based on the specified byte array
      public Buffer(byte[] buffer) { attach(buffer); }
      // Initialize a new buffer based on the specified region (offset) of a byte array
      public Buffer(byte[] buffer, long offset) { attach(buffer, offset); }
      // Initialize a new buffer based on the specified region (size and offset) of a byte array
      public Buffer(byte[] buffer, long size, long offset) { attach(buffer, size, offset); }
  
      // Attach memory buffer methods
      public void attach() { _data = new byte[0]; _size = 0; _offset = 0; }
      public void attach(long capacity) { _data = new byte[(int)capacity]; _size = 0; _offset = 0; }
      public void attach(byte[] buffer) { _data = buffer; _size = buffer.length; _offset = 0; }
      public void attach(byte[] buffer, long offset) { _data = buffer; _size = buffer.length; _offset = offset; }
      public void attach(byte[] buffer, long size, long offset) { _data = buffer; _size = size; _offset = offset; }
  
      // Allocate memory in the current buffer and return offset to the allocated memory block
      public long allocate(long size)
      {
          assert (size >= 0) : "Invalid allocation size!";
          if (size < 0)
              throw new IllegalArgumentException("Invalid allocation size!");
  
          long offset = _size;
  
          // Calculate a new buffer size
          long total = _size + size;
  
          if (total <= _data.length)
          {
              _size = total;
              return offset;
          }
  
          byte[] data = new byte[(int)Math.max(total, 2 * _data.length)];
          System.arraycopy(_data, 0, data, 0, (int)_size);
          _data = data;
          _size = total;
          return offset;
      }
  
      // Remove some memory of the given size from the current buffer
      public void remove(long offset, long size)
      {
          assert ((offset + size) <= _size) : "Invalid offset & size!";
          if ((offset + size) > _size)
              throw new IllegalArgumentException("Invalid offset & size!");
  
          System.arraycopy(_data, (int)(offset + size), _data, (int)offset, (int)(_size - size - offset));
          _size -= size;
          if (_offset >= (offset + size))
              _offset -= size;
          else if (_offset >= offset)
          {
              _offset -= _offset - offset;
              if (_offset > _size)
                  _offset = _size;
          }
      }
  
      // Reserve memory of the given capacity in the current buffer
      public void reserve(long capacity)
      {
          assert (capacity >= 0) : "Invalid reserve capacity!";
          if (capacity < 0)
              throw new IllegalArgumentException("Invalid reserve capacity!");
  
          if (capacity > _data.length)
          {
              byte[] data = new byte[(int)Math.max(capacity, 2 * _data.length)];
              System.arraycopy(_data, 0, data, 0, (int)_size);
              _data = data;
          }
      }
  
      // Resize the current buffer
      public void resize(long size)
      {
          reserve(size);
          _size = size;
          if (_offset > _size)
              _offset = _size;
      }
  
      // Reset the current buffer and its offset
      public void reset()
      {
          _size = 0;
          _offset = 0;
      }
  
      // Shift the current buffer offset
      public void shift(long offset) { _offset += offset; }
      // Unshift the current buffer offset
      public void unshift(long offset) { _offset -= offset; }
  
      // Buffer I/O methods
  
      public static boolean readBoolean(byte[] buffer, long offset)
      {
          return buffer[(int)offset] != 0;
      }
  
      public static byte readByte(byte[] buffer, long offset)
      {
          return buffer[(int)offset];
      }
  
      public static char readChar(byte[] buffer, long offset)
      {
          return (char)readInt8(buffer, offset);
      }
  
      public static char readWChar(byte[] buffer, long offset)
      {
          return (char)readInt32(buffer, offset);
      }
  
      public static byte readInt8(byte[] buffer, long offset)
      {
          return buffer[(int)offset];
      }
  
      public static short readInt16(byte[] buffer, long offset)
      {
          return (short)(((buffer[(int)offset + 0] & 0xFF) << 0) | ((buffer[(int)offset + 1] & 0xFF) << 8));
      }
  
      public static int readInt32(byte[] buffer, long offset)
      {
          return ((buffer[(int)offset + 0] & 0xFF) <<  0)|
                 ((buffer[(int)offset + 1] & 0xFF) <<  8)|
                 ((buffer[(int)offset + 2] & 0xFF) << 16)|
                 ((buffer[(int)offset + 3] & 0xFF) << 24);
      }
  
      public static long readInt64(byte[] buffer, long offset)
      {
          return (((long)buffer[(int)offset + 0] & 0xFF) <<  0)|
                 (((long)buffer[(int)offset + 1] & 0xFF) <<  8)|
                 (((long)buffer[(int)offset + 2] & 0xFF) << 16)|
                 (((long)buffer[(int)offset + 3] & 0xFF) << 24)|
                 (((long)buffer[(int)offset + 4] & 0xFF) << 32)|
                 (((long)buffer[(int)offset + 5] & 0xFF) << 40)|
                 (((long)buffer[(int)offset + 6] & 0xFF) << 48)|
                 (((long)buffer[(int)offset + 7] & 0xFF) << 56);
      }
  
      private static long readInt64BE(byte[] buffer, long offset)
      {
          return (((long)buffer[(int)offset + 0] & 0xFF) << 56)|
                 (((long)buffer[(int)offset + 1] & 0xFF) << 48)|
                 (((long)buffer[(int)offset + 2] & 0xFF) << 40)|
                 (((long)buffer[(int)offset + 3] & 0xFF) << 32)|
                 (((long)buffer[(int)offset + 4] & 0xFF) << 24)|
                 (((long)buffer[(int)offset + 5] & 0xFF) << 16)|
                 (((long)buffer[(int)offset + 6] & 0xFF) <<  8)|
                 (((long)buffer[(int)offset + 7] & 0xFF) <<  0);
      }
  
      public static float readFloat(byte[] buffer, long offset)
      {
          int bits = readInt32(buffer, offset);
          return Float.intBitsToFloat(bits);
      }
  
      public static double readDouble(byte[] buffer, long offset)
      {
          long bits = readInt64(buffer, offset);
          return Double.longBitsToDouble(bits);
      }
  
      public static byte[] readBytes(byte[] buffer, long offset, long size)
      {
          byte[] result = new byte[(int)size];
          System.arraycopy(buffer, (int)offset, result, 0, (int)size);
          return result;
      }
  
      public static String readString(byte[] buffer, long offset, long size)
      {
          return new String(buffer, (int)offset, (int)size, StandardCharsets.UTF_8);
      }
  
      public static UUID readUUID(byte[] buffer, long offset)
      {
          return new UUID(readInt64BE(buffer, offset), readInt64BE(buffer, offset + 8));
      }
  
      public static void write(byte[] buffer, long offset, boolean value)
      {
          buffer[(int)offset] = (byte)(value ? 1 : 0);
      }
  
      public static void write(byte[] buffer, long offset, byte value)
      {
          buffer[(int)offset] = value;
      }
  
      public static void write(byte[] buffer, long offset, short value)
      {
          buffer[(int)offset + 0] = (byte)(value >>  0);
          buffer[(int)offset + 1] = (byte)(value >>  8);
      }
  
      public static void write(byte[] buffer, long offset, int value)
      {
          buffer[(int)offset + 0] = (byte)(value >>  0);
          buffer[(int)offset + 1] = (byte)(value >>  8);
          buffer[(int)offset + 2] = (byte)(value >> 16);
          buffer[(int)offset + 3] = (byte)(value >> 24);
      }
  
      public static void write(byte[] buffer, long offset, long value)
      {
          buffer[(int)offset + 0] = (byte)(value >>  0);
          buffer[(int)offset + 1] = (byte)(value >>  8);
          buffer[(int)offset + 2] = (byte)(value >> 16);
          buffer[(int)offset + 3] = (byte)(value >> 24);
          buffer[(int)offset + 4] = (byte)(value >> 32);
          buffer[(int)offset + 5] = (byte)(value >> 40);
          buffer[(int)offset + 6] = (byte)(value >> 48);
          buffer[(int)offset + 7] = (byte)(value >> 56);
      }
  
      private static void writeBE(byte[] buffer, long offset, long value)
      {
          buffer[(int)offset + 0] = (byte)(value >> 56);
          buffer[(int)offset + 1] = (byte)(value >> 48);
          buffer[(int)offset + 2] = (byte)(value >> 40);
          buffer[(int)offset + 3] = (byte)(value >> 32);
          buffer[(int)offset + 4] = (byte)(value >> 24);
          buffer[(int)offset + 5] = (byte)(value >> 16);
          buffer[(int)offset + 6] = (byte)(value >>  8);
          buffer[(int)offset + 7] = (byte)(value >>  0);
      }
  
      public static void write(byte[] buffer, long offset, float value)
      {
          int bits = Float.floatToIntBits(value);
          write(buffer, offset, bits);
      }
  
      public static void write(byte[] buffer, long offset, double value)
      {
          long bits = Double.doubleToLongBits(value);
          write(buffer, offset, bits);
      }
  
      public static void write(byte[] buffer, long offset, byte[] value)
      {
          System.arraycopy(value, 0, buffer, (int)offset, value.length);
      }
  
      public static void write(byte[] buffer, long offset, byte[] value, long valueOffset, long valueSize)
      {
          System.arraycopy(value, (int)valueOffset, buffer, (int)offset, (int)valueSize);
      }
  
      public static void write(byte[] buffer, long offset, byte value, long valueCount)
      {
          for (long i = 0; i < valueCount; i++)
              buffer[(int)(offset + i)] = value;
      }
  
      public static void write(byte[] buffer, long offset, UUID value)
      {
          writeBE(buffer, offset, value.getMostSignificantBits());
          writeBE(buffer, offset + 8, value.getLeastSignificantBits());
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEModel(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Model.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding base model
  public class Model
  {
      private Buffer _buffer;
  
      // Get bytes buffer
      public Buffer getBuffer() { return _buffer; }
  
      // Initialize a new model
      protected Model() { _buffer = new Buffer(); }
      protected Model(Buffer buffer) { _buffer = buffer; }
  
      // Attach memory buffer methods
      public void attach() { _buffer.attach(); }
      public void attach(long capacity) { _buffer.attach(capacity); }
      public void attach(byte[] buffer) { _buffer.attach(buffer); }
      public void attach(byte[] buffer, long offset) { _buffer.attach(buffer, offset); }
      public void attach(byte[] buffer, long size, long offset) { _buffer.attach(buffer, size, offset); }
      public void attach(Buffer buffer) { _buffer.attach(buffer.getData(), buffer.getSize(), buffer.getOffset()); }
      public void attach(Buffer buffer, long offset) { _buffer.attach(buffer.getData(), buffer.getSize(), offset); }
  
      // Model buffer operations
      public long allocate(long size) { return _buffer.allocate(size); }
      public void remove(long offset, long size) { _buffer.remove(offset, size); }
      public void reserve(long capacity) { _buffer.reserve(capacity); }
      public void resize(long size) { _buffer.resize(size); }
      public void reset() { _buffer.reset(); }
      public void shift(long offset) { _buffer.shift(offset); }
      public void unshift(long offset) { _buffer.unshift(offset); }
  
      // Buffer I/O methods
      protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
      protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModel(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FieldModel.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding base field model
  public abstract class FieldModel
  {
      protected Buffer _buffer;
      protected long _offset;
  
      // Get the field offset
      public long fbeOffset() { return _offset; }
      // Set the field offset
      public void fbeOffset(long value) { _offset = value; }
  
      // Get the field size
      public long fbeSize() { return 0; }
      // Get the field extra size
      public long fbeExtra() { return 0; }
  
      // Shift the current field offset
      public void fbeShift(long size) { _offset += size; }
      // Unshift the current field offset
      public void fbeUnshift(long size) { _offset -= size; }
  
      // Initialize a new field model
      protected FieldModel(Buffer buffer, long offset)
      {
          _buffer = buffer;
          _offset = offset;
      }
  
      // Check if the value is valid
      public boolean verify() { return true; }
  
      // Buffer I/O methods
      protected boolean readBoolean(long offset) { return Buffer.readBoolean(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte readByte(long offset) { return Buffer.readByte(_buffer.getData(), _buffer.getOffset() + offset); }
      protected char readChar(long offset) { return Buffer.readChar(_buffer.getData(), _buffer.getOffset() + offset); }
      protected char readWChar(long offset) { return Buffer.readWChar(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte readInt8(long offset) { return Buffer.readInt8(_buffer.getData(), _buffer.getOffset() + offset); }
      protected short readInt16(long offset) { return Buffer.readInt16(_buffer.getData(), _buffer.getOffset() + offset); }
      protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
      protected long readInt64(long offset) { return Buffer.readInt64(_buffer.getData(), _buffer.getOffset() + offset); }
      protected float readFloat(long offset) { return Buffer.readFloat(_buffer.getData(), _buffer.getOffset() + offset); }
      protected double readDouble(long offset) { return Buffer.readDouble(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte[] readBytes(long offset, long size) { return Buffer.readBytes(_buffer.getData(), _buffer.getOffset() + offset, size); }
      protected String readString(long offset, long size) { return Buffer.readString(_buffer.getData(), _buffer.getOffset() + offset, size); }
      protected UUID readUUID(long offset) { return Buffer.readUUID(_buffer.getData(), _buffer.getOffset() + offset); }
      protected void write(long offset, boolean value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, short value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, long value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, float value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, double value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte[] value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte[] value, long valueOffset, long valueSize) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueOffset, valueSize); }
      protected void write(long offset, byte value, long valueCount) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueCount); }
      protected void write(long offset, UUID value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModel(const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / ("FieldModel" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding _TYPE_ field model
  public final class FieldModel_NAME_ extends FieldModel
  {
      public FieldModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return _SIZE_; }
  
      // Get the value
      public _TYPE_ get() { return get(_DEFAULTS_); }
      public _TYPE_ get(_TYPE_ defaults)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          return read_NAME_(fbeOffset());
      }
  
      // Set the value
      public void set(_TYPE_ value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          write(fbeOffset(), _BASE_value);
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_BASE_"), base);
      code = std::regex_replace(code, std::regex("_SIZE_"), size);
      code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelDecimal(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FieldModelDecimal.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding decimal field model
  public final class FieldModelDecimal extends FieldModel
  {
      public FieldModelDecimal(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return 16; }
  
      // Get the decimal value
      public BigDecimal get() { return get(BigDecimal.valueOf(0L)); }
      public BigDecimal get(BigDecimal defaults)
      {
          assert (defaults != null) : "Invalid default decimal value!";
          if (defaults == null)
              throw new IllegalArgumentException("Invalid default decimal value!");
  
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          byte[] magnitude = readBytes(fbeOffset(), 12);
          int scale = readByte(fbeOffset() + 14);
          int signum = (readByte(fbeOffset() + 15) < 0) ? -1 : 1;
  
          // Reverse magnitude
          for(int i = 0; i < magnitude.length / 2; i++)
          {
              byte temp = magnitude[i];
              magnitude[i] = magnitude[magnitude.length - i - 1];
              magnitude[magnitude.length - i - 1] = temp;
          }
  
          var unscaled = new BigInteger(signum, magnitude);
  
          return new BigDecimal(unscaled, scale);
      }
  
      // Set the decimal value
      public void set(BigDecimal value)
      {
          assert (value != null) : "Invalid decimal value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid decimal value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          // Get unscaled absolute value
          BigInteger unscaled = value.abs().unscaledValue();
          int bitLength = unscaled.bitLength();
          if ((bitLength < 0) || (bitLength > 96))
          {
              // Value too big for .NET Decimal (bit length is limited to [0, 96])
              write(fbeOffset(), (byte)0, fbeSize());
              return;
          }
  
          // Get byte array
          byte[] unscaledBytes = unscaled.toByteArray();
  
          // Get scale
          int scale = value.scale();
          if ((scale < 0) || (scale > 28))
          {
              // Value scale exceeds .NET Decimal limit of [0, 28]
              write(fbeOffset(), (byte)0, fbeSize());
              return;
          }
  
          // Write unscaled value to bytes 0-11
          int index = 0;
          for (int i = unscaledBytes.length - 1; (i >= 0) && (index < 12); i--, index++)
              write(fbeOffset() + index, unscaledBytes[i]);
  
          // Fill remaining bytes with zeros
          for (; index < 14; index++)
              write(fbeOffset() + index, (byte)0);
  
          // Write scale at byte 14
          write(fbeOffset() + 14, (byte)scale);
  
          // Write signum at byte 15
          write(fbeOffset() + 15, (byte)((value.signum() < 0) ? -128 : 0));
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelTimestamp(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FieldModelTimestamp.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding timestamp field model
  public final class FieldModelTimestamp extends FieldModel
  {
      public FieldModelTimestamp(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return 8; }
  
      // Get the timestamp value
      public Instant get() { return get(Instant.EPOCH); }
      public Instant get(Instant defaults)
      {
          assert (defaults != null) : "Invalid default timestamp value!";
          if (defaults == null)
              throw new IllegalArgumentException("Invalid default timestamp value!");
  
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          long nanoseconds = readInt64(fbeOffset());
          return Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
      }
  
      // Set the timestamp value
      public void set(Instant value)
      {
          assert (value != null) : "Invalid timestamp value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid timestamp value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          long nanoseconds = value.getEpochSecond() * 1000000000 + value.getNano();
          write(fbeOffset(), nanoseconds);
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelBytes(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FieldModelBytes.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding bytes field model
  public final class FieldModelBytes extends FieldModel
  {
      public FieldModelBytes(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return 4; }
      // Get the field extra size
      @Override
      public long fbeExtra()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeBytesOffset = readInt32(fbeOffset());
          if ((fbeBytesOffset == 0) || ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeBytesSize = readInt32(fbeBytesOffset);
          return 4 + fbeBytesSize;
      }
  
      // Check if the bytes value is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return true;
  
          int fbeBytesOffset = readInt32(fbeOffset());
          if (fbeBytesOffset == 0)
              return true;
  
          if ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize())
              return false;
  
          int fbeBytesSize = readInt32(fbeBytesOffset);
          if ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize())
              return false;
  
          return true;
      }
  
      // Get the bytes value
      public ByteBuffer get() { return get(ByteBuffer.allocate(0)); }
      public ByteBuffer get(ByteBuffer defaults)
      {
          assert (defaults != null) : "Invalid default bytes value!";
          if (defaults == null)
              throw new IllegalArgumentException("Invalid default bytes value!");
  
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          int fbeBytesOffset = readInt32(fbeOffset());
          if (fbeBytesOffset == 0)
              return defaults;
  
          assert ((_buffer.getOffset() + fbeBytesOffset + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeBytesOffset + 4) > _buffer.getSize())
              return defaults;
  
          int fbeBytesSize = readInt32(fbeBytesOffset);
          assert ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize())
              return defaults;
  
          return ByteBuffer.wrap(readBytes(fbeBytesOffset + 4, fbeBytesSize));
      }
  
      // Set the bytes value
      public void set(ByteBuffer value)
      {
          assert (value != null) : "Invalid bytes value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid bytes value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          int fbeBytesSize = value.array().length;
          int fbeBytesOffset = (int)(_buffer.allocate(4 + fbeBytesSize) - _buffer.getOffset());
          assert ((fbeBytesOffset > 0) && ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.getSize())) : "Model is broken!";
          if ((fbeBytesOffset <= 0) || ((_buffer.getOffset() + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.getSize()))
              return;
  
          write(fbeOffset(), fbeBytesOffset);
          write(fbeBytesOffset, fbeBytesSize);
          write(fbeBytesOffset + 4, value.array());
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelString(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FieldModelString.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding string field model
  public final class FieldModelString extends FieldModel
  {
      public FieldModelString(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return 4; }
      // Get the field extra size
      @Override
      public long fbeExtra()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeStringOffset = readInt32(fbeOffset());
          if ((fbeStringOffset == 0) || ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeStringSize = readInt32(fbeStringOffset);
          return 4 + fbeStringSize;
      }
  
      // Check if the string value is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return true;
  
          int fbeStringOffset = readInt32(fbeOffset());
          if (fbeStringOffset == 0)
              return true;
  
          if ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize())
              return false;
  
          int fbeStringSize = readInt32(fbeStringOffset);
          if ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize())
              return false;
  
          return true;
      }
  
      // Get the string value
      public String get() { return get(""); }
      public String get(String defaults)
      {
          assert (defaults != null) : "Invalid default string value!";
          if (defaults == null)
              throw new IllegalArgumentException("Invalid default string value!");
  
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          int fbeStringOffset = readInt32(fbeOffset());
          if (fbeStringOffset == 0)
              return defaults;
  
          assert ((_buffer.getOffset() + fbeStringOffset + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeStringOffset + 4) > _buffer.getSize())
              return defaults;
  
          int fbeStringSize = readInt32(fbeStringOffset);
          assert ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize())
              return defaults;
  
          return readString(fbeStringOffset + 4, fbeStringSize);
      }
  
      // Set the string value
      public void set(String value)
      {
          assert (value != null) : "Invalid string value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid string value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
  
          int fbeStringSize = bytes.length;
          int fbeStringOffset = (int)(_buffer.allocate(4 + fbeStringSize) - _buffer.getOffset());
          assert ((fbeStringOffset > 0) && ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) <= _buffer.getSize())) : "Model is broken!";
          if ((fbeStringOffset <= 0) || ((_buffer.getOffset() + fbeStringOffset + 4 + fbeStringSize) > _buffer.getSize()))
              return;
  
          write(fbeOffset(), fbeStringOffset);
          write(fbeStringOffset, fbeStringSize);
          write(fbeStringOffset + 4, bytes);
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelOptional(const std::string& package, const std::string& name, const std::string& type, const std::string& model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModelOptional" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding optional _NAME_ field model
  public final class FieldModelOptional_NAME_ extends FieldModel
  {
      public FieldModelOptional_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          value = new _MODEL_(buffer, 0);
      }
  
      // Get the field size
      @Override
      public long fbeSize() { return 1 + 4; }
      // Get the field extra size
      @Override
      public long fbeExtra()
      {
          if (!hasValue())
              return 0;
  
          int fbeOptionalOffset = readInt32(fbeOffset() + 1);
          if ((fbeOptionalOffset == 0) || ((_buffer.getOffset() + fbeOptionalOffset + 4) > _buffer.getSize()))
              return 0;
  
          _buffer.shift(fbeOptionalOffset);
          long fbeResult = value.fbeSize() + value.fbeExtra();
          _buffer.unshift(fbeOptionalOffset);
          return fbeResult;
      }
  
      // Checks if the object contains a value
      public boolean hasValue()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return false;
  
          byte fbeHasValue = readInt8(fbeOffset());
          return (fbeHasValue != 0);
      }
  
      // Base field model value
      public final _MODEL_ value;
  
      // Check if the optional value is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return true;
  
          byte fbeHasValue = readInt8(fbeOffset());
          if (fbeHasValue == 0)
              return true;
  
          int fbeOptionalOffset = readInt32(fbeOffset() + 1);
          if (fbeOptionalOffset == 0)
              return false;
  
          _buffer.shift(fbeOptionalOffset);
          boolean fbeResult = value.verify();
          _buffer.unshift(fbeOptionalOffset);
          return fbeResult;
      }
  
      // Get the optional value (being phase)
      public long getBegin()
      {
          if (!hasValue())
              return 0;
  
          int fbeOptionalOffset = readInt32(fbeOffset() + 1);
          assert (fbeOptionalOffset > 0) : "Model is broken!";
          if (fbeOptionalOffset <= 0)
              return 0;
  
          _buffer.shift(fbeOptionalOffset);
          return fbeOptionalOffset;
      }
  
      // Get the optional value (end phase)
      public void getEnd(long fbeBegin)
      {
          _buffer.unshift(fbeBegin);
      }
  
      // Get the optional value
      public _TYPE_ get() { return get(null); }
      public _TYPE_ get(_TYPE_ defaults)
      {
          long fbeBegin = getBegin();
          if (fbeBegin == 0)
              return defaults;
  
          _TYPE_ optional = value.get();
          getEnd(fbeBegin);
          return optional;
      }
  
      // Set the optional value (begin phase)
      public long setBegin(boolean hasValue)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          byte fbeHasValue = (byte)(hasValue ? 1 : 0);
          write(fbeOffset(), fbeHasValue);
          if (fbeHasValue == 0)
              return 0;
  
          int fbeOptionalSize = (int)value.fbeSize();
          int fbeOptionalOffset = (int)(_buffer.allocate(fbeOptionalSize) - _buffer.getOffset());
          assert ((fbeOptionalOffset > 0) && ((_buffer.getOffset() + fbeOptionalOffset + fbeOptionalSize) <= _buffer.getSize())) : "Model is broken!";
          if ((fbeOptionalOffset <= 0) || ((_buffer.getOffset() + fbeOptionalOffset + fbeOptionalSize) > _buffer.getSize()))
              return 0;
  
          write(fbeOffset() + 1, fbeOptionalOffset);
  
          _buffer.shift(fbeOptionalOffset);
          return fbeOptionalOffset;
      }
  
      // Set the optional value (end phase)
      public void setEnd(long fbeBegin)
      {
          _buffer.unshift(fbeBegin);
      }
  
      // Set the optional value
      public void set(_TYPE_ optional)
      {
          long fbeBegin = setBegin(optional != null);
          if (fbeBegin == 0)
              return;
  
          value.set(optional);
          setEnd(fbeBegin);
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelArray(const std::string& package, const std::string& name, const std::string& type, const std::string& model, bool bytes)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModelArray" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ array field model
  public final class FieldModelArray_NAME_ extends FieldModel
  {
      private final _MODEL_ _model;
      private final long _size;
  
      public FieldModelArray_NAME_(Buffer buffer, long offset, long size)
      {
          super(buffer, offset);
          _model = new _MODEL_(buffer, offset);
          _size = size;
      }
  
      // Get the field size
      @Override
      public long fbeSize() { return _size * _model.fbeSize(); }
      // Get the field extra size
      @Override
      public long fbeExtra() { return 0; }
  
      // Get the array offset
      public long getOffset() { return 0; }
      // Get the array size
      public long getSize() { return _size; }
  
      // Array index operator
      public _MODEL_ getItem(long index)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          assert (index < _size) : "Index is out of bounds!";
  
          _model.fbeOffset(fbeOffset());
          _model.fbeShift(index * _model.fbeSize());
          return _model;
      }
  
      // Check if the array is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return false;
  
          _model.fbeOffset(fbeOffset());
          for (long i = _size; i-- > 0;)
          {
              if (!_model.verify())
                  return false;
              _model.fbeShift(_model.fbeSize());
          }
  
          return true;
      }
  
      // Get the array
      public _ARRAY_ get()
      {
          var values = _INIT_;
  
          var fbeModel = getItem(0);
          for (long i = 0; i < _size; i++)
          {
              values[(int)i] = fbeModel.get();
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
          return values;
      }
  
      // Get the array
      public void get(_ARRAY_ values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          var fbeModel = getItem(0);
          for (long i = 0; (i < values.length) && (i < _size); i++)
          {
              values[(int)i] = fbeModel.get();
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Get the array as ArrayList
      public void get(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
          values.ensureCapacity((int)_size);
  
          var fbeModel = getItem(0);
          for (long i = _size; i-- > 0;)
          {
              _TYPE_ value = fbeModel.get();
              values.add(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Set the array
      public void set(_ARRAY_ values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = getItem(0);
          for (long i = 0; (i < values.length) && (i < _size); i++)
          {
              fbeModel.set(values[(int)i]);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Set the array as List
      public void set(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = getItem(0);
          for (long i = 0; (i < values.size()) && (i < _size); i++)
          {
              fbeModel.set(values.get((int)i));
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("_ARRAY_"), (bytes ? "byte" : type) + "[]");
      code = std::regex_replace(code, std::regex("_INIT_"), ((type != "byte[]") ? ("new " + (bytes ? "byte" : type) + "[(int)_size]") : "new byte[(int)_size][]"));
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelVector(const std::string& package, const std::string& name, const std::string& type, const std::string& model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModelVector" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ vector field model
  public final class FieldModelVector_NAME_ extends FieldModel
  {
      private final _MODEL_ _model;
  
      public FieldModelVector_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          _model = new _MODEL_(buffer, offset);
      }
  
      // Get the field size
      @Override
      public long fbeSize() { return 4; }
      // Get the field extra size
      @Override
      public long fbeExtra()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeVectorOffset = readInt32(fbeOffset());
          if ((fbeVectorOffset == 0) || ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeVectorSize = readInt32(fbeVectorOffset);
  
          long fbeResult = 4;
          _model.fbeOffset(fbeVectorOffset + 4);
          for (int i = fbeVectorSize; i-- > 0;)
          {
              fbeResult += _model.fbeSize() + _model.fbeExtra();
              _model.fbeShift(_model.fbeSize());
          }
          return fbeResult;
      }
  
      // Get the vector offset
      public long getOffset()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeVectorOffset = readInt32(fbeOffset());
          return fbeVectorOffset;
      }
  
      // Get the vector size
      public long getSize()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeVectorOffset = readInt32(fbeOffset());
          if ((fbeVectorOffset == 0) || ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeVectorSize = readInt32(fbeVectorOffset);
          return fbeVectorSize;
      }
  
      // Vector index operator
      public _MODEL_ getItem(long index)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
  
          int fbeVectorOffset = readInt32(fbeOffset());
          assert ((fbeVectorOffset > 0) && ((_buffer.getOffset() + fbeVectorOffset + 4) <= _buffer.getSize())) : "Model is broken!";
  
          int fbeVectorSize = readInt32(fbeVectorOffset);
          assert (index < fbeVectorSize) : "Index is out of bounds!";
  
          _model.fbeOffset(fbeVectorOffset + 4);
          _model.fbeShift(index * _model.fbeSize());
          return _model;
      }
  
      // Resize the vector and get its first model
      public _MODEL_ resize(long size)
      {
          int fbeVectorSize = (int)(size * _model.fbeSize());
          int fbeVectorOffset = (int)(_buffer.allocate(4 + fbeVectorSize) - _buffer.getOffset());
          assert ((fbeVectorOffset > 0) && ((_buffer.getOffset() + fbeVectorOffset + 4) <= _buffer.getSize())) : "Model is broken!";
  
          write(fbeOffset(), fbeVectorOffset);
          write(fbeVectorOffset, (int)size);
          write(fbeVectorOffset + 4, (byte)0, fbeVectorSize);
  
          _model.fbeOffset(fbeVectorOffset + 4);
          return _model;
      }
  
      // Check if the vector is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return true;
  
          int fbeVectorOffset = readInt32(fbeOffset());
          if (fbeVectorOffset == 0)
              return true;
  
          if ((_buffer.getOffset() + fbeVectorOffset + 4) > _buffer.getSize())
              return false;
  
          int fbeVectorSize = readInt32(fbeVectorOffset);
  
          _model.fbeOffset(fbeVectorOffset + 4);
          for (int i = fbeVectorSize; i-- > 0;)
          {
              if (!_model.verify())
                  return false;
              _model.fbeShift(_model.fbeSize());
          }
  
          return true;
      }
  
      // Get the vector as ArrayList
      public void get(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          long fbeVectorSize = getSize();
          if (fbeVectorSize == 0)
              return;
  
          values.ensureCapacity((int)fbeVectorSize);
  
          var fbeModel = getItem(0);
          for (long i = fbeVectorSize; i-- > 0;)
          {
              _TYPE_ value = fbeModel.get();
              values.add(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Get the vector as LinkedList
      public void get(LinkedList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          long fbeVectorSize = getSize();
          if (fbeVectorSize == 0)
              return;
  
          var fbeModel = getItem(0);
          for (long i = fbeVectorSize; i-- > 0;)
          {
              _TYPE_ value = fbeModel.get();
              values.add(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Get the vector as HashSet
      public void get(HashSet<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          long fbeVectorSize = getSize();
          if (fbeVectorSize == 0)
              return;
  
          var fbeModel = getItem(0);
          for (long i = fbeVectorSize; i-- > 0;)
          {
              _TYPE_ value = fbeModel.get();
              values.add(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Set the vector as ArrayList
      public void set(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = resize(values.size());
          for (var value : values)
          {
              fbeModel.set(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Set the vector as LinkedList
      public void set(LinkedList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = resize(values.size());
          for (var value : values)
          {
              fbeModel.set(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  
      // Set the vector as HashSet
      public void set(HashSet<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = resize(values.size());
          for (var value : values)
          {
              fbeModel.set(value);
              fbeModel.fbeShift(fbeModel.fbeSize());
          }
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelMap(const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModelMap" + key_name + value_name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map field model
  public final class FieldModelMap_KEY_NAME__VALUE_NAME_ extends FieldModel
  {
      private final _KEY_MODEL_ _modelKey;
      private final _VALUE_MODEL_ _modelValue;
  
      public FieldModelMap_KEY_NAME__VALUE_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          _modelKey = new _KEY_MODEL_(buffer, offset);
          _modelValue = new _VALUE_MODEL_(buffer, offset);
      }
  
      // Get the field size
      @Override
      public long fbeSize() { return 4; }
      // Get the field extra size
      @Override
      public long fbeExtra()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeMapOffset = readInt32(fbeOffset());
          if ((fbeMapOffset == 0) || ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeMapSize = readInt32(fbeMapOffset);
  
          long fbeResult = 4;
          _modelKey.fbeOffset(fbeMapOffset + 4);
          _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
          for (int i = fbeMapSize; i-- > 0;)
          {
              fbeResult += _modelKey.fbeSize() + _modelKey.fbeExtra();
              _modelKey.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
  
              fbeResult += _modelValue.fbeSize() + _modelValue.fbeExtra();
              _modelValue.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
          }
          return fbeResult;
      }
  
      // Get the map offset
      public long getOffset()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeMapOffset = readInt32(fbeOffset());
          return fbeMapOffset;
      }
  
      // Get the map size
      public long getSize()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          int fbeMapOffset = readInt32(fbeOffset());
          if ((fbeMapOffset == 0) || ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize()))
              return 0;
  
          int fbeMapSize = readInt32(fbeMapOffset);
          return fbeMapSize;
      }
  
      // Map index operator
      public Pair<_KEY_MODEL_, _VALUE_MODEL_> getItem(long index)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
  
          int fbeMapOffset = readInt32(fbeOffset());
          assert ((fbeMapOffset > 0) && ((_buffer.getOffset() + fbeMapOffset + 4) <= _buffer.getSize())) : "Model is broken!";
  
          int fbeMapSize = readInt32(fbeMapOffset);
          assert (index < fbeMapSize) : "Index is out of bounds!";
  
          _modelKey.fbeOffset(fbeMapOffset + 4);
          _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
          _modelKey.fbeShift(index * (_modelKey.fbeSize() + _modelValue.fbeSize()));
          _modelValue.fbeShift(index * (_modelKey.fbeSize() + _modelValue.fbeSize()));
          return Pair.create(_modelKey, _modelValue);
      }
  
      // Resize the map and get its first model
      public Pair<_KEY_MODEL_, _VALUE_MODEL_> resize(long size)
      {
          int fbeMapSize = (int)(size * (_modelKey.fbeSize() + _modelValue.fbeSize()));
          int fbeMapOffset = (int)(_buffer.allocate(4 + fbeMapSize) - _buffer.getOffset());
          assert ((fbeMapOffset > 0) && ((_buffer.getOffset() + fbeMapOffset + 4) <= _buffer.getSize())) : "Model is broken!";
  
          write(fbeOffset(), fbeMapOffset);
          write(fbeMapOffset, (int)size);
          write(fbeMapOffset + 4, (byte)0, fbeMapSize);
  
          _modelKey.fbeOffset(fbeMapOffset + 4);
          _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
          return Pair.create(_modelKey, _modelValue);
      }
  
      // Check if the map is valid
      @Override
      public boolean verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return true;
  
          int fbeMapOffset = readInt32(fbeOffset());
          if (fbeMapOffset == 0)
              return true;
  
          if ((_buffer.getOffset() + fbeMapOffset + 4) > _buffer.getSize())
              return false;
  
          int fbeMapSize = readInt32(fbeMapOffset);
  
          _modelKey.fbeOffset(fbeMapOffset + 4);
          _modelValue.fbeOffset(fbeMapOffset + 4 + _modelKey.fbeSize());
          for (int i = fbeMapSize; i-- > 0;)
          {
              if (!_modelKey.verify())
                  return false;
              _modelKey.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
              if (!_modelValue.verify())
                  return false;
              _modelValue.fbeShift(_modelKey.fbeSize() + _modelValue.fbeSize());
          }
  
          return true;
      }
  
      // Get the map as TreeMap
      public void get(TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          long fbeMapSize = getSize();
          if (fbeMapSize == 0)
              return;
  
          var fbeModel = getItem(0);
          for (long i = fbeMapSize; i-- > 0;)
          {
              _KEY_TYPE_ key = fbeModel.getKey().get();
              _VALUE_TYPE_ value = fbeModel.getValue().get();
              values.put(key, value);
              fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
              fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
          }
      }
  
      // Get the map as HashMap
      public void get(HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          long fbeMapSize = getSize();
          if (fbeMapSize == 0)
              return;
  
          var fbeModel = getItem(0);
          for (long i = fbeMapSize; i-- > 0;)
          {
              _KEY_TYPE_ key = fbeModel.getKey().get();
              _VALUE_TYPE_ value = fbeModel.getValue().get();
              values.put(key, value);
              fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
              fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
          }
      }
  
      // Set the map as TreeMap
      public void set(TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = resize(values.size());
          for (var value : values.entrySet())
          {
              fbeModel.getKey().set(value.getKey());
              fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
              fbeModel.getValue().set(value.getValue());
              fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
          }
      }
  
      // Set the map as HashMap
      public void set(HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          var fbeModel = resize(values.size());
          for (var value : values.entrySet())
          {
              fbeModel.getKey().set(value.getKey());
              fbeModel.getKey().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
              fbeModel.getValue().set(value.getValue());
              fbeModel.getValue().fbeShift(fbeModel.getKey().fbeSize() + fbeModel.getValue().fbeSize());
          }
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
      code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type);
      code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
      code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
      code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type);
      code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFieldModelEnumFlags(const std::string& package, const std::string& name, const std::string& type)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModel" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ field model
  public final class FieldModel_NAME_ extends FieldModel
  {
      public FieldModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the field size
      @Override
      public long fbeSize() { return _SIZE_; }
  
      // Get the value
      public _NAME_ get() { return get(new _NAME_()); }
      public _NAME_ get(_NAME_ defaults)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return defaults;
  
          return new _NAME_(_READ_(fbeOffset()));
      }
  
      // Set the value
      public void set(_NAME_ value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return;
  
          write(fbeOffset(), value.getRaw());
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
      code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBESize(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Size.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding size
  public class Size
  {
      public long value;
  
      // Initialize a new size
      public Size() { value = 0; }
      public Size(long size) { value = size; }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModel(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FinalModel.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding base final model
  public abstract class FinalModel
  {
      protected Buffer _buffer;
      protected long _offset;
  
      // Get the final offset
      public long fbeOffset() { return _offset; }
      // Set the final offset
      public void fbeOffset(long value) { _offset = value; }
  
      // Get the final size
      public long fbeSize() { return 0; }
      // Get the final extra size
      public long fbeExtra() { return 0; }
  
      // Shift the current final offset
      public void fbeShift(long size) { _offset += size; }
      // Unshift the current final offset
      public void fbeUnshift(long size) { _offset -= size; }
  
      // Initialize a new final model
      protected FinalModel(Buffer buffer, long offset)
      {
          _buffer = buffer;
          _offset = offset;
      }
  
      // Check if the value is valid
      public abstract long verify();
  
      // Buffer I/O methods
      protected boolean readBoolean(long offset) { return Buffer.readBoolean(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte readByte(long offset) { return Buffer.readByte(_buffer.getData(), _buffer.getOffset() + offset); }
      protected char readChar(long offset) { return Buffer.readChar(_buffer.getData(), _buffer.getOffset() + offset); }
      protected char readWChar(long offset) { return Buffer.readWChar(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte readInt8(long offset) { return Buffer.readInt8(_buffer.getData(), _buffer.getOffset() + offset); }
      protected short readInt16(long offset) { return Buffer.readInt16(_buffer.getData(), _buffer.getOffset() + offset); }
      protected int readInt32(long offset) { return Buffer.readInt32(_buffer.getData(), _buffer.getOffset() + offset); }
      protected long readInt64(long offset) { return Buffer.readInt64(_buffer.getData(), _buffer.getOffset() + offset); }
      protected float readFloat(long offset) { return Buffer.readFloat(_buffer.getData(), _buffer.getOffset() + offset); }
      protected double readDouble(long offset) { return Buffer.readDouble(_buffer.getData(), _buffer.getOffset() + offset); }
      protected byte[] readBytes(long offset, long size) { return Buffer.readBytes(_buffer.getData(), _buffer.getOffset() + offset, size); }
      protected String readString(long offset, long size) { return Buffer.readString(_buffer.getData(), _buffer.getOffset() + offset, size); }
      protected UUID readUUID(long offset) { return Buffer.readUUID(_buffer.getData(), _buffer.getOffset() + offset); }
      protected void write(long offset, boolean value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, short value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, int value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, long value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, float value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, double value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte[] value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
      protected void write(long offset, byte[] value, long valueOffset, long valueSize) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueOffset, valueSize); }
      protected void write(long offset, byte value, long valueCount) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value, valueCount); }
      protected void write(long offset, UUID value) { Buffer.write(_buffer.getData(), _buffer.getOffset() + offset, value); }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModel(const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / ("FinalModel" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding _TYPE_ final model
  public final class FinalModel_NAME_ extends FinalModel
  {
      public FinalModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(_TYPE_ value) { return fbeSize(); }
  
      // Get the final size
      @Override
      public long fbeSize() { return _SIZE_; }
  
      // Check if the value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return fbeSize();
      }
  
      // Get the value
      public _TYPE_ get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return _DEFAULTS_;
  
          size.value = fbeSize();
          return read_NAME_(fbeOffset());
      }
  
      // Set the value
      public long set(_TYPE_ value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), _BASE_value);
          return fbeSize();
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_BASE_"), base);
      code = std::regex_replace(code, std::regex("_SIZE_"), size);
      code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelDecimal(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FinalModelDecimal.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding decimal final model
  public final class FinalModelDecimal extends FinalModel
  {
      public FinalModelDecimal(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(BigDecimal value) { return fbeSize(); }
  
      // Get the final size
      @Override
      public long fbeSize() { return 16; }
  
      // Check if the decimal value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return fbeSize();
      }
  
      // Get the decimal value
      public BigDecimal get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return BigDecimal.valueOf(0L);
  
          byte[] magnitude = readBytes(fbeOffset(), 12);
          int scale = readByte(fbeOffset() + 14);
          int signum = (readByte(fbeOffset() + 15) < 0) ? -1 : 1;
  
          // Reverse magnitude
          for(int i = 0; i < magnitude.length / 2; i++)
          {
              byte temp = magnitude[i];
              magnitude[i] = magnitude[magnitude.length - i - 1];
              magnitude[magnitude.length - i - 1] = temp;
          }
  
          var unscaled = new BigInteger(signum, magnitude);
  
          size.value = fbeSize();
          return new BigDecimal(unscaled, scale);
      }
  
      // Set the decimal value
      public long set(BigDecimal value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          // Get unscaled absolute value
          BigInteger unscaled = value.abs().unscaledValue();
          int bitLength = unscaled.bitLength();
          if ((bitLength < 0) || (bitLength > 96))
          {
              // Value too big for .NET Decimal (bit length is limited to [0, 96])
              write(fbeOffset(), (byte)0, fbeSize());
              return fbeSize();
          }
  
          // Get byte array
          byte[] unscaledBytes = unscaled.toByteArray();
  
          // Get scale
          int scale = value.scale();
          if ((scale < 0) || (scale > 28))
          {
              // Value scale exceeds .NET Decimal limit of [0, 28]
              write(fbeOffset(), (byte)0, fbeSize());
              return fbeSize();
          }
  
          // Write unscaled value to bytes 0-11
          int index = 0;
          for (int i = unscaledBytes.length - 1; (i >= 0) && (index < 12); i--, index++)
              write(fbeOffset() + index, unscaledBytes[i]);
  
          // Fill remaining bytes with zeros
          for (; index < 14; index++)
              write(fbeOffset() + index, (byte)0);
  
          // Write scale at byte 14
          write(fbeOffset() + 14, (byte)scale);
  
          // Write signum at byte 15
          write(fbeOffset() + 15, (byte)((value.signum() < 0) ? -128 : 0));
          return fbeSize();
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelTimestamp(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FinalModelTimestamp.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding timestamp final model
  public final class FinalModelTimestamp extends FinalModel
  {
      public FinalModelTimestamp(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(Instant value) { return fbeSize(); }
  
      // Get the final size
      @Override
      public long fbeSize() { return 8; }
  
      // Check if the timestamp value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return fbeSize();
      }
  
      // Get the timestamp value
      public Instant get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return Instant.EPOCH;
  
          size.value = fbeSize();
          long nanoseconds = readInt64(fbeOffset());
          return Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
      }
  
      // Set the timestamp value
      public long set(Instant value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          long nanoseconds = value.getEpochSecond() * 1000000000 + value.getNano();
          write(fbeOffset(), nanoseconds);
          return fbeSize();
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelBytes(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FinalModelBytes.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding bytes final model
  public final class FinalModelBytes extends FinalModel
  {
      public FinalModelBytes(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(ByteBuffer value) { return 4 + value.array().length; }
  
      // Check if the bytes value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          int fbeBytesSize = readInt32(fbeOffset());
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return 4 + fbeBytesSize;
      }
  
      // Get the bytes value
      public ByteBuffer get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
          {
              size.value = 0;
              return ByteBuffer.allocate(0);
          }
  
          int fbeBytesSize = readInt32(fbeOffset());
          assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
          {
              size.value = 4;
              return ByteBuffer.allocate(0);
          }
  
          size.value = 4 + fbeBytesSize;
          return ByteBuffer.wrap(readBytes(fbeOffset() + 4, fbeBytesSize));
      }
  
      // Set the bytes value
      public long set(ByteBuffer value)
      {
          assert (value != null) : "Invalid bytes value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid bytes value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          int fbeBytesSize = value.array().length;
          assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeBytesSize) > _buffer.getSize())
              return 4;
  
          write(fbeOffset(), fbeBytesSize);
          write(fbeOffset() + 4, value.array());
          return 4 + fbeBytesSize;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelString(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "FinalModelString.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding string final model
  public final class FinalModelString extends FinalModel
  {
      public FinalModelString(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(String value) { return 4 + 3 * (value.length() + 1); }
  
      // Check if the string value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          int fbeStringSize = readInt32(fbeOffset());
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return 4 + fbeStringSize;
      }
  
      // Get the string value
      public String get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
          {
              size.value = 0;
              return "";
          }
  
          int fbeStringSize = readInt32(fbeOffset());
          assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
          {
              size.value = 4;
              return "";
          }
  
          size.value = 4 + fbeStringSize;
          return readString(fbeOffset() + 4, fbeStringSize);
      }
  
      // Set the string value
      public long set(String value)
      {
          assert (value != null) : "Invalid string value!";
          if (value == null)
              throw new IllegalArgumentException("Invalid string value!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
  
          int fbeStringSize = bytes.length;
          assert ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4 + fbeStringSize) > _buffer.getSize())
              return 4;
  
          write(fbeOffset(), fbeStringSize);
          write(fbeOffset() + 4, bytes);
          return 4 + fbeStringSize;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelOptional(const std::string& package, const std::string& name, const std::string& type, const std::string& model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModelOptional" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding optional _NAME_ final model
  public final class FinalModelOptional_NAME_ extends FinalModel
  {
      public FinalModelOptional_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          value = new _MODEL_(buffer, 0);
      }
  
      // Get the allocation size
      public long fbeAllocationSize(_TYPE_ optional) { return 1 + ((optional != null) ? value.fbeAllocationSize(optional) : 0); }
  
      // Checks if the object contains a value
      public boolean hasValue()
      {
          if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
              return false;
  
          byte fbeHasValue = readInt8(fbeOffset());
          return (fbeHasValue != 0);
      }
  
      // Base final model value
      public final _MODEL_ value;
  
      // Check if the optional value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          byte fbeHasValue = readInt8(fbeOffset());
          if (fbeHasValue == 0)
              return 1;
  
          _buffer.shift(fbeOffset() + 1);
          long fbeResult = value.verify();
          _buffer.unshift(fbeOffset() + 1);
          return 1 + fbeResult;
      }
  
      // Get the optional value
      public _TYPE_ get(Size size)
      {
          assert ((_buffer.getOffset() + fbeOffset() + 1) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
          {
              size.value = 0;
              return null;
          }
  
          if (!hasValue())
          {
              size.value = 1;
              return null;
          }
  
          _buffer.shift(fbeOffset() + 1);
          _TYPE_ optional = value.get(size);
          _buffer.unshift(fbeOffset() + 1);
          size.value += 1;
          return optional;
      }
  
      // Set the optional value
      public long set(_TYPE_ optional)
      {
          assert ((_buffer.getOffset() + fbeOffset() + 1) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 1) > _buffer.getSize())
              return 0;
  
          byte fbeHasValue = (byte)((optional != null) ? 1 : 0);
          write(fbeOffset(), fbeHasValue);
          if (fbeHasValue == 0)
              return 1;
  
          _buffer.shift(fbeOffset() + 1);
          long size = value.set(optional);
          _buffer.unshift(fbeOffset() + 1);
          return 1 + size;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelArray(const std::string& package, const std::string& name, const std::string& type, const std::string& model, bool bytes)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModelArray" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ array final model
  public final class FinalModelArray_NAME_ extends FinalModel
  {
      private final _MODEL_ _model;
      private final long _size;
  
      public FinalModelArray_NAME_(Buffer buffer, long offset, long size)
      {
          super(buffer, offset);
          _model = new _MODEL_(buffer, offset);
          _size = size;
      }
  
      // Get the allocation size
      public long fbeAllocationSize(_ARRAY_ values)
      {
          long size = 0;
          for (long i = 0; (i < values.length) && (i < _size); i++)
              size += _model.fbeAllocationSize(values[(int)i]);
          return size;
      }
      public long fbeAllocationSize(ArrayList<_TYPE_> values)
      {
          long size = 0;
          for (long i = 0; (i < values.size()) && (i < _size); i++)
              size += _model.fbeAllocationSize(values.get((int)i));
          return size;
      }
  
      // Check if the array is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          long size = 0;
          _model.fbeOffset(fbeOffset());
          for (long i = _size; i-- > 0;)
          {
              long offset = _model.verify();
              if (offset == Long.MAX_VALUE)
                  return Long.MAX_VALUE;
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  
      // Get the array
      public _ARRAY_ get(Size size)
      {
          var values = _INIT_;
  
          assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
          {
              size.value = 0;
              return values;
          }
  
          size.value = 0;
          var offset = new Size();
          _model.fbeOffset(fbeOffset());
          for (long i = 0; i < _size; i++)
          {
              offset.value = 0;
              values[(int)i] = _model.get(offset);
              _model.fbeShift(offset.value);
              size.value += offset.value;
          }
          return values;
      }
  
      // Get the array
      public long get(_ARRAY_ values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
              return 0;
  
          long size = 0;
          var offset = new Size();
          _model.fbeOffset(fbeOffset());
          for (long i = 0; (i < values.length) && (i < _size); i++)
          {
              offset.value = 0;
              values[(int)i] = _model.get(offset);
              _model.fbeShift(offset.value);
              size += offset.value;
          }
          return size;
      }
  
      // Get the array as ArrayList
      public long get(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
              return 0;
  
          values.ensureCapacity((int)_size);
  
          long size = 0;
          var offset = new Size();
          _model.fbeOffset(fbeOffset());
          for (long i = _size; i-- > 0;)
          {
              offset.value = 0;
              _TYPE_ value = _model.get(offset);
              values.add(value);
              _model.fbeShift(offset.value);
              size += offset.value;
          }
          return size;
      }
  
      // Set the array
      public long set(_ARRAY_ values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
              return 0;
  
          long size = 0;
          _model.fbeOffset(fbeOffset());
          for (long i = 0; (i < values.length) && (i < _size); i++)
          {
              long offset = _model.set(values[(int)i]);
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  
      // Set the array as List
      public long set(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset()) > _buffer.getSize())
              return 0;
  
          long size = 0;
          _model.fbeOffset(fbeOffset());
          for (long i = 0; (i < values.size()) && (i < _size); i++)
          {
              long offset = _model.set(values.get((int)i));
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("_ARRAY_"), (bytes ? "byte" : type) + "[]");
      code = std::regex_replace(code, std::regex("_INIT_"), ((type != "byte[]") ? ("new " + (bytes ? "byte" : type) + "[(int)_size]") : "new byte[(int)_size][]"));
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelVector(const std::string& package, const std::string& name, const std::string& type, const std::string& model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModelVector" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ vector final model
  public final class FinalModelVector_NAME_ extends FinalModel
  {
      private final _MODEL_ _model;
  
      public FinalModelVector_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          _model = new _MODEL_(buffer, offset);
      }
  
      // Get the allocation size
      public long fbeAllocationSize(_TYPE_[] values)
      {
          long size = 4;
          for (var value : values)
              size += _model.fbeAllocationSize(value);
          return size;
      }
      public long fbeAllocationSize(ArrayList<_TYPE_> values)
      {
          long size = 4;
          for (var value : values)
              size += _model.fbeAllocationSize(value);
          return size;
      }
      public long fbeAllocationSize(LinkedList<_TYPE_> values)
      {
          long size = 4;
          for (var value : values)
              size += _model.fbeAllocationSize(value);
          return size;
      }
      public long fbeAllocationSize(HashSet<_TYPE_> values)
      {
          long size = 4;
          for (var value : values)
              size += _model.fbeAllocationSize(value);
          return size;
      }
  
      // Check if the vector is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          int fbeVectorSize = readInt32(fbeOffset());
  
          long size = 4;
          _model.fbeOffset(fbeOffset() + 4);
          for (int i = fbeVectorSize; i-- > 0;)
          {
              long offset = _model.verify();
              if (offset == Long.MAX_VALUE)
                  return Long.MAX_VALUE;
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  
      // Get the vector as ArrayList
      public long get(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          long fbeVectorSize = readInt32(fbeOffset());
          if (fbeVectorSize == 0)
              return 4;
  
          values.ensureCapacity((int)fbeVectorSize);
  
          long size = 4;
          var offset = new Size();
          _model.fbeOffset(fbeOffset() + 4);
          for (long i = 0; i < fbeVectorSize; i++)
          {
              offset.value = 0;
              _TYPE_ value = _model.get(offset);
              values.add(value);
              _model.fbeShift(offset.value);
              size += offset.value;
          }
          return size;
      }
  
      // Get the vector as LinkedList
      public long get(LinkedList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          long fbeVectorSize = readInt32(fbeOffset());
          if (fbeVectorSize == 0)
              return 4;
  
          long size = 4;
          var offset = new Size();
          _model.fbeOffset(fbeOffset() + 4);
          for (long i = 0; i < fbeVectorSize; i++)
          {
              offset.value = 0;
              _TYPE_ value = _model.get(offset);
              values.add(value);
              _model.fbeShift(offset.value);
              size += offset.value;
          }
          return size;
      }
  
      // Get the vector as HashSet
      public long get(HashSet<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          long fbeVectorSize = readInt32(fbeOffset());
          if (fbeVectorSize == 0)
              return 4;
  
          long size = 4;
          var offset = new Size();
          _model.fbeOffset(fbeOffset() + 4);
          for (long i = 0; i < fbeVectorSize; i++)
          {
              offset.value = 0;
              _TYPE_ value = _model.get(offset);
              values.add(value);
              _model.fbeShift(offset.value);
              size += offset.value;
          }
          return size;
      }
  
      // Set the vector as ArrayList
      public long set(ArrayList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), (int)values.size());
  
          long size = 4;
          _model.fbeOffset(fbeOffset() + 4);
          for (var value : values)
          {
              long offset = _model.set(value);
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  
      // Set the vector as LinkedList
      public long set(LinkedList<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), (int)values.size());
  
          long size = 4;
          _model.fbeOffset(fbeOffset() + 4);
          for (var value : values)
          {
              long offset = _model.set(value);
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  
      // Set the vector as HashSet
      public long set(HashSet<_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), (int)values.size());
  
          long size = 4;
          _model.fbeOffset(fbeOffset() + 4);
          for (var value : values)
          {
              long offset = _model.set(value);
              _model.fbeShift(offset);
              size += offset;
          }
          return size;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_TYPE_"), type);
      code = std::regex_replace(code, std::regex("_MODEL_"), model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelMap(const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModelMap" + key_name + value_name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map final model
  public final class FinalModelMap_KEY_NAME__VALUE_NAME_ extends FinalModel
  {
      private final _KEY_MODEL_ _modelKey;
      private final _VALUE_MODEL_ _modelValue;
  
      public FinalModelMap_KEY_NAME__VALUE_NAME_(Buffer buffer, long offset)
      {
          super(buffer, offset);
          _modelKey = new _KEY_MODEL_(buffer, offset);
          _modelValue = new _VALUE_MODEL_(buffer, offset);
      }
  
      // Get the allocation size
      public long fbeAllocationSize(TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          long size = 4;
          for (var value : values.entrySet())
          {
              size += _modelKey.fbeAllocationSize(value.getKey());
              size += _modelValue.fbeAllocationSize(value.getValue());
          }
          return size;
      }
      public long fbeAllocationSize(HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          long size = 4;
          for (var value : values.entrySet())
          {
              size += _modelKey.fbeAllocationSize(value.getKey());
              size += _modelValue.fbeAllocationSize(value.getValue());
          }
          return size;
      }
  
      // Check if the map is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          int fbeMapSize = readInt32(fbeOffset());
  
          long size = 4;
          _modelKey.fbeOffset(fbeOffset() + 4);
          _modelValue.fbeOffset(fbeOffset() + 4);
          for (int i = fbeMapSize; i-- > 0;)
          {
              long offsetKey = _modelKey.verify();
              if (offsetKey == Long.MAX_VALUE)
                  return Long.MAX_VALUE;
              _modelKey.fbeShift(offsetKey);
              _modelValue.fbeShift(offsetKey);
              size += offsetKey;
              long offsetValue = _modelValue.verify();
              if (offsetValue == Long.MAX_VALUE)
                  return Long.MAX_VALUE;
              _modelKey.fbeShift(offsetValue);
              _modelValue.fbeShift(offsetValue);
              size += offsetValue;
          }
          return size;
      }
  
      // Get the map as TreeMap
      public long get(TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          long fbeMapSize = readInt32(fbeOffset());
          if (fbeMapSize == 0)
              return 4;
  
          long size = 4;
          var offset = new Size();
          _modelKey.fbeOffset(fbeOffset() + 4);
          _modelValue.fbeOffset(fbeOffset() + 4);
          for (long i = fbeMapSize; i-- > 0;)
          {
              offset.value = 0;
              _KEY_TYPE_ key = _modelKey.get(offset);
              _modelKey.fbeShift(offset.value);
              _modelValue.fbeShift(offset.value);
              size += offset.value;
              offset.value = 0;
              _VALUE_TYPE_ value = _modelValue.get(offset);
              _modelKey.fbeShift(offset.value);
              _modelValue.fbeShift(offset.value);
              size += offset.value;
              values.put(key, value);
          }
          return size;
      }
  
      // Get the map as HashMap
      public long get(HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          values.clear();
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          long fbeMapSize = readInt32(fbeOffset());
          if (fbeMapSize == 0)
              return 4;
  
          long size = 4;
          var offset = new Size();
          _modelKey.fbeOffset(fbeOffset() + 4);
          _modelValue.fbeOffset(fbeOffset() + 4);
          for (long i = fbeMapSize; i-- > 0;)
          {
              offset.value = 0;
              _KEY_TYPE_ key = _modelKey.get(offset);
              _modelKey.fbeShift(offset.value);
              _modelValue.fbeShift(offset.value);
              size += offset.value;
              offset.value = 0;
              _VALUE_TYPE_ value = _modelValue.get(offset);
              _modelKey.fbeShift(offset.value);
              _modelValue.fbeShift(offset.value);
              size += offset.value;
  
              values.put(key, value);
          }
          return size;
      }
  
      // Set the map as TreeMap
      public long set(TreeMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), (int)values.size());
  
          long size = 4;
          _modelKey.fbeOffset(fbeOffset() + 4);
          _modelValue.fbeOffset(fbeOffset() + 4);
          for (var value : values.entrySet())
          {
              long offsetKey = _modelKey.set(value.getKey());
              _modelKey.fbeShift(offsetKey);
              _modelValue.fbeShift(offsetKey);
              long offsetValue = _modelValue.set(value.getValue());
              _modelKey.fbeShift(offsetValue);
              _modelValue.fbeShift(offsetValue);
              size += offsetKey + offsetValue;
          }
          return size;
      }
  
      // Set the map as HashMap
      public long set(HashMap<_KEY_TYPE_, _VALUE_TYPE_> values)
      {
          assert (values != null) : "Invalid values parameter!";
          if (values == null)
              throw new IllegalArgumentException("Invalid values parameter!");
  
          assert ((_buffer.getOffset() + fbeOffset() + 4) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + 4) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), (int)values.size());
  
          long size = 4;
          _modelKey.fbeOffset(fbeOffset() + 4);
          _modelValue.fbeOffset(fbeOffset() + 4);
          for (var value : values.entrySet())
          {
              long offsetKey = _modelKey.set(value.getKey());
              _modelKey.fbeShift(offsetKey);
              _modelValue.fbeShift(offsetKey);
              long offsetValue = _modelValue.set(value.getValue());
              _modelKey.fbeShift(offsetValue);
              _modelValue.fbeShift(offsetValue);
              size += offsetKey + offsetValue;
          }
          return size;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
      code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type);
      code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
      code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
      code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type);
      code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEFinalModelEnumFlags(const std::string& package, const std::string& name, const std::string& type)
  {
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModel" + name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      std::string code = R"CODE(
  // Fast Binary Encoding _NAME_ final model
  public final class FinalModel_NAME_ extends FinalModel
  {
      public FinalModel_NAME_(Buffer buffer, long offset) { super(buffer, offset); }
  
      // Get the allocation size
      public long fbeAllocationSize(_NAME_ value) { return fbeSize(); }
  
      // Get the final size
      @Override
      public long fbeSize() { return _SIZE_; }
  
      // Check if the value is valid
      @Override
      public long verify()
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return Long.MAX_VALUE;
  
          return fbeSize();
      }
  
      // Get the value
      public _NAME_ get(Size size)
      {
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return new _NAME_();
  
          size.value = fbeSize();
          return new _NAME_(_READ_(fbeOffset()));
      }
  
      // Set the value
      public long set(_NAME_ value)
      {
          assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : "Model is broken!";
          if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())
              return 0;
  
          write(fbeOffset(), value.getRaw());
          return fbeSize();
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("_NAME_"), name);
      code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
      code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBESender(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Sender.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding base sender
  public abstract class Sender
  {
      private Buffer _buffer;
      private boolean _logging;
      private boolean _final;
  
      // Get the bytes buffer
      public Buffer getBuffer() { return _buffer; }
  
      // Get the final protocol flag
      public boolean getFinal() { return _final; }
  
      // Get the logging flag
      public boolean getLogging() { return _logging; }
      // Enable/Disable logging
      public void setLogging(boolean enable) { _logging = enable; }
  
      protected Sender(boolean finalProto) { _buffer = new Buffer(); _final = finalProto; }
      protected Sender(Buffer buffer, boolean finalProto) { _buffer = buffer; _final = finalProto; }
  
      // Reset the sender buffer
      public void reset() { _buffer.reset(); }
  
      // Send serialized buffer.
      // Direct call of the method requires knowledge about internals of FBE models serialization.
      // Use it with care!
      public long sendSerialized(long serialized)
      {
          assert (serialized > 0) : "Invalid size of the serialized buffer!";
          if (serialized <= 0)
              return 0;
  
          // Shift the send buffer
          _buffer.shift(serialized);
  
          // Send the value
          long sent = onSend(_buffer.getData(), 0, _buffer.getSize());
          _buffer.remove(0, sent);
          return sent;
      }
  
      // Send message handler
      protected abstract long onSend(byte[] buffer, long offset, long size);
      // Send log message handler
      protected void onSendLog(String message) {}
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEReceiver(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Receiver.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      std::string code = R"CODE(
  // Fast Binary Encoding base receiver
  public abstract class Receiver
  {
      private Buffer _buffer;
      private boolean _logging;
      private boolean _final;
  
      // Get the bytes buffer
      public Buffer getBuffer() { return _buffer; }
  
      // Get the final protocol flag
      public boolean getFinal() { return _final; }
  
      // Get the logging flag
      public boolean getLogging() { return _logging; }
      // Enable/Disable logging
      public void setLogging(boolean enable) { _logging = enable; }
  
      protected Receiver(boolean finalProto) { _buffer = new Buffer(); _final = finalProto; }
      protected Receiver(Buffer buffer, boolean finalProto) { _buffer = buffer; _final = finalProto; }
  
      // Reset the receiver buffer
      public void reset() { _buffer.reset(); }
  
      // Receive data
      public void receive(Buffer buffer) { receive(buffer.getData(), 0, buffer.getSize()); }
      public void receive(byte[] buffer) { receive(buffer, 0, buffer.length); }
      public void receive(byte[] buffer, long offset, long size)
      {
          assert (buffer != null) : "Invalid buffer!";
          if (buffer == null)
              throw new IllegalArgumentException("Invalid buffer!");
          assert ((offset + size) <= buffer.length) : "Invalid offset & size!";
          if ((offset + size) > buffer.length)
              throw new IllegalArgumentException("Invalid offset & size!");
  
          if (size == 0)
              return;
  
          // Storage buffer
          long offset0 = _buffer.getOffset();
          long offset1 = _buffer.getSize();
          long size1 = _buffer.getSize();
  
          // Receive buffer
          long offset2 = 0;
          long size2 = size;
  
          // While receive buffer is available to handle...
          while (offset2 < size2)
          {
              byte[] messageBuffer = null;
              long messageOffset = 0;
              long messageSize = 0;
  
              // Try to receive message size
              boolean messageSizeCopied = false;
              boolean messageSizeFound = false;
              while (!messageSizeFound)
              {
                  // Look into the storage buffer
                  if (offset0 < size1)
                  {
                      long count = Math.min(size1 - offset0, 4);
                      if (count == 4)
                      {
                          messageSizeCopied = true;
                          messageSizeFound = true;
                          messageSize = _buffer.readInt32(_buffer.getData(), offset0);
                          offset0 += 4;
                          break;
                      }
                      else
                      {
                          // Fill remaining data from the receive buffer
                          if (offset2 < size2)
                          {
                              count = Math.min(size2 - offset2, 4 - count);
  
                              // Allocate and refresh the storage buffer
                              _buffer.allocate(count);
                              size1 += count;
  
                              System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                              offset1 += count;
                              offset2 += count;
                              continue;
                          }
                          else
                              break;
                      }
                  }
  
                  // Look into the receive buffer
                  if (offset2 < size2)
                  {
                      long count = Math.min(size2 - offset2, 4);
                      if (count == 4)
                      {
                          messageSizeFound = true;
                          messageSize = _buffer.readInt32(buffer, offset + offset2);
                          offset2 += 4;
                          break;
                      }
                      else
                      {
                          // Allocate and refresh the storage buffer
                          _buffer.allocate(count);
                          size1 += count;
  
                          System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                          offset1 += count;
                          offset2 += count;
                          continue;
                      }
                  }
                  else
                      break;
              }
  
              if (!messageSizeFound)
                  return;
  
              // Check the message full size
              assert (messageSize >= (4 + 4 + 4 + 4)) : "Invalid receive data!";
              if (messageSize < (4 + 4 + 4 + 4))
                  return;
  
              // Try to receive message body
              boolean messageFound = false;
              while (!messageFound)
              {
                  // Look into the storage buffer
                  if (offset0 < size1)
                  {
                      long count = Math.min(size1 - offset0, messageSize - 4);
                      if (count == (messageSize - 4))
                      {
                          messageFound = true;
                          messageBuffer = _buffer.getData();
                          messageOffset = offset0 - 4;
                          offset0 += messageSize - 4;
                          break;
                      }
                      else
                      {
                          // Fill remaining data from the receive buffer
                          if (offset2 < size2)
                          {
                              // Copy message size into the storage buffer
                              if (!messageSizeCopied)
                              {
                                  // Allocate and refresh the storage buffer
                                  _buffer.allocate(4);
                                  size1 += 4;
  
                                  _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                                  offset0 += 4;
                                  offset1 += 4;
  
                                  messageSizeCopied = true;
                              }
  
                              count = Math.min(size2 - offset2, messageSize - 4 - count);
  
                              // Allocate and refresh the storage buffer
                              _buffer.allocate(count);
                              size1 += count;
  
                              System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                              offset1 += count;
                              offset2 += count;
                              continue;
                          }
                          else
                              break;
                      }
                  }
  
                  // Look into the receive buffer
                  if (offset2 < size2)
                  {
                      long count = Math.min(size2 - offset2, messageSize - 4);
                      if (!messageSizeCopied && (count == (messageSize - 4)))
                      {
                          messageFound = true;
                          messageBuffer = buffer;
                          messageOffset = offset + offset2 - 4;
                          offset2 += messageSize - 4;
                          break;
                      }
                      else
                      {
                          // Copy message size into the storage buffer
                          if (!messageSizeCopied)
                          {
                              // Allocate and refresh the storage buffer
                              _buffer.allocate(4);
                              size1 += 4;
  
                              _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                              offset0 += 4;
                              offset1 += 4;
  
                              messageSizeCopied = true;
                          }
  
                          // Allocate and refresh the storage buffer
                          _buffer.allocate(count);
                          size1 += count;
  
                          System.arraycopy(buffer, (int)(offset + offset2), _buffer.getData(), (int)offset1, (int)count);
                          offset1 += count;
                          offset2 += count;
                          continue;
                      }
                  }
                  else
                      break;
              }
  
              if (!messageFound)
              {
                  // Copy message size into the storage buffer
                  if (!messageSizeCopied)
                  {
                      // Allocate and refresh the storage buffer
                      _buffer.allocate(4);
                      size1 += 4;
  
                      _buffer.write(_buffer.getData(), offset0, (int)messageSize);
                      offset0 += 4;
                      offset1 += 4;
  
                      messageSizeCopied = true;
                  }
                  return;
              }
  
              int fbeStructSize;
              int fbeStructType;
  
              // Read the message parameters
              if (_final)
              {
                  fbeStructSize = Buffer.readInt32(messageBuffer, messageOffset);
                  fbeStructType = Buffer.readInt32(messageBuffer, messageOffset + 4);
              }
              else
              {
                  int fbeStructOffset = Buffer.readInt32(messageBuffer, messageOffset + 4);
                  fbeStructSize = Buffer.readInt32(messageBuffer, messageOffset + fbeStructOffset);
                  fbeStructType = Buffer.readInt32(messageBuffer, messageOffset + fbeStructOffset + 4);
              }
  
              // Handle the message
              onReceive(fbeStructType, messageBuffer, messageOffset, messageSize);
  
              // Reset the storage buffer
              _buffer.reset();
  
              // Refresh the storage buffer
              offset1 = _buffer.getOffset();
              size1 = _buffer.getSize();
          }
      }
  
      // Receive message handler
      public abstract boolean onReceive(long type, byte[] buffer, long offset, long size);
      // Receive log message handler
      protected void onReceiveLog(String message) {}
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateFBEJson(const std::string& package)
  {
      CppCommon::Path path = CppCommon::Path(_output) / package;
  
      // Open the file
      CppCommon::Path file = path / "Json.java";
      Open(file);
  
      // Generate headers
      GenerateHeader("fbe");
      GenerateImports(package);
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import com.google.gson.*;");
  
      std::string code = R"CODE(
  final class ByteBufferJson implements JsonSerializer<ByteBuffer>, JsonDeserializer<ByteBuffer>
  {
      @Override
      public JsonElement serialize(ByteBuffer src, Type typeOfSrc, JsonSerializationContext context)
      {
          return new JsonPrimitive(Base64.getEncoder().encodeToString(src.array()));
      }
  
      @Override
      public ByteBuffer deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
      {
          return ByteBuffer.wrap(Base64.getDecoder().decode(json.getAsString()));
      }
  }
  
  final class CharacterJson implements JsonSerializer<Character>, JsonDeserializer<Character>
  {
      @Override
      public JsonElement serialize(Character src, Type typeOfSrc, JsonSerializationContext context)
      {
          return new JsonPrimitive((long)src);
      }
  
      @Override
      public Character deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
      {
          return (char)json.getAsLong();
      }
  }
  
  final class InstantJson implements JsonSerializer<Instant>, JsonDeserializer<Instant>
  {
      @Override
      public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context)
      {
          long nanoseconds = src.getEpochSecond() * 1000000000 + src.getNano();
          return new JsonPrimitive(nanoseconds);
      }
  
      @Override
      public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
      {
          long nanoseconds = json.getAsJsonPrimitive().getAsLong();
          return Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000);
      }
  }
  
  final class BigDecimalJson implements JsonSerializer<BigDecimal>, JsonDeserializer<BigDecimal>
  {
      @Override
      public JsonElement serialize(BigDecimal src, Type typeOfSrc, JsonSerializationContext context)
      {
          return new JsonPrimitive(src.toPlainString());
      }
  
      @Override
      public BigDecimal deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
      {
          return new BigDecimal(json.getAsJsonPrimitive().getAsString());
      }
  }
  
  final class UUIDJson implements JsonSerializer<UUID>, JsonDeserializer<UUID>
  {
      @Override
      public JsonElement serialize(UUID src, Type typeOfSrc, JsonSerializationContext context)
      {
          return new JsonPrimitive(src.toString());
      }
  
      @Override
      public UUID deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
      {
          return UUID.fromString(json.getAsJsonPrimitive().getAsString());
      }
  }
  
  // Fast Binary Encoding base JSON engine
  public final class Json
  {
      private static final Gson _engine;
  
      // Get the JSON engine
      public static Gson getEngine() { return _engine; }
  
      static
      {
          _engine = register(new GsonBuilder()).create();
      }
  
      private Json() {}
  
      public static GsonBuilder register(GsonBuilder builder)
      {
          builder.serializeNulls();
          builder.registerTypeAdapter(ByteBuffer.class, new ByteBufferJson());
          builder.registerTypeAdapter(char.class, new CharacterJson());
          builder.registerTypeAdapter(Character.class, new CharacterJson());
          builder.registerTypeAdapter(Instant.class, new InstantJson());
          builder.registerTypeAdapter(BigDecimal.class, new BigDecimalJson());
          builder.registerTypeAdapter(UUID.class, new UUIDJson());
          return builder;
      }
  }
  )CODE";
  
      // Prepare code template
      code = std::regex_replace(code, std::regex("\n"), EndLine());
  
      Write(code);
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateContainers(const std::shared_ptr<Package>& p, bool final)
  {
      CppCommon::Path path = CppCommon::Path(_output) / *p->name;
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      if (p->body)
      {
          // Check all structs in the package
          for (const auto& s : p->body->structs)
          {
              if (s->body)
              {
                  // Check all fields in the struct
                  for (const auto& field : s->body->fields)
                  {
                      if (field->array)
                      {
                          if (final)
                              GenerateFBEFinalModelArray(*p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final), ((*field->type == "byte") && !field->optional));
                          else
                              GenerateFBEFieldModelArray(*p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final), ((*field->type == "byte") && !field->optional));
                      }
                      if (field->vector || field->list || field->set)
                      {
                          if (final)
                              GenerateFBEFinalModelVector(*p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final));
                          else
                              GenerateFBEFieldModelVector(*p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final));
                      }
                      if (field->map || field->hash)
                      {
                          if (final)
                              GenerateFBEFinalModelMap(*p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(*field->key, false), ConvertTypeFieldDeclaration(*field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final));
                          else
                              GenerateFBEFieldModelMap(*p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(*field->key, false), ConvertTypeFieldDeclaration(*field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, field->optional, final));
                      }
                      if (field->optional)
                      {
                          if (final)
                              GenerateFBEFinalModelOptional(*p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, false, final));
                          else
                              GenerateFBEFieldModelOptional(*p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(*field->type, field->optional), ConvertTypeFieldDeclaration(*field->type, false, final));
                      }
                  }
              }
          }
      }
  }
  
  void GeneratorJava::GeneratePackage(const std::shared_ptr<Package>& p)
  {
      CppCommon::Path path = CppCommon::Path(_output) / *p->name;
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Generate namespace
      if (p->body)
      {
          // Generate child enums
          for (const auto& child_e : p->body->enums)
              GenerateEnum(p, child_e, path);
  
          // Generate child flags
          for (const auto& child_f : p->body->flags)
              GenerateFlags(p, child_f, path);
  
          // Generate child structs
          for (const auto& child_s : p->body->structs)
              GenerateStruct(p, child_s, path);
      }
  
      // Generate containers
      GenerateContainers(p, false);
      if (Final())
          GenerateContainers(p, true);
  
      // Generate sender & receiver
      if (Sender())
      {
          GenerateSender(p, false);
          GenerateReceiver(p, false);
          GenerateProxy(p, false);
          if (Final())
          {
              GenerateSender(p, true);
              GenerateReceiver(p, true);
          }
      }
  
      // Generate JSON engine
      if (JSON())
          GenerateJson(p);
  }
  
  void GeneratorJava::GenerateEnum(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
  {
      std::string enum_name = *e->name + "Enum";
  
      // Open the output file
      CppCommon::Path output = path / (enum_name + ".java");
      Open(output);
  
      // Generate enum header
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(p);
  
      std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
      std::string enum_base_type = ConvertEnumType(enum_type);
      std::string enum_mapping_type = ConvertEnumBase(enum_type);
  
      // Generate enum body
      WriteLine();
      WriteLineIndent("public enum " + enum_name);
      WriteLineIndent("{");
      Indent(1);
      if (e->body)
      {
          int index = 0;
          bool first = true;
          std::string last = ConvertEnumConstant(enum_type, "0");
          for (const auto& value : e->body->values)
          {
              WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
              if (value->value)
              {
                  if (value->value->constant && !value->value->constant->empty())
                  {
                      index = 0;
                      last = ConvertEnumConstant(enum_type, *value->value->constant);
                      Write(last + " + " + std::to_string(index++));
                  }
                  else if (value->value->reference && !value->value->reference->empty())
                  {
                      index = 0;
                      last = ConvertEnumConstant("", *value->value->reference);
                      Write(last);
                  }
              }
              else
                  Write(last + " + " + std::to_string(index++));
              WriteLine(")");
              first = false;
          }
          WriteLineIndent(";");
          WriteLine();
      }
  
      // Generate enum value
      WriteLineIndent("private " + enum_base_type + " value;");
  
      // Generate enum constructors
      WriteLine();
      if (enum_base_type != "int")
          WriteLineIndent(enum_name + "(" + enum_base_type + " value) { this.value = value; }");
      WriteLineIndent(enum_name + "(int value) { this.value = (" + enum_base_type + ")value; }");
      WriteLineIndent(enum_name + "(" + enum_name + " value) { this.value = value.value; }");
  
      // Generate enum getRaw() method
      WriteLine();
      WriteLineIndent("public " + enum_base_type + " getRaw() { return value; }");
  
      // Generate enum mapValue() method
      WriteLine();
      WriteLineIndent("public static " + enum_name + " mapValue(" + enum_base_type + " value) { return mapping.get(value); }");
  
      // Generate enum toString() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public String toString()");
      WriteLineIndent("{");
      Indent(1);
      if (e->body)
      {
          for (const auto& value : e->body->values)
              WriteLineIndent("if (this == " + *value->name + ")" + " return \"" + *value->name + "\";");
  
      }
      WriteLineIndent("return \"<unknown>\";");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum mapping
      WriteLine();
      WriteLineIndent("private static final Map<" + enum_mapping_type + ", " + enum_name + "> mapping = new HashMap<>();");
      WriteLineIndent("static");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("for (var value : " + enum_name + ".values())");
      Indent(1);
      WriteLineIndent("mapping.put(value.value, value);");
      Indent(-1);
      Indent(-1);
      WriteLineIndent("}");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum footer
      GenerateFooter();
  
      // Close the output file
      Close();
  
      // Generate enum wrapper class
      GenerateEnumClass(p, e, path);
  
      // Generate enum JSON adapter
      if (JSON())
          GenerateEnumJson(p, e);
  
      // Generate enum field model
      GenerateFBEFieldModelEnumFlags(*p->name, *e->name, enum_type);
  
      // Generate enum final model
      if (Final())
          GenerateFBEFinalModelEnumFlags(*p->name, *e->name, enum_type);
  }
  
  void GeneratorJava::GenerateEnumClass(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
  {
      std::string enum_name = *e->name;
      std::string enum_type_name = *e->name + "Enum";
  
      // Open the output file
      CppCommon::Path output = path / (enum_name + ".java");
      Open(output);
  
      // Generate enum class header
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(p);
  
      std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
      std::string enum_base_type = ConvertEnumType(enum_type);
  
      // Generate enum class body
      WriteLine();
      WriteLineIndent("public final class " + enum_name + " implements Comparable<" + enum_name + ">");
      WriteLineIndent("{");
      Indent(1);
      if (e->body)
      {
          for (const auto& value : e->body->values)
              WriteLineIndent("public static final " + enum_name + " " + *value->name + " = new " + enum_name + "(" + enum_type_name + "." + *value->name + ");");
          WriteLine();
      }
  
      // Generate enum class value
      WriteLineIndent("private " + enum_type_name + " value = " + enum_type_name + ".values()[0];");
  
      // Generate enum class constructors
      WriteLine();
      WriteLineIndent("public " + enum_name + "() {}");
      WriteLineIndent("public " + enum_name + "(" + enum_base_type + " value) { setEnum(value); }");
      WriteLineIndent("public " + enum_name + "(" + enum_type_name + " value) { setEnum(value); }");
      WriteLineIndent("public " + enum_name + "(" + enum_name + " value) { setEnum(value); }");
  
      // Generate enum class getEnum() and getRaw() methods
      WriteLine();
      WriteLineIndent("public " + enum_type_name + " getEnum() { return value; }");
      WriteLineIndent("public " + enum_base_type + " getRaw() { return value.getRaw(); }");
  
      // Generate enum class setDefault() method
      WriteLine();
      WriteLineIndent("public void setDefault() { setEnum((" + enum_base_type + ")0); }");
  
      // Generate enum class setEnum() methods
      WriteLine();
      WriteLineIndent("public void setEnum(" + enum_base_type + " value) { this.value = " + enum_type_name + ".mapValue(value); }");
      WriteLineIndent("public void setEnum(" + enum_type_name + " value) { this.value = value; }");
      WriteLineIndent("public void setEnum(" + enum_name + " value) { this.value = value.value; }");
  
      // Generate enum class compareTo() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int compareTo(" + enum_name + " other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if (value == null)");
      Indent(1);
      WriteLineIndent("return -1;");
      Indent(-1);
      WriteLineIndent("if (other.value == null)");
      Indent(1);
      WriteLineIndent("return 1;");
      Indent(-1);
      WriteLineIndent("return (int)(value.getRaw() - other.value.getRaw());");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum class equals() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean equals(Object other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if (other == null)");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("if (!" + enum_name + ".class.isAssignableFrom(other.getClass()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("final " + enum_name + " enm = (" + enum_name + ")other;");
      WriteLine();
      WriteLineIndent("if ((value == null) || (enm.value == null))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLineIndent("if (value.getRaw() != enm.value.getRaw())");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLineIndent("return true;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum class hashCode() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int hashCode()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("int hash = 17;");
      WriteLineIndent("hash = hash * 31 + ((value != null) ? value.hashCode() : 0);");
      WriteLineIndent("return hash;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum class toString() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public String toString() { return (value != null) ? value.toString() : \"<unknown>\"; }");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate enum class footer
      GenerateFooter();
  
      // Close the output file
      Close();
  }
  
  void GeneratorJava::GenerateEnumJson(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      std::string enum_name = *e->name;
      std::string adapter_name = *e->name + "Json";
  
      // Open the output file
      CppCommon::Path output = path / (adapter_name + ".java");
      Open(output);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe;");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import com.google.gson.*;");
  
      std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
  
      // Generate JSON adapter body
      WriteLine();
      WriteLineIndent("public final class " + adapter_name + " implements JsonSerializer<" + enum_name + ">, JsonDeserializer<" + enum_name + ">");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate JSON adapter serialize() method
      WriteLineIndent("@Override");
      WriteLineIndent("public JsonElement serialize(" + enum_name + " src, Type typeOfSrc, JsonSerializationContext context)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("return new JsonPrimitive(src.getRaw());");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate JSON adapter deserialize() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public " + enum_name + " deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("return new " + enum_name + "(json.getAsJsonPrimitive()." + ConvertEnumGet(enum_type) + "());");
      Indent(-1);
      WriteLineIndent("}");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate JSON adapter footer
      GenerateFooter();
  
      // Close the output file
      Close();
  }
  
  void GeneratorJava::GenerateFlags(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
  {
      std::string flags_name = *f->name + "Enum";
  
      // Open the output file
      CppCommon::Path output = path / (flags_name + ".java");
      Open(output);
  
      // Generate flags header
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(p);
  
      std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
      std::string flags_base_type = ConvertEnumType(flags_type);
      std::string flags_mapping_type = ConvertEnumBase(flags_type);
  
      // Generate flags body
      WriteLine();
      WriteLineIndent("public enum " + flags_name);
      WriteLineIndent("{");
      Indent(1);
      if (f->body)
      {
          bool first = true;
          for (const auto& value : f->body->values)
          {
              WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
              if (value->value)
              {
                  if (value->value->constant && !value->value->constant->empty())
                      Write(ConvertEnumConstant(flags_type, *value->value->constant));
                  else if (value->value->reference && !value->value->reference->empty())
                      Write(ConvertEnumConstant("", *value->value->reference));
              }
              WriteLine(")");
              first = false;
          }
          WriteLineIndent(";");
          WriteLine();
      }
      else
          WriteIndent("unknown(0);");
  
      // Generate flags class value
      WriteLineIndent("private " + flags_base_type + " value;");
  
      // Generate flags class constructors
      WriteLine();
      if (flags_base_type != "int")
          WriteLineIndent(flags_name + "(" + flags_base_type + " value) { this.value = value; }");
      WriteLineIndent(flags_name + "(int value) { this.value = (" + flags_base_type + ")value; }");
      WriteLineIndent(flags_name + "(" + flags_name + " value) { this.value = value.value; }");
  
      // Generate flags getRaw() method
      WriteLine();
      WriteLineIndent("public " + flags_base_type + " getRaw() { return value; }");
  
      // Generate flags mapValue() method
      WriteLine();
      WriteLineIndent("public static " + flags_name + " mapValue(" + flags_base_type + " value) { return mapping.get(value); }");
  
      // Generate flags hasFlags() methods
      WriteLine();
      WriteLineIndent("public boolean hasFlags(" + flags_base_type + " flags) { return (((value & flags) != 0) && ((value & flags) == flags)); }");
      WriteLineIndent("public boolean hasFlags(" + flags_name + " flags) { return hasFlags(flags.value); }");
  
      // Generate flags getAllSet(), getNoneSet(), getCurrentSet() methods
      WriteLine();
      WriteLineIndent("public EnumSet<" + flags_name + "> getAllSet() { return EnumSet.allOf(" + flags_name + ".class); }");
      WriteLineIndent("public EnumSet<" + flags_name + "> getNoneSet() { return EnumSet.noneOf(" + flags_name + ".class); }");
      WriteLineIndent("public EnumSet<" + flags_name + "> getCurrentSet()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("EnumSet<" + flags_name + "> result = EnumSet.noneOf(" + flags_name + ".class);");
      if (f->body)
      {
          for (const auto& value : f->body->values)
          {
              WriteLineIndent("if ((value & " + *value->name + ".getRaw()) != 0)");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("result.add(" + *value->name + ");");
              Indent(-1);
              WriteLineIndent("}");
          }
      }
      WriteLineIndent("return result;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags toString() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public String toString()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("var sb = new StringBuilder();");
      WriteLineIndent("boolean first = true;");
      if (f->body)
      {
          for (const auto& value : f->body->values)
          {
              WriteLineIndent("if (hasFlags(" + *value->name + "))");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("sb.append(first ? \"\" : \"|\").append(\"" + *value->name + "\");");
              WriteLineIndent("first = false;");
              Indent(-1);
              WriteLineIndent("}");
          }
      }
      WriteLineIndent("return sb.toString();");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags mapping
      WriteLine();
      WriteLineIndent("private static final Map<" + flags_mapping_type + ", " + flags_name + "> mapping = new HashMap<>();");
      WriteLineIndent("static");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("for (var value : " + flags_name + ".values())");
      Indent(1);
      WriteLineIndent("mapping.put(value.value, value);");
      Indent(-1);
      Indent(-1);
      WriteLineIndent("}");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags footer
      GenerateFooter();
  
      // Close the output file
      Close();
  
      // Generate flags wrapper class
      GenerateFlagsClass(p, f, path);
  
      // Generate flags JSON adapter
      if (JSON())
          GenerateFlagsJson(p, f);
  
      // Generate flags field model
      GenerateFBEFieldModelEnumFlags(*p->name, *f->name, flags_type);
  
      // Generate flags final model
      if (Final())
          GenerateFBEFinalModelEnumFlags(*p->name, *f->name, flags_type);
  }
  
  void GeneratorJava::GenerateFlagsClass(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
  {
      std::string flags_name = *f->name;
      std::string flags_type_name = *f->name + "Enum";
  
      // Open the output file
      CppCommon::Path output = path / (flags_name + ".java");
      Open(output);
  
      // Generate flags class header
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(p);
  
      std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
      std::string flags_base_type = ConvertEnumType(flags_type);
  
      // Generate flags class body
      WriteLine();
      WriteLineIndent("public final class " + flags_name + " implements Comparable<" + flags_name + ">");
      WriteLineIndent("{");
      Indent(1);
      if (f->body)
      {
          for (const auto& value : f->body->values)
              WriteLineIndent("public static final " + flags_name + " " + *value->name + " = new " + flags_name + "(" + flags_type_name + "." + *value->name + ");");
          WriteLine();
      }
  
      // Generate flags class value and flags
      WriteLineIndent("private " + flags_type_name + " value = " + flags_type_name + ".values()[0];");
      WriteLineIndent("private " + flags_base_type + " flags = value.getRaw();");
  
      // Generate flags class constructors
      WriteLine();
      WriteLineIndent("public " + flags_name + "() {}");
      WriteLineIndent("public " + flags_name + "(" + flags_base_type + " value) { setEnum(value); }");
      WriteLineIndent("public " + flags_name + "(" + flags_type_name + " value) { setEnum(value); }");
      WriteLineIndent("public " + flags_name + "(EnumSet<" + flags_type_name + "> value) { setEnum(value); }");
      WriteLineIndent("public " + flags_name + "(" + flags_name + " value) { setEnum(value); }");
  
      // Generate flags class getEnum() and getRaw() methods
      WriteLine();
      WriteLineIndent("public " + flags_type_name + " getEnum() { return value; }");
      WriteLineIndent("public " + flags_base_type + " getRaw() { return flags; }");
  
      // Generate flags class setDefault() method
      WriteLine();
      WriteLineIndent("public void setDefault() { setEnum((" + flags_base_type + ")0); }");
  
      // Generate flags class setEnum() methods
      WriteLine();
      WriteLineIndent("public void setEnum(" + flags_base_type + " value) { this.flags = value; this.value = " + flags_type_name + ".mapValue(value); }");
      WriteLineIndent("public void setEnum(" + flags_type_name + " value) { this.value = value; this.flags = value.getRaw(); }");
      WriteLineIndent("public void setEnum(EnumSet<" + flags_type_name + "> value) { setEnum(" + flags_name + ".fromSet(value)); }");
      WriteLineIndent("public void setEnum(" + flags_name + " value) { this.value = value.value; this.flags = value.flags; }");
  
      // Generate flags class hasFlags() methods
      WriteLine();
      WriteLineIndent("public boolean hasFlags(" + flags_base_type + " flags) { return (((this.flags & flags) != 0) && ((this.flags & flags) == flags)); }");
      WriteLineIndent("public boolean hasFlags(" + flags_type_name + " flags) { return hasFlags(flags.getRaw()); }");
      WriteLineIndent("public boolean hasFlags(" + flags_name + " flags) { return hasFlags(flags.flags); }");
  
      // Generate flags class setFlags() methods
      WriteLine();
      WriteLineIndent("public " + flags_name + " setFlags(" + flags_base_type + " flags) { setEnum((" + flags_base_type + ")(this.flags | flags)); return this; }");
      WriteLineIndent("public " + flags_name + " setFlags(" + flags_type_name + " flags) { setFlags(flags.getRaw()); return this; }");
      WriteLineIndent("public " + flags_name + " setFlags(" + flags_name + " flags) { setFlags(flags.flags); return this; }");
  
      // Generate flags class removeFlags() methods
      WriteLine();
      WriteLineIndent("public " + flags_name + " removeFlags(" + flags_base_type + " flags) { setEnum((" + flags_base_type + ")(this.flags & ~flags)); return this; }");
      WriteLineIndent("public " + flags_name + " removeFlags(" + flags_type_name + " flags) { removeFlags(flags.getRaw()); return this; }");
      WriteLineIndent("public " + flags_name + " removeFlags(" + flags_name + " flags) { removeFlags(flags.flags); return this; }");
  
      // Generate flags class getAllSet(), getNoneSet() and getCurrentSet() methods
      WriteLine();
      WriteLineIndent("public EnumSet<" + flags_type_name + "> getAllSet() { return value.getAllSet(); }");
      WriteLineIndent("public EnumSet<" + flags_type_name + "> getNoneSet() { return value.getNoneSet(); }");
      WriteLineIndent("public EnumSet<" + flags_type_name + "> getCurrentSet() { return value.getCurrentSet(); }");
  
      // Generate flags class fromSet() method
      WriteLine();
      WriteLineIndent("public static " + flags_name + " fromSet(EnumSet<" + flags_type_name + "> set)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent(flags_base_type + " result = 0;");
      if (f->body)
      {
          for (const auto& value : f->body->values)
          {
              WriteLineIndent("if (set.contains(" + *value->name + ".getEnum()))");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("result |= " + *value->name + ".flags;");
              Indent(-1);
              WriteLineIndent("}");
          }
      }
      WriteLineIndent("return new " + flags_name + "(result);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags class compareTo() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int compareTo(" + flags_name + " other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("return (int)(flags - other.flags);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags class equals() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean equals(Object other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if (other == null)");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("if (!" + flags_name + ".class.isAssignableFrom(other.getClass()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("final " + flags_name + " flg = (" + flags_name + ")other;");
      WriteLine();
      WriteLineIndent("if (flags != flg.flags)");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLineIndent("return true;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags class hashCode() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int hashCode()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("int hash = 17;");
      WriteLineIndent("hash = hash * 31 + (int)flags;");
      WriteLineIndent("return hash;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags class toString() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public String toString()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("var sb = new StringBuilder();");
      WriteLineIndent("boolean first = true;");
      if (f->body)
      {
          for (const auto& value : f->body->values)
          {
              WriteLineIndent("if (hasFlags(" + *value->name + ".flags))");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("sb.append(first ? \"\" : \"|\").append(\"" + *value->name + "\");");
              WriteLineIndent("first = false;");
              Indent(-1);
              WriteLineIndent("}");
          }
      }
      WriteLineIndent("return sb.toString();");
      Indent(-1);
      WriteLineIndent("}");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate flags class footer
      GenerateFooter();
  
      // Close the output file
      Close();
  }
  
  void GeneratorJava::GenerateFlagsJson(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      std::string flags_name = *f->name;
      std::string adapter_name = *f->name + "Json";
  
      // Open the output file
      CppCommon::Path output = path / (adapter_name + ".java");
      Open(output);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe;");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import com.google.gson.*;");
  
      std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
  
      // Generate JSON adapter body
      WriteLine();
      WriteLineIndent("public final class " + adapter_name + " implements JsonSerializer<" + flags_name + ">, JsonDeserializer<" + flags_name + ">");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate JSON adapter serialize() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public JsonElement serialize(" + flags_name + " src, Type typeOfSrc, JsonSerializationContext context)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("return new JsonPrimitive(src.getRaw());");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate JSON adapter deserialize() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public " + flags_name + " deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("return new " + flags_name + "(json.getAsJsonPrimitive()." + ConvertEnumGet(flags_type) + "());");
      Indent(-1);
      WriteLineIndent("}");
  
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate JSON adapter footer
      GenerateFooter();
  
      // Close the output file
      Close();
  }
  
  void GeneratorJava::GenerateStruct(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s, const CppCommon::Path& path)
  {
      bool first;
  
      // Open the output file
      CppCommon::Path output = path / (*s->name + ".java");
      Open(output);
  
      // Generate struct header
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(p);
  
      // Generate struct begin
      WriteLine();
      WriteIndent("public class " + *s->name);
      if (s->base && !s->base->empty())
          Write(" extends " + ConvertTypeName(*s->base, false));
      else
          Write(" implements Comparable<Object>");
      WriteLine();
      WriteLineIndent("{");
      Indent(1);
  
      // Generate struct body
      if (s->body && !s->body->fields.empty())
      {
          for (const auto& field : s->body->fields)
              WriteLineIndent("public " + ConvertTypeName(*field) + " " + *field->name + " = " + ConvertDefault(*field) + ";");
          WriteLine();
      }
  
      // Generate struct default constructor
      WriteLineIndent("public " + *s->name + "() {}");
  
      // Generate struct initialization constructor
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          first = true;
          WriteLine();
          WriteIndent("public " + *s->name + "(");
          if (s->base && !s->base->empty())
          {
              Write(ConvertTypeName(*s->base, false) + " parent");
              first = false;
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  Write(std::string(first ? "" : ", ") + ConvertTypeName(*field) + " " + *field->name);
                  first = false;
              }
          }
          WriteLine(")");
          WriteLineIndent("{");
          Indent(1);
          if (s->base && !s->base->empty())
              WriteLineIndent("super(parent);");
          if (s->body)
              for (const auto& field : s->body->fields)
                  WriteLineIndent("this." + *field->name + " = " + *field->name + ";");
          Indent(-1);
          WriteLineIndent("}");
      }
  
      // Generate struct copy constructor
      WriteLine();
      WriteLineIndent("public " + *s->name + "(" + *s->name +" other)");
      WriteLineIndent("{");
      Indent(1);
      if (s->base && !s->base->empty())
          WriteLineIndent("super(other);");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("this." + *field->name + " = other." + *field->name + ";");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct clone() method
      WriteLine();
      WriteLineIndent("public " + *s->name + " clone()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("// Serialize the struct to the FBE stream");
      WriteLineIndent("var writer = new " + *p->name + ".fbe." + *s->name + "Model();");
      WriteLineIndent("writer.serialize(this);");
      WriteLine();
      WriteLineIndent("// Deserialize the struct from the FBE stream");
      WriteLineIndent("var reader = new " + *p->name + ".fbe." + *s->name + "Model();");
      WriteLineIndent("reader.attach(writer.getBuffer());");
      WriteLineIndent("return reader.deserialize();");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct compareTo() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int compareTo(Object other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if (other == null)");
      Indent(1);
      WriteLineIndent("return -1;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("if (!" + *s->name + ".class.isAssignableFrom(other.getClass()))");
      Indent(1);
      WriteLineIndent("return -1;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("final " + *s->name + " obj = (" + *s->name + ")other;");
      WriteLine();
      WriteLineIndent("int result = 0;");
      if (s->base && !s->base->empty())
      {
          WriteLineIndent("result = super.compareTo(obj);");
          WriteLineIndent("if (result != 0)");
          Indent(1);
          WriteLineIndent("return result;");
          Indent(-1);
      }
      if (s->body)
      {
          for (const auto& field : s->body->fields)
          {
              if (field->keys)
              {
                  if (IsPrimitiveType(*field->type, field->optional))
                      WriteLineIndent("result = " + ConvertPrimitiveTypeName(*field->type)  + ".compare(" + *field->name + ", obj." + *field->name + ");");
                  else
                      WriteLineIndent("result = " + *field->name + ".compareTo(obj." + *field->name + ");");
                  WriteLineIndent("if (result != 0)");
                  Indent(1);
                  WriteLineIndent("return result;");
                  Indent(-1);
              }
          }
      }
      WriteLineIndent("return result;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct equals() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean equals(Object other)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if (other == null)");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("if (!" + *s->name + ".class.isAssignableFrom(other.getClass()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("final " + *s->name + " obj = (" + *s->name + ")other;");
      WriteLine();
      if (s->base && !s->base->empty())
      {
          WriteLineIndent("if (!super.equals(obj))");
          Indent(1);
          WriteLineIndent("return false;");
          Indent(-1);
      }
      if (s->body)
      {
          for (const auto& field : s->body->fields)
          {
              if (field->keys)
              {
                  if (IsPrimitiveType(*field->type, field->optional))
                      WriteLineIndent("if (" + *field->name + " != obj." + *field->name + ")");
                  else
                      WriteLineIndent("if (!" + *field->name + ".equals(obj." + *field->name + "))");
                  Indent(1);
                  WriteLineIndent("return false;");
                  Indent(-1);
              }
          }
      }
      WriteLineIndent("return true;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct hashCode() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public int hashCode()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("int hash = 17;");
      if (s->base && !s->base->empty())
          WriteLineIndent("hash = hash * 31 + super.hashCode();");
      if (s->body)
      {
          for (const auto& field : s->body->fields)
          {
              if (field->keys)
              {
                  if (IsPrimitiveType(*field->type, field->optional))
                      WriteLineIndent("hash = hash * 31 + " + ConvertPrimitiveTypeName(*field->type) + ".hashCode(" + *field->name + ");");
                  else
                      WriteLineIndent("hash = hash * 31 + " + *field->name + ".hashCode();");
              }
          }
      }
      WriteLineIndent("return hash;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct toString() method
      WriteLine();
      WriteLineIndent("@Override");
      WriteLineIndent("public String toString()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("var sb = new StringBuilder();");
      WriteLineIndent("sb.append(\"" + *s->name + "(\");");
      first = true;
      if (s->base && !s->base->empty())
      {
          WriteLineIndent("sb.append(super.toString());");
          first = false;
      }
      if (s->body)
      {
          for (const auto& field : s->body->fields)
          {
              if (field->array)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".length" + ").append(\"][\");");
                  WriteLineIndent("for (var item : " + *field->name + ")");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\"]\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                  Indent(-1);
              }
              else if (field->vector)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"][\");");
                  WriteLineIndent("for (var item : " + *field->name + ")");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\"]\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][]\");");
                  Indent(-1);
              }
              else if (field->list)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]<\");");
                  WriteLineIndent("for (var item : " + *field->name + ")");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\">\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<>\");");
                  Indent(-1);
              }
              else if (field->set)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]{\");");
                  WriteLineIndent("for (var item : " + *field->name + ")");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\"}\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]{}\");");
                  Indent(-1);
              }
              else if (field->map)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"]<{\");");
                  WriteLineIndent("for (var item : " + *field->name + ".entrySet())");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.getKey()", false, true));
                  WriteLineIndent("sb.append(\"->\");");
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.getValue()", field->optional, false));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\"}>\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0]<{}>\");");
                  Indent(-1);
              }
              else if (field->hash)
              {
                  WriteLineIndent("if (" + *field->name + " != null)");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent("boolean first = true;");
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size()" + ").append(\"][{\");");
                  WriteLineIndent("for (var item : " + *field->name + ".entrySet())");
                  WriteLineIndent("{");
                  Indent(1);
                  WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.getKey()", false, true));
                  WriteLineIndent("sb.append(\"->\");");
                  WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.getValue()", field->optional, false));
                  WriteLineIndent("first = false;");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("sb.append(\"}]\");");
                  Indent(-1);
                  WriteLineIndent("}");
                  WriteLineIndent("else");
                  Indent(1);
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[0][{}]\");");
                  Indent(-1);
              }
              else
                  WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=\"); " + ConvertOutputStreamValue(*field->type, *field->name, field->optional, false));
              first = false;
          }
      }
      WriteLineIndent("sb.append(\")\");");
      WriteLineIndent("return sb.toString();");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct JSON methods
      if (JSON())
      {
          WriteLine();
          WriteLineIndent("public String toJson() { return " + *p->name + ".fbe.Json.getEngine().toJson(this); }");
          WriteLineIndent("public static " + *s->name + " fromJson(String json) { return " + *p->name + ".fbe.Json.getEngine().fromJson(json, " + *s->name + ".class); }");
      }
  
      // Generate struct end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct footer
      GenerateFooter();
  
      // Close the output file
      Close();
  
      // Generate struct field models
      GenerateStructFieldModel(p, s);
      GenerateStructModel(p, s);
  
      // Generate struct final models
      if (Final())
      {
          GenerateStructFinalModel(p, s);
          GenerateStructModelFinal(p, s);
      }
  }
  
  void GeneratorJava::GenerateStructFieldModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FieldModel" + *s->name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate struct field model begin
      WriteLine();
      WriteLineIndent("// Fast Binary Encoding " + *s->name + " field model");
      WriteLineIndent("public final class FieldModel" + *s->name + " extends FieldModel");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate struct field model accessors
      if (s->base && !s->base->empty())
          WriteLineIndent("public final " + ConvertBaseFieldName(*s->base, false) + " parent;");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("public final " + ConvertTypeFieldDeclaration(*field, false) + " " + *field->name + ";");
  
      // Generate struct field model constructor
      WriteLine();
      WriteLineIndent("public FieldModel" + *s->name + "(Buffer buffer, long offset)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(buffer, offset);");
      std::string prev_offset("4");
      std::string prev_size("4");
      if (s->base && !s->base->empty())
      {
          WriteLineIndent("parent = new " + ConvertBaseFieldName(*s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ");");
          prev_offset = "parent.fbeOffset()";
          prev_size = "parent.fbeBody() - 4 - 4";
      }
      if (s->body)
      {
          for (const auto& field : s->body->fields)
          {
              WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(*field, prev_offset + " + " + prev_size, false) + ";");
              prev_offset = *field->name + ".fbeOffset()";
              prev_size = *field->name + ".fbeSize()";
          }
      }
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model FBE properties
      WriteLine();
      WriteLineIndent("// Get the field size");
      WriteLineIndent("@Override");
      WriteLineIndent("public long fbeSize() { return 4; }");
      WriteLineIndent("// Get the field body size");
      WriteLineIndent("public long fbeBody()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeResult = 4 + 4");
      Indent(1);
      if (s->base && !s->base->empty())
          WriteLineIndent("+ parent.fbeBody() - 4 - 4");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("+ " + *field->name + ".fbeSize()");
      WriteLineIndent(";");
      Indent(-1);
      WriteLineIndent("return fbeResult;");
      Indent(-1);
      WriteLineIndent("}");
      WriteLineIndent("// Get the field extra size");
      WriteLineIndent("@Override");
      WriteLineIndent("public long fbeExtra()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
      WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4) > _buffer.getSize()))");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("_buffer.shift(fbeStructOffset);");
      WriteLine();
      WriteLineIndent("long fbeResult = fbeBody()");
      Indent(1);
      if (s->base && !s->base->empty())
          WriteLineIndent("+ parent.fbeExtra()");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("+ " + *field->name + ".fbeExtra()");
      WriteLineIndent(";");
      Indent(-1);
      WriteLine();
      WriteLineIndent("_buffer.unshift(fbeStructOffset);");
      WriteLine();
      WriteLineIndent("return fbeResult;");
      Indent(-1);
      WriteLineIndent("}");
      WriteLineIndent("// Get the field type");
      if (s->base && !s->base->empty() && (s->type == 0))
          WriteLineIndent("public static final long fbeTypeConst = " + ConvertBaseFieldName(*s->base, false) + ".fbeTypeConst;");
      else
          WriteLineIndent("public static final long fbeTypeConst = " + std::to_string(s->type) + ";");
      WriteLineIndent("public long fbeType() { return fbeTypeConst; }");
  
      // Generate struct field model verify() methods
      WriteLine();
      WriteLineIndent("// Check if the struct value is valid");
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean verify() { return verify(true); }");
      WriteLineIndent("public boolean verify(boolean fbeVerifyType)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
      Indent(1);
      WriteLineIndent("return true;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
      WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4 + 4) > _buffer.getSize()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructSize = readInt32(fbeStructOffset);");
      WriteLineIndent("if (fbeStructSize < (4 + 4))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructType = readInt32(fbeStructOffset + 4);");
      WriteLineIndent("if (fbeVerifyType && (fbeStructType != fbeType()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("_buffer.shift(fbeStructOffset);");
      WriteLineIndent("boolean fbeResult = verifyFields(fbeStructSize);");
      WriteLineIndent("_buffer.unshift(fbeStructOffset);");
      WriteLineIndent("return fbeResult;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model verifyFields() method
      WriteLine();
      WriteLineIndent("// Check if the struct fields are valid");
      WriteLineIndent("public boolean verifyFields(long fbeStructSize)");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          WriteLineIndent("long fbeCurrentSize = 4 + 4;");
          if (s->base && !s->base->empty())
          {
              WriteLine();
              WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody() - 4 - 4) > fbeStructSize)");
              Indent(1);
              WriteLineIndent("return true;");
              Indent(-1);
              WriteLineIndent("if (!parent.verifyFields(fbeStructSize))");
              Indent(1);
              WriteLineIndent("return false;");
              Indent(-1);
              WriteLineIndent("fbeCurrentSize += parent.fbeBody() - 4 - 4;");
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  WriteLine();
                  WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize()) > fbeStructSize)");
                  Indent(1);
                  WriteLineIndent("return true;");
                  Indent(-1);
                  WriteLineIndent("if (!" + *field->name + ".verify())");
                  Indent(1);
                  WriteLineIndent("return false;");
                  Indent(-1);
                  WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize();");
              }
          }
          WriteLine();
      }
      WriteLineIndent("return true;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model getBegin() method
      WriteLine();
      WriteLineIndent("// Get the struct value (begin phase)");
      WriteLineIndent("public long getBegin()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructOffset = readInt32(fbeOffset());");
      WriteLineIndent("assert ((fbeStructOffset > 0) && ((_buffer.getOffset() + fbeStructOffset + 4 + 4) <= _buffer.getSize())) : \"Model is broken!\";");
      WriteLineIndent("if ((fbeStructOffset == 0) || ((_buffer.getOffset() + fbeStructOffset + 4 + 4) > _buffer.getSize()))");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructSize = readInt32(fbeStructOffset);");
      WriteLineIndent("assert (fbeStructSize >= (4 + 4)) : \"Model is broken!\";");
      WriteLineIndent("if (fbeStructSize < (4 + 4))");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("_buffer.shift(fbeStructOffset);");
      WriteLineIndent("return fbeStructOffset;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model getEnd() method
      WriteLine();
      WriteLineIndent("// Get the struct value (end phase)");
      WriteLineIndent("public void getEnd(long fbeBegin)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_buffer.unshift(fbeBegin);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model get() methods
      WriteLine();
      WriteLineIndent("// Get the struct value");
      WriteLineIndent("public " + *s->name + " get() { return get(new " + *s->name + "()); }");
      WriteLineIndent("public " + *s->name + " get(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeBegin = getBegin();");
      WriteLineIndent("if (fbeBegin == 0)");
      Indent(1);
      WriteLineIndent("return fbeValue;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructSize = readInt32(0);");
      WriteLineIndent("getFields(fbeValue, fbeStructSize);");
      WriteLineIndent("getEnd(fbeBegin);");
      WriteLineIndent("return fbeValue;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model getFields() method
      WriteLine();
      WriteLineIndent("// Get the struct fields values");
      WriteLineIndent("public void getFields(" + *s->name + " fbeValue, long fbeStructSize)");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          WriteLineIndent("long fbeCurrentSize = 4 + 4;");
          if (s->base && !s->base->empty())
          {
              WriteLine();
              WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody() - 4 - 4) <= fbeStructSize)");
              Indent(1);
              WriteLineIndent("parent.getFields(fbeValue, fbeStructSize);");
              Indent(-1);
              WriteLineIndent("fbeCurrentSize += parent.fbeBody() - 4 - 4;");
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  WriteLine();
                  WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize()) <= fbeStructSize)");
                  Indent(1);
                  if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                      WriteLineIndent(*field->name + ".get(fbeValue." + *field->name + ");");
                  else
                      WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(" + (field->value ? ConvertConstant(*field->type, *field->value, field->optional) : "") + ");");
                  Indent(-1);
                  WriteLineIndent("else");
                  Indent(1);
                  if (field->vector || field->list || field->set || field->map || field->hash)
                      WriteLineIndent("fbeValue." + *field->name + ".clear();");
                  else
                      WriteLineIndent("fbeValue." + *field->name + " = " + ConvertDefault(*field) + ";");
                  Indent(-1);
                  WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize();");
              }
          }
      }
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model setBegin() method
      WriteLine();
      WriteLineIndent("// Set the struct value (begin phase)");
      WriteLineIndent("public long setBegin()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("assert ((_buffer.getOffset() + fbeOffset() + fbeSize()) <= _buffer.getSize()) : \"Model is broken!\";");
      WriteLineIndent("if ((_buffer.getOffset() + fbeOffset() + fbeSize()) > _buffer.getSize())");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructSize = (int)fbeBody();");
      WriteLineIndent("int fbeStructOffset = (int)(_buffer.allocate(fbeStructSize) - _buffer.getOffset());");
      WriteLineIndent("assert ((fbeStructOffset > 0) && ((_buffer.getOffset() + fbeStructOffset + fbeStructSize) <= _buffer.getSize())) : \"Model is broken!\";");
      WriteLineIndent("if ((fbeStructOffset <= 0) || ((_buffer.getOffset() + fbeStructOffset + fbeStructSize) > _buffer.getSize()))");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("write(fbeOffset(), fbeStructOffset);");
      WriteLineIndent("write(fbeStructOffset, fbeStructSize);");
      WriteLineIndent("write(fbeStructOffset + 4, (int)fbeType());");
      WriteLine();
      WriteLineIndent("_buffer.shift(fbeStructOffset);");
      WriteLineIndent("return fbeStructOffset;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model setEnd() method
      WriteLine();
      WriteLineIndent("// Set the struct value (end phase)");
      WriteLineIndent("public void setEnd(long fbeBegin)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_buffer.unshift(fbeBegin);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model set() method
      WriteLine();
      WriteLineIndent("// Set the struct value");
      WriteLineIndent("public void set(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeBegin = setBegin();");
      WriteLineIndent("if (fbeBegin == 0)");
      Indent(1);
      WriteLineIndent("return;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("setFields(fbeValue);");
      WriteLineIndent("setEnd(fbeBegin);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model setFields() method
      WriteLine();
      WriteLineIndent("// Set the struct fields values");
      WriteLineIndent("public void setFields(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          if (s->base && !s->base->empty())
              WriteLineIndent("parent.setFields(fbeValue);");
          if (s->body)
              for (const auto& field : s->body->fields)
                  WriteLineIndent(*field->name + ".set(fbeValue." + *field->name + ");");
      }
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct field model end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateStructModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / (*s->name + "Model.java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate struct model begin
      WriteLine();
      WriteLineIndent("// Fast Binary Encoding " + *s->name + " model");
      WriteLineIndent("public final class " + *s->name + "Model extends Model");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate struct model accessor
      WriteLineIndent("public final FieldModel" + *s->name + " model;");
  
      // Generate struct model constructors
      WriteLine();
      WriteLineIndent("public " + *s->name + "Model() { model = new FieldModel" + *s->name + "(getBuffer(), 4); }");
      WriteLineIndent("public " + *s->name + "Model(Buffer buffer) { super(buffer); model = new FieldModel" + *s->name + "(getBuffer(), 4); }");
  
      // Generate struct model FBE properties
      WriteLine();
      WriteLineIndent("// Get the model size");
      WriteLineIndent("public long fbeSize() { return model.fbeSize() + model.fbeExtra(); }");
      WriteLineIndent("// Get the model type");
      WriteLineIndent("public static final long fbeTypeConst = FieldModel" + *s->name + ".fbeTypeConst;");
      WriteLineIndent("public long fbeType() { return fbeTypeConst; }");
  
      // Generate struct model verify() method
      WriteLine();
      WriteLineIndent("// Check if the struct value is valid");
      WriteLineIndent("public boolean verify()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((getBuffer().getOffset() + model.fbeOffset() - 4) > getBuffer().getSize())");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeFullSize = readInt32(model.fbeOffset() - 4);");
      WriteLineIndent("if (fbeFullSize < model.fbeSize())");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("return model.verify();");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model createBegin() method
      WriteLine();
      WriteLineIndent("// Create a new model (begin phase)");
      WriteLineIndent("public long createBegin()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeBegin = getBuffer().allocate(4 + model.fbeSize());");
      WriteLineIndent("return fbeBegin;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model createEnd() method
      WriteLine();
      WriteLineIndent("// Create a new model (end phase)");
      WriteLineIndent("public long createEnd(long fbeBegin)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeEnd = getBuffer().getSize();");
      WriteLineIndent("int fbeFullSize = (int)(fbeEnd - fbeBegin);");
      WriteLineIndent("write(model.fbeOffset() - 4, fbeFullSize);");
      WriteLineIndent("return fbeFullSize;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model serialize() method
      WriteLine();
      WriteLineIndent("// Serialize the struct value");
      WriteLineIndent("public long serialize(" + *s->name + " value)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeBegin = createBegin();");
      WriteLineIndent("model.set(value);");
      WriteLineIndent("long fbeFullSize = createEnd(fbeBegin);");
      WriteLineIndent("return fbeFullSize;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model deserialize() methods
      WriteLine();
      WriteLineIndent("// Deserialize the struct value");
      WriteLineIndent("public " + *s->name + " deserialize() { var value = new " + *s->name + "(); deserialize(value); return value; }");
      WriteLineIndent("public long deserialize(" + *s->name + " value)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((getBuffer().getOffset() + model.fbeOffset() - 4) > getBuffer().getSize())");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("value = new " + *s->name + "();");
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
      WriteLineIndent("int fbeFullSize = readInt32(model.fbeOffset() - 4);");
      WriteLineIndent("assert (fbeFullSize >= model.fbeSize()) : \"Model is broken!\";");
      WriteLineIndent("if (fbeFullSize < model.fbeSize())");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("value = new " + *s->name + "();");
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
      WriteLineIndent("value = model.get(value);");
      WriteLineIndent("return fbeFullSize;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model next() method
      WriteLine();
      WriteLineIndent("// Move to the next struct value");
      WriteLineIndent("public void next(long prev)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("model.fbeShift(prev);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateStructFinalModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / ("FinalModel" + *s->name + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate struct final model begin
      WriteLine();
      WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
      WriteLineIndent("public final class FinalModel" + *s->name + " extends FinalModel");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate struct final model accessors
      if (s->base && !s->base->empty())
          WriteLineIndent("public final " + ConvertBaseFieldName(*s->base, true) + " parent;");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("public final " + ConvertTypeFieldDeclaration(*field, true) + " " + *field->name + ";");
  
      // Generate struct final model constructor
      WriteLine();
      WriteLineIndent("public FinalModel" + *s->name + "(Buffer buffer, long offset)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(buffer, offset);");
      if (s->base && !s->base->empty())
          WriteLineIndent("parent = new " + ConvertBaseFieldName(*s->base, true) + "(buffer, 0);");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent(*field->name + " = " + ConvertTypeFieldInitialization(*field, "0", true) + ";");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model FBE properties
      WriteLine();
      WriteLineIndent("// Get the allocation size");
      WriteLineIndent("public long fbeAllocationSize(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeResult = 0");
      Indent(1);
      if (s->base && !s->base->empty())
          WriteLineIndent("+ parent.fbeAllocationSize(fbeValue)");
      if (s->body)
          for (const auto& field : s->body->fields)
              WriteLineIndent("+ " + *field->name + ".fbeAllocationSize(fbeValue." + *field->name + ")");
      WriteLineIndent(";");
      Indent(-1);
      WriteLineIndent("return fbeResult;");
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
      WriteLineIndent("// Get the final type");
      if (s->base && !s->base->empty() && (s->type == 0))
          WriteLineIndent("public static final long fbeTypeConst = " + ConvertBaseFieldName(*s->base, true) + ".fbeTypeConst;");
      else
          WriteLineIndent("public static final long fbeTypeConst = " + std::to_string(s->type) + ";");
      WriteLineIndent("public long fbeType() { return fbeTypeConst; }");
  
      // Generate struct final model verify() methods
      WriteLine();
      WriteLineIndent("// Check if the struct value is valid");
      WriteLineIndent("@Override");
      WriteLineIndent("public long verify()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_buffer.shift(fbeOffset());");
      WriteLineIndent("long fbeResult = verifyFields();");
      WriteLineIndent("_buffer.unshift(fbeOffset());");
      WriteLineIndent("return fbeResult;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model verifyFields() method
      WriteLine();
      WriteLineIndent("// Check if the struct fields are valid");
      WriteLineIndent("public long verifyFields()");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          WriteLineIndent("long fbeCurrentOffset = 0;");
          WriteLineIndent("long fbeFieldSize = 0;");
          if (s->base && !s->base->empty())
          {
              WriteLine();
              WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
              WriteLineIndent("fbeFieldSize = parent.verifyFields();");
              WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
              Indent(1);
              WriteLineIndent("return Long.MAX_VALUE;");
              Indent(-1);
              WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  WriteLine();
                  WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                  WriteLineIndent("fbeFieldSize = " + *field->name + ".verify();");
                  WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
                  Indent(1);
                  WriteLineIndent("return Long.MAX_VALUE;");
                  Indent(-1);
                  WriteLineIndent("fbeCurrentOffset += fbeFieldSize;");
              }
          }
          WriteLine();
          WriteLineIndent("return fbeCurrentOffset;");
      }
      else
          WriteLineIndent("return 0;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model get() methods
      WriteLine();
      WriteLineIndent("// Get the struct value");
      WriteLineIndent("public " + *s->name + " get(Size fbeSize) { return get(fbeSize, new " + *s->name + "()); }");
      WriteLineIndent("public " + *s->name + " get(Size fbeSize, " + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_buffer.shift(fbeOffset());");
      WriteLineIndent("fbeSize.value = getFields(fbeValue);");
      WriteLineIndent("_buffer.unshift(fbeOffset());");
      WriteLineIndent("return fbeValue;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model getFields() method
      WriteLine();
      WriteLineIndent("// Get the struct fields values");
      WriteLineIndent("public long getFields(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          WriteLineIndent("long fbeCurrentOffset = 0;");
          WriteLineIndent("long fbeCurrentSize = 0;");
          WriteLineIndent("var fbeFieldSize = new Size(0);");
          if (s->base && !s->base->empty())
          {
              WriteLine();
              WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
              WriteLineIndent("fbeFieldSize.value = parent.getFields(fbeValue);");
              WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
              WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  WriteLine();
                  WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                  if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                      WriteLineIndent("fbeFieldSize.value = " + *field->name + ".get(fbeValue." + *field->name + ");");
                  else
                      WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(fbeFieldSize);");
                  WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
                  WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
              }
          }
          WriteLine();
          WriteLineIndent("return fbeCurrentSize;");
      }
      else
          WriteLineIndent("return 0;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model set() method
      WriteLine();
      WriteLineIndent("// Set the struct value");
      WriteLineIndent("public long set(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_buffer.shift(fbeOffset());");
      WriteLineIndent("long fbeSize = setFields(fbeValue);");
      WriteLineIndent("_buffer.unshift(fbeOffset());");
      WriteLineIndent("return fbeSize;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model setFields() method
      WriteLine();
      WriteLineIndent("// Set the struct fields values");
      WriteLineIndent("public long setFields(" + *s->name + " fbeValue)");
      WriteLineIndent("{");
      Indent(1);
      if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
      {
          WriteLineIndent("long fbeCurrentOffset = 0;");
          WriteLineIndent("long fbeCurrentSize = 0;");
          WriteLineIndent("var fbeFieldSize = new Size(0);");
          if (s->base && !s->base->empty())
          {
              WriteLine();
              WriteLineIndent("parent.fbeOffset(fbeCurrentOffset);");
              WriteLineIndent("fbeFieldSize.value = parent.setFields(fbeValue);");
              WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
              WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
          }
          if (s->body)
          {
              for (const auto& field : s->body->fields)
              {
                  WriteLine();
                  WriteLineIndent(*field->name + ".fbeOffset(fbeCurrentOffset);");
                  WriteLineIndent("fbeFieldSize.value = " + *field->name + ".set(fbeValue." + *field->name + ");");
                  WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value;");
                  WriteLineIndent("fbeCurrentSize += fbeFieldSize.value;");
              }
          }
          WriteLine();
          WriteLineIndent("return fbeCurrentSize;");
      }
      else
          WriteLineIndent("return 0;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct final model end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateStructModelFinal(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / (*s->name + "FinalModel.java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate struct model final begin
      WriteLine();
      WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
      WriteLineIndent("public final class " + *s->name + "FinalModel extends Model");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate struct model final accessor
      WriteLineIndent("private final FinalModel" + *s->name + " _model;");
  
      // Generate struct model final constructors
      WriteLine();
      WriteLineIndent("public " + *s->name + "FinalModel() { _model = new FinalModel" + *s->name + "(getBuffer(), 8); }");
      WriteLineIndent("public " + *s->name + "FinalModel(Buffer buffer) { super(buffer); _model = new FinalModel" + *s->name + "(getBuffer(), 8); }");
  
      // Generate struct model final FBE properties
      WriteLine();
      WriteLineIndent("// Get the model type");
      WriteLineIndent("public static final long fbeTypeConst = FinalModel" + *s->name + ".fbeTypeConst;");
      WriteLineIndent("public long fbeType() { return fbeTypeConst; }");
  
      // Generate struct model final verify() method
      WriteLine();
      WriteLineIndent("// Check if the struct value is valid");
      WriteLineIndent("public boolean verify()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("if ((getBuffer().getOffset() + _model.fbeOffset()) > getBuffer().getSize())");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("int fbeStructSize = readInt32(_model.fbeOffset() - 8);");
      WriteLineIndent("int fbeStructType = readInt32(_model.fbeOffset() - 4);");
      WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType()))");
      Indent(1);
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("return ((8 + _model.verify()) == fbeStructSize);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model final serialize() method
      WriteLine();
      WriteLineIndent("// Serialize the struct value");
      WriteLineIndent("public long serialize(" + *s->name + " value)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("long fbeInitialSize = getBuffer().getSize();");
      WriteLine();
      WriteLineIndent("int fbeStructType = (int)fbeType();");
      WriteLineIndent("int fbeStructSize = (int)(8 + _model.fbeAllocationSize(value));");
      WriteLineIndent("int fbeStructOffset = (int)(getBuffer().allocate(fbeStructSize) - getBuffer().getOffset());");
      WriteLineIndent("assert ((getBuffer().getOffset() + fbeStructOffset + fbeStructSize) <= getBuffer().getSize()) : \"Model is broken!\";");
      WriteLineIndent("if ((getBuffer().getOffset() + fbeStructOffset + fbeStructSize) > getBuffer().getSize())");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("fbeStructSize = (int)(8 + _model.set(value));");
      WriteLineIndent("getBuffer().resize(fbeInitialSize + fbeStructSize);");
      WriteLine();
      WriteLineIndent("write(_model.fbeOffset() - 8, fbeStructSize);");
      WriteLineIndent("write(_model.fbeOffset() - 4, fbeStructType);");
      WriteLine();
      WriteLineIndent("return fbeStructSize;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model final deserialize() methods
      WriteLine();
      WriteLineIndent("// Deserialize the struct value");
      WriteLineIndent("public " + *s->name + " deserialize() { var value = new " + *s->name + "(); deserialize(value); return value; }");
      WriteLineIndent("public long deserialize(" + *s->name + " value)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("assert ((getBuffer().getOffset() + _model.fbeOffset()) <= getBuffer().getSize()) : \"Model is broken!\";");
      WriteLineIndent("if ((getBuffer().getOffset() + _model.fbeOffset()) > getBuffer().getSize())");
      Indent(1);
      WriteLineIndent("return 0;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("long fbeStructSize = readInt32(_model.fbeOffset() - 8);");
      WriteLineIndent("long fbeStructType = readInt32(_model.fbeOffset() - 4);");
      WriteLineIndent("assert ((fbeStructSize > 0) && (fbeStructType == fbeType())) : \"Model is broken!\";");
      WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType()))");
      Indent(1);
      WriteLineIndent("return 8;");
      Indent(-1);
      WriteLine();
      WriteLineIndent("var fbeSize = new Size(0);");
      WriteLineIndent("value = _model.get(fbeSize, value);");
      WriteLineIndent("return 8 + fbeSize.value;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model final next() method
      WriteLine();
      WriteLineIndent("// Move to the next struct value");
      WriteLineIndent("public void next(long prev)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_model.fbeShift(prev);");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate struct model final end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateSender(const std::shared_ptr<Package>& p, bool final)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      std::string sender = (final ? "FinalSender" : "Sender");
      std::string model = (final ? "FinalModel" : "Model");
  
      // Open the file
      CppCommon::Path file = path / (sender + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate sender begin
      WriteLine();
      if (final)
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " final sender");
      else
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " sender");
      WriteLineIndent("public class " + sender + " extends fbe.Sender");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate imported senders accessors
      if (p->import)
      {
          WriteLineIndent("// Imported senders");
          for (const auto& import : p->import->imports)
              WriteLineIndent("public final " + *import + ".fbe." + sender + " " + *import + "Sender;");
          WriteLine();
      }
  
      // Generate sender models accessors
      if (p->body)
      {
          WriteLineIndent("// Sender models accessors");
          for (const auto& s : p->body->structs)
              WriteLineIndent("public final " + *s->name + model + " " + *s->name + "Model;");
          WriteLine();
      }
  
      // Generate sender constructors
      WriteLineIndent("public " + sender + "()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Sender = new " + *import + ".fbe." + sender + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(getBuffer());");
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLineIndent("public " + sender + "(Buffer buffer)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Sender = new " + *import + ".fbe." + sender + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "(getBuffer());");
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
  
      // Generate sender methods
      if (p->body)
      {
          for (const auto& s : p->body->structs)
          {
              std::string struct_name = *p->name + "." + *s->name;
              WriteLineIndent("public long send(" + struct_name + " value)");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("// Serialize the value into the FBE stream");
              WriteLineIndent("long serialized = " + *s->name + "Model.serialize(value);");
              WriteLineIndent("assert (serialized > 0) : \"" + *p->name + "." + *s->name + " serialization failed!\";");
              WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + *p->name + "." + *s->name + " validation failed!\";");
              WriteLine();
              WriteLineIndent("// Log the value");
              WriteLineIndent("if (getLogging())");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("String message = value.toString();");
              WriteLineIndent("onSendLog(message);");
              Indent(-1);
              WriteLineIndent("}");
              WriteLine();
              WriteLineIndent("// Send the serialized value");
              WriteLineIndent("return sendSerialized(serialized);");
              Indent(-1);
              WriteLineIndent("}");
          }
      }
  
      // Generate sender message handler
      WriteLine();
      WriteLineIndent("// Send message handler");
      WriteLineIndent("@Override");
      WriteLineIndent("protected long onSend(byte[] buffer, long offset, long size) { throw new UnsupportedOperationException(\"" + *p->name + ".fbe.Sender.onSend() not implemented!\"); }");
  
      // Generate sender end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      std::string receiver = (final ? "FinalReceiver" : "Receiver");
      std::string model = (final ? "FinalModel" : "Model");
  
      // Open the file
      CppCommon::Path file = path / (receiver + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate receiver begin
      WriteLine();
      if (final)
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " final receiver");
      else
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " receiver");
      WriteLineIndent("public class " + receiver + " extends fbe.Receiver");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate imported receivers accessors
      if (p->import)
      {
          WriteLineIndent("// Imported receivers");
          for (const auto& import : p->import->imports)
              WriteLineIndent("public " + *import + ".fbe." + receiver + " " + *import + "Receiver;");
          WriteLine();
      }
  
      // Generate receiver models accessors
      if (p->body)
      {
          WriteLineIndent("// Receiver values accessors");
          for (const auto& s : p->body->structs)
          {
              std::string struct_name = *p->name + "." + *s->name;
              WriteLineIndent("private final " + struct_name + " " + *s->name + "Value;");
          }
          WriteLine();
          WriteLineIndent("// Receiver models accessors");
          for (const auto& s : p->body->structs)
              WriteLineIndent("private final " + *s->name + model + " " + *s->name + "Model;");
          WriteLine();
      }
  
      // Generate receiver constructors
      WriteLineIndent("public " + receiver + "()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Receiver = new " + *import + ".fbe." + receiver + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
          {
              std::string struct_name = *p->name + "." + *s->name;
              WriteLineIndent(*s->name + "Value = new " + struct_name + "();");
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
          }
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLineIndent("public " + receiver + "(Buffer buffer)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Receiver = new " + *import + ".fbe." + receiver + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
          {
              std::string struct_name = *p->name + "." + *s->name;
              WriteLineIndent(*s->name + "Value = new " + struct_name + "();");
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
          }
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
  
      // Generate receiver handlers
      if (p->body)
      {
          WriteLineIndent("// Receive handlers");
          for (const auto& s : p->body->structs)
          {
              std::string struct_name = *p->name + "." + *s->name;
              WriteLineIndent("protected void onReceive(" + struct_name + " value) {}");
          }
          WriteLine();
      }
  
      // Generate receiver message handler
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean onReceive(long type, byte[] buffer, long offset, long size)");
      WriteLineIndent("{");
      Indent(1);
      if (p->body)
      {
          WriteLineIndent("switch ((int)type)");
          WriteLineIndent("{");
          Indent(1);
          for (const auto& s : p->body->structs)
          {
              WriteLineIndent("case (int)" + package + ".fbe." + *s->name + model + ".fbeTypeConst:");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("// Deserialize the value from the FBE stream");
              WriteLineIndent(*s->name + "Model.attach(buffer, offset);");
              WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + *p->name + "." + *s->name + " validation failed!\";");
              WriteLineIndent("long deserialized = " + *s->name + "Model.deserialize(" + *s->name + "Value);");
              WriteLineIndent("assert (deserialized > 0) : \"" + *p->name + "." + *s->name + " deserialization failed!\";");
              WriteLine();
              WriteLineIndent("// Log the value");
              WriteLineIndent("if (getLogging())");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("String message = " + *s->name + "Value.toString();");
              WriteLineIndent("onReceiveLog(message);");
              Indent(-1);
              WriteLineIndent("}");
              WriteLine();
              WriteLineIndent("// Call receive handler with deserialized value");
              WriteLineIndent("onReceive(" + *s->name + "Value);");
              WriteLineIndent("return true;");
              Indent(-1);
              WriteLineIndent("}");
          }
          Indent(-1);
          WriteLineIndent("}");
      }
      if (p->import)
      {
          WriteLine();
          for (const auto& import : p->import->imports)
          {
              WriteLineIndent("if ((" + *import + "Receiver != null) && " + *import + "Receiver.onReceive(type, buffer, offset, size))");
              Indent(1);
              WriteLineIndent("return true;");
              Indent(-1);
          }
      }
      WriteLine();
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate receiver end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      std::string proxy = (final ? "FinalProxy" : "Proxy");
      std::string model = (final ? "FinalModel" : "Model");
  
      // Open the file
      CppCommon::Path file = path / (proxy + ".java");
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
  
      // Generate proxy begin
      WriteLine();
      if (final)
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " final proxy");
      else
          WriteLineIndent("// Fast Binary Encoding " + *p->name + " proxy");
      WriteLineIndent("public class " + proxy + " extends fbe.Receiver");
      WriteLineIndent("{");
      Indent(1);
  
      // Generate imported proxy accessors
      if (p->import)
      {
          WriteLineIndent("// Imported proxy");
          for (const auto& import : p->import->imports)
              WriteLineIndent("public " + *import + ".fbe." + proxy + " " + *import + "Proxy;");
          WriteLine();
      }
  
      // Generate proxy models accessors
      if (p->body)
      {
          WriteLineIndent("// Proxy models accessors");
          for (const auto& s : p->body->structs)
              WriteLineIndent("private final " + *s->name + model + " " + *s->name + "Model;");
          WriteLine();
      }
  
      // Generate proxy constructors
      WriteLineIndent("public " + proxy + "()");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(" + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Proxy = new " + *import + ".fbe." + proxy + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLineIndent("public " + proxy + "(Buffer buffer)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("super(buffer, " + std::string(final ? "true" : "false") + ");");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + "Proxy = new " + *import + ".fbe." + proxy + "(getBuffer());");
      }
      if (p->body)
      {
          for (const auto& s : p->body->structs)
              WriteLineIndent(*s->name + "Model = new " + *s->name + model + "();");
      }
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
  
      // Generate proxy handlers
      if (p->body)
      {
          WriteLineIndent("// Proxy handlers");
          for (const auto& s : p->body->structs)
          {
              std::string struct_model = *s->name + model;
              WriteLineIndent("protected void onProxy(" + struct_model + " model, long type, byte[] buffer, long offset, long size) {}");
          }
          WriteLine();
      }
  
      // Generate proxy message handler
      WriteLineIndent("@Override");
      WriteLineIndent("public boolean onReceive(long type, byte[] buffer, long offset, long size)");
      WriteLineIndent("{");
      Indent(1);
      if (p->body)
      {
          WriteLineIndent("switch ((int)type)");
          WriteLineIndent("{");
          Indent(1);
          for (const auto& s : p->body->structs)
          {
              WriteLineIndent("case (int)" + package + ".fbe." + *s->name + model + ".fbeTypeConst:");
              WriteLineIndent("{");
              Indent(1);
              WriteLineIndent("// Attach the FBE stream to the proxy model");
              WriteLineIndent(*s->name + "Model.attach(buffer, offset);");
              WriteLineIndent("assert " + *s->name + "Model.verify() : \"" + *p->name + "." + *s->name + " validation failed!\";");
              WriteLine();
              WriteLineIndent("long fbeBegin = " + *s->name + "Model.model.getBegin();");
              WriteLineIndent("if (fbeBegin == 0)");
              Indent(1);
              WriteLineIndent("return false;");
              Indent(-1);
              WriteLineIndent("// Call proxy handler");
              WriteLineIndent("onProxy(" + *s->name + "Model, type, buffer, offset, size);");
              WriteLineIndent(*s->name + "Model.model.getEnd(fbeBegin);");
              WriteLineIndent("return true;");
              Indent(-1);
              WriteLineIndent("}");
          }
          Indent(-1);
          WriteLineIndent("}");
      }
      if (p->import)
      {
          WriteLine();
          for (const auto& import : p->import->imports)
          {
              WriteLineIndent("if ((" + *import + "Proxy != null) && " + *import + "Proxy.onReceive(type, buffer, offset, size))");
              Indent(1);
              WriteLineIndent("return true;");
              Indent(-1);
          }
      }
      WriteLine();
      WriteLineIndent("return false;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate proxy end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  void GeneratorJava::GenerateJson(const std::shared_ptr<Package>& p)
  {
      std::string package = *p->name;
  
      CppCommon::Path path = (CppCommon::Path(_output) / package) / "fbe";
  
      // Create package path
      CppCommon::Directory::CreateTree(path);
  
      // Open the file
      CppCommon::Path file = path / "Json.java";
      Open(file);
  
      // Generate headers
      GenerateHeader(CppCommon::Path(_input).filename().string());
      GenerateImports(package + ".fbe");
  
      // Generate custom import
      WriteLine();
      WriteLineIndent("import fbe.*;");
      WriteLineIndent("import " + package + ".*;");
      WriteLine();
      WriteLineIndent("import com.google.gson.*;");
  
      // Generate JSON engine begin
      WriteLine();
      WriteLineIndent("// Fast Binary Encoding " + *p->name + " JSON engine");
      WriteLineIndent("public final class Json");
      WriteLineIndent("{");
      Indent(1);
  
      WriteLineIndent("private static final Gson _engine;");
      WriteLine();
      WriteLineIndent("// Get the JSON engine");
      WriteLineIndent("public static Gson getEngine() { return _engine; }");
      WriteLine();
  
      // Generate JSON engine static initialization
      WriteLineIndent("static");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("_engine = register(new GsonBuilder()).create();");
      Indent(-1);
      WriteLineIndent("}");
      WriteLine();
  
      // Generate JSON engine private constructor
      WriteLineIndent("private Json() {}");
      WriteLine();
  
      // Generate JSON engine Register() method
      WriteLineIndent("public static GsonBuilder register(GsonBuilder builder)");
      WriteLineIndent("{");
      Indent(1);
      WriteLineIndent("fbe.Json.register(builder);");
      if (p->import)
      {
          for (const auto& import : p->import->imports)
              WriteLineIndent(*import + ".fbe.Json.register(builder);");
      }
      if (p->body)
      {
          for (const auto& e : p->body->enums)
              WriteLineIndent("builder.registerTypeAdapter(" + *e->name + ".class, new " + *e->name + "Json());");
          for (const auto& f : p->body->flags)
              WriteLineIndent("builder.registerTypeAdapter(" + *f->name + ".class, new " + *f->name + "Json());");
      }
      WriteLineIndent("return builder;");
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate JSON engine end
      Indent(-1);
      WriteLineIndent("}");
  
      // Generate footer
      GenerateFooter();
  
      // Close the file
      Close();
  }
  
  bool GeneratorJava::IsKnownType(const std::string& type)
  {
      return ((type == "bool") ||
              (type == "byte") || (type == "bytes") ||
              (type == "char") || (type == "wchar") ||
              (type == "int8") || (type == "uint8") ||
              (type == "int16") || (type == "uint16") ||
              (type == "int32") || (type == "uint32") ||
              (type == "int64") || (type == "uint64") ||
              (type == "float") || (type == "double") ||
              (type == "decimal") || (type == "string") ||
              (type == "timestamp") || (type == "uuid"));
  }
  
  bool GeneratorJava::IsPrimitiveType(const std::string& type, bool optional)
  {
      if (optional)
          return false;
  
      return ((type == "bool") || (type == "byte") ||
              (type == "char") || (type == "wchar") ||
              (type == "int8") || (type == "uint8") ||
              (type == "int16") || (type == "uint16") ||
              (type == "int32") || (type == "uint32") ||
              (type == "int64") || (type == "uint64") ||
              (type == "float") || (type == "double"));
  }
  
  std::string GeneratorJava::ConvertEnumBase(const std::string& type)
  {
      if (type == "byte")
          return "Byte";
      else if (type == "char")
          return "Byte";
      else if (type == "wchar")
          return "Integer";
      else if (type == "int8")
          return "Byte";
      else if (type == "uint8")
          return "Byte";
      else if (type == "int16")
          return "Short";
      else if (type == "uint16")
          return "Short";
      else if (type == "int32")
          return "Integer";
      else if (type == "uint32")
          return "Integer";
      else if (type == "int64")
          return "Long";
      else if (type == "uint64")
          return "Long";
  
      yyerror("Unsupported enum base type - " + type);
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumType(const std::string& type)
  {
      if (type == "byte")
          return "byte";
      else if (type == "char")
          return "byte";
      else if (type == "wchar")
          return "int";
      else if (type == "int8")
          return "byte";
      else if (type == "uint8")
          return "byte";
      else if (type == "int16")
          return "short";
      else if (type == "uint16")
          return "short";
      else if (type == "int32")
          return "int";
      else if (type == "uint32")
          return "int";
      else if (type == "int64")
          return "long";
      else if (type == "uint64")
          return "long";
  
      yyerror("Unsupported enum base type - " + type);
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumSize(const std::string& type)
  {
      if (type == "byte")
          return "1";
      else if (type == "char")
          return "1";
      else if (type == "wchar")
          return "4";
      else if (type == "int8")
          return "1";
      else if (type == "uint8")
          return "1";
      else if (type == "int16")
          return "2";
      else if (type == "uint16")
          return "2";
      else if (type == "int32")
          return "4";
      else if (type == "uint32")
          return "4";
      else if (type == "int64")
          return "8";
      else if (type == "uint64")
          return "8";
  
      yyerror("Unsupported enum base type - " + type);
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumGet(const std::string& type)
  {
      if (type == "byte")
          return "getAsByte";
      else if (type == "char")
          return "getAsByte";
      else if (type == "wchar")
          return "getAsInt";
      else if (type == "int8")
          return "getAsByte";
      else if (type == "uint8")
          return "getAsByte";
      else if (type == "int16")
          return "getAsShort";
      else if (type == "uint16")
          return "getAsShort";
      else if (type == "int32")
          return "getAsInt";
      else if (type == "uint32")
          return "getAsInt";
      else if (type == "int64")
          return "getAsLong";
      else if (type == "uint64")
          return "getAsLong";
  
      yyerror("Unsupported enum base type - " + type);
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumRead(const std::string& type)
  {
      if (type == "byte")
          return "readByte";
      else if (type == "char")
          return "readInt8";
      else if (type == "wchar")
          return "readInt32";
      else if (type == "int8")
          return "readInt8";
      else if (type == "uint8")
          return "readInt8";
      else if (type == "int16")
          return "readInt16";
      else if (type == "uint16")
          return "readInt16";
      else if (type == "int32")
          return "readInt32";
      else if (type == "uint32")
          return "readInt32";
      else if (type == "int64")
          return "readInt64";
      else if (type == "uint64")
          return "readInt64";
  
      yyerror("Unsupported enum base type - " + type);
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumConstant(const std::string& type, const std::string& value)
  {
      std::string result = value;
  
      if (type.empty())
      {
          // Fill flags values
          std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);
  
          // Generate flags combination
          if (!flags.empty())
          {
              result = "";
              bool first = true;
              for (const auto& it : flags)
              {
                  result += (first ? "" : "|") + CppCommon::StringUtils::ToTrim(it) + ".getRaw()";
                  first = false;
              }
          }
      }
  
      return ConvertEnumConstantPrefix(type) + result + ConvertEnumConstantSuffix(type);
  }
  
  std::string GeneratorJava::ConvertEnumConstantPrefix(const std::string& type)
  {
      if (type == "byte")
          return "(byte)";
      else if (type == "char")
          return "(char)";
      else if (type == "wchar")
          return "(char)";
      else if (type == "int8")
          return "(byte)";
      else if (type == "uint8")
          return "(byte)";
      else if (type == "int16")
          return "(short)";
      else if (type == "uint16")
          return "(short)";
      else if (type == "int32")
          return "(int)";
      else if (type == "uint32")
          return "(int)";
      else if (type == "int64")
          return "(long)";
      else if (type == "uint64")
          return "(long)";
  
      return "";
  }
  
  std::string GeneratorJava::ConvertEnumConstantSuffix(const std::string& type)
  {
      if ((type == "int64") || (type == "uint64"))
          return "L";
  
      return "";
  }
  
  std::string GeneratorJava::ConvertPrimitiveTypeName(const std::string& type)
  {
      if (type == "bool")
          return "Boolean";
      else if (type == "byte")
          return "Byte";
      else if (type == "char")
          return "Character";
      else if (type == "wchar")
          return "Character";
      else if (type == "int8")
          return "Byte";
      else if (type == "uint8")
          return "Byte";
      else if (type == "int16")
          return "Short";
      else if (type == "uint16")
          return "Short";
      else if (type == "int32")
          return "Integer";
      else if (type == "uint32")
          return "Integer";
      else if (type == "int64")
          return "Long";
      else if (type == "uint64")
          return "Long";
      else if (type == "float")
          return "Float";
      else if (type == "double")
          return "Double";
  
      return "";
  }
  
  std::string GeneratorJava::ConvertTypeName(const std::string& type, bool optional)
  {
      if (optional)
      {
          if (type == "bool")
              return "Boolean";
          else if (type == "byte")
              return "Byte";
          else if (type == "char")
              return "Character";
          else if (type == "wchar")
              return "Character";
          else if (type == "int8")
              return "Byte";
          else if (type == "uint8")
              return "Byte";
          else if (type == "int16")
              return "Short";
          else if (type == "uint16")
              return "Short";
          else if (type == "int32")
              return "Integer";
          else if (type == "uint32")
              return "Integer";
          else if (type == "int64")
              return "Long";
          else if (type == "uint64")
              return "Long";
          else if (type == "float")
              return "Float";
          else if (type == "double")
              return "Double";
      }
  
      if (type == "bool")
          return "boolean";
      else if (type == "byte")
          return "byte";
      else if (type == "bytes")
          return "ByteBuffer";
      else if (type == "char")
          return "char";
      else if (type == "wchar")
          return "char";
      else if (type == "int8")
          return "byte";
      else if (type == "uint8")
          return "byte";
      else if (type == "int16")
          return "short";
      else if (type == "uint16")
          return "short";
      else if (type == "int32")
          return "int";
      else if (type == "uint32")
          return "int";
      else if (type == "int64")
          return "long";
      else if (type == "uint64")
          return "long";
      else if (type == "float")
          return "float";
      else if (type == "double")
          return "double";
      else if (type == "decimal")
          return "BigDecimal";
      else if (type == "string")
          return "String";
      else if (type == "timestamp")
          return "Instant";
      else if (type == "uuid")
          return "UUID";
  
      return type;
  }
  
  std::string GeneratorJava::ConvertTypeName(const StructField& field)
  {
      if (field.array)
          return ConvertTypeName(*field.type, field.optional) + "[]";
      else if (field.vector)
          return "ArrayList<" + ConvertTypeName(*field.type, true) + ">";
      else if (field.list)
          return "LinkedList<" + ConvertTypeName(*field.type, true) + ">";
      else if (field.set)
          return "HashSet<" + ConvertTypeName(*field.key, true) + ">";
      else if (field.map)
          return "TreeMap<" + ConvertTypeName(*field.key, true) + ", " + ConvertTypeName(*field.type, true) +">";
      else if (field.hash)
          return "HashMap<" + ConvertTypeName(*field.key, true) + ", " + ConvertTypeName(*field.type, true) +">";
  
      return ConvertTypeName(*field.type, field.optional);
  }
  
  std::string GeneratorJava::ConvertBaseFieldName(const std::string& type, bool final)
  {
      std::string modelType = (final ? "Final" : "Field");
  
      std::string ns = "";
      std::string t = type;
  
      size_t pos = type.find_last_of('.');
      if (pos != std::string::npos)
      {
          ns.assign(type, 0, pos + 1);
          ns.append("fbe.");
          t.assign(type, pos + 1, type.size() - pos);
      }
  
      return ns + modelType + "Model" + ConvertTypeFieldName(t);
  }
  
  std::string GeneratorJava::ConvertTypeFieldName(const std::string& type)
  {
      if (type == "bool")
          return "Boolean";
      else if (type == "byte")
          return "Byte";
      else if (type == "bytes")
          return "Bytes";
      else if (type == "char")
          return "Char";
      else if (type == "wchar")
          return "WChar";
      else if (type == "int8")
          return "Int8";
      else if (type == "uint8")
          return "Int8";
      else if (type == "int16")
          return "Int16";
      else if (type == "uint16")
          return "Int16";
      else if (type == "int32")
          return "Int32";
      else if (type == "uint32")
          return "Int32";
      else if (type == "int64")
          return "Int64";
      else if (type == "uint64")
          return "Int64";
      else if (type == "float")
          return "Float";
      else if (type == "double")
          return "Double";
      else if (type == "decimal")
          return "Decimal";
      else if (type == "string")
          return "String";
      else if (type == "timestamp")
          return "Timestamp";
      else if (type == "uuid")
          return "UUID";
  
      std::string result = type;
      CppCommon::StringUtils::ReplaceAll(result, ".", "");
      return result;
  }
  
  std::string GeneratorJava::ConvertTypeFieldType(const std::string& type, bool optional)
  {
      if (type == "bool")
          return "Boolean";
      else if (type == "byte")
          return "Byte";
      else if (type == "bytes")
          return "ByteBuffer";
      else if (type == "char")
          return "Character";
      else if (type == "wchar")
          return "Character";
      else if (type == "int8")
          return "Byte";
      else if (type == "uint8")
          return "Byte";
      else if (type == "int16")
          return "Short";
      else if (type == "uint16")
          return "Short";
      else if (type == "int32")
          return "Integer";
      else if (type == "uint32")
          return "Integer";
      else if (type == "int64")
          return "Long";
      else if (type == "uint64")
          return "Long";
      else if (type == "float")
          return "Float";
      else if (type == "double")
          return "Double";
      else if (type == "decimal")
          return "BigDecimal";
      else if (type == "string")
          return "String";
      else if (type == "timestamp")
          return "Instant";
      else if (type == "uuid")
          return "UUID";
  
      std::string ns = "";
      std::string t = type;
  
      size_t pos = type.find_last_of('.');
      if (pos != std::string::npos)
      {
          ns.assign(type, 0, pos + 1);
          t.assign(type, pos + 1, type.size() - pos);
      }
  
      return ns + t;
  }
  
  std::string GeneratorJava::ConvertTypeFieldDeclaration(const std::string& type, bool optional, bool final)
  {
      std::string modelType = (final ? "Final" : "Field");
  
      std::string ns = "";
      std::string opt = optional ? "Optional" : "";
      std::string t = type;
  
      size_t pos = type.find_last_of('.');
      if (pos != std::string::npos)
      {
          ns.assign(type, 0, pos + 1);
          ns.append("fbe.");
          t.assign(type, pos + 1, type.size() - pos);
      }
  
      return ns + modelType + "Model" + opt + ConvertTypeFieldName(t);
  }
  
  std::string GeneratorJava::ConvertTypeFieldDeclaration(const StructField& field, bool final)
  {
      std::string modelType = (final ? "Final" : "Field");
  
      if (field.array)
          return modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
      else if (field.vector || field.list || field.set)
          return modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
      else if (field.map || field.hash)
          return modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
      else if (field.optional)
          return modelType + "ModelOptional" + ConvertTypeFieldName(*field.type);
  
      return ConvertTypeFieldDeclaration(*field.type, field.optional, final);
  }
  
  std::string GeneratorJava::ConvertTypeFieldInitialization(const StructField& field, const std::string& offset, bool final)
  {
      std::string modelType = (final ? "Final" : "Field");
  
      if (field.array)
          return "new " + modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ", " + std::to_string(field.N) + ")";
      else if (field.vector || field.list || field.set)
          return "new " + modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
      else if (field.map || field.hash)
          return "new " + modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
      else if (field.optional)
          return "new " + modelType + "ModelOptional" + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
  
      std::string ns = "";
      std::string t = *field.type;
      std::string type = *field.type;
  
      size_t pos = type.find_last_of('.');
      if (pos != std::string::npos)
      {
          ns.assign(type, 0, pos + 1);
          ns.append("fbe.");
          t.assign(type, pos + 1, type.size() - pos);
      }
  
      return "new " + ns + modelType + "Model" + ConvertTypeFieldName(t) + "(buffer, " + offset + ")";
  }
  
  std::string GeneratorJava::ConvertConstant(const std::string& type, const std::string& value, bool optional)
  {
      if (value == "true")
          return "true";
      else if (value == "false")
          return "false";
      else if (value == "null")
          return "null";
      else if (value == "epoch")
          return "Instant.EPOCH";
      else if (value == "utc")
          return "Instant.now()";
      else if (value == "uuid0")
          return "fbe.UUIDGenerator.nil()";
      else if (value == "uuid1")
          return "fbe.UUIDGenerator.sequential()";
      else if (value == "uuid4")
          return "fbe.UUIDGenerator.random()";
  
      std::string result = value;
  
      if (!IsKnownType(type))
      {
          // Fill flags values
          std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);
  
          // Generate flags combination
          if (flags.size() > 1)
          {
              result = "";
              bool first = true;
              for (const auto& it : flags)
              {
                  result += (first ? "" : ", ") + CppCommon::StringUtils::ToTrim(it) + ".getEnum()";
                  first = false;
              }
              result = type + ".fromSet(EnumSet.of(" + result + "))";
          }
      }
  
      return ConvertConstantPrefix(type) + result + ConvertConstantSuffix(type);
  }
  
  std::string GeneratorJava::ConvertConstantPrefix(const std::string& type)
  {
      if (type == "boolean")
          return "(boolean)";
      else if (type == "byte")
          return "(byte)";
      else if (type == "char")
          return "(char)";
      else if (type == "wchar")
          return "(char)";
      else if (type == "int8")
          return "(byte)";
      else if (type == "uint8")
          return "(byte)";
      else if (type == "int16")
          return "(short)";
      else if (type == "uint16")
          return "(short)";
      else if (type == "int32")
          return "(int)";
      else if (type == "uint32")
          return "(int)";
      else if (type == "int64")
          return "(long)";
      else if (type == "uint64")
          return "(long)";
      else if (type == "float")
          return "(float)";
      else if (type == "double")
          return "(double)";
      else if (type == "decimal")
          return "BigDecimal.valueOf(";
      else if (type == "uuid")
          return "UUID.fromString(";
  
      return "";
  }
  
  std::string GeneratorJava::ConvertConstantSuffix(const std::string& type)
  {
      if ((type == "int64") || (type == "uint64"))
          return "L";
      else if (type == "float")
          return "f";
      else if (type == "double")
          return "d";
      else if (type == "decimal")
          return "d)";
      else if (type == "uuid")
          return ")";
  
      return "";
  }
  
  std::string GeneratorJava::ConvertDefault(const std::string& type)
  {
      if (type == "bool")
          return "false";
      else if (type == "byte")
          return "(byte)0";
      else if (type == "bytes")
          return "ByteBuffer.allocate(0)";
      else if ((type == "char") || (type == "wchar"))
          return "'\\0'";
      else if ((type == "int8") || (type == "uint8"))
          return "(byte)0";
      else if ((type == "int16") || (type == "uint16"))
          return "(short)0";
      else if ((type == "int32") || (type == "uint32"))
          return "0";
      else if ((type == "int64") || (type == "uint64"))
          return "0L";
      else if (type == "float")
          return "0.0f";
      else if (type == "double")
          return "0.0d";
      else if (type == "decimal")
          return "BigDecimal.valueOf(0L)";
      else if (type == "string")
          return "\"\"";
      else if (type == "timestamp")
          return "Instant.EPOCH";
      else if (type == "uuid")
          return "fbe.UUIDGenerator.nil()";
  
      return "new " + type + "()";
  }
  
  std::string GeneratorJava::ConvertDefault(const StructField& field)
  {
      if (field.value)
          return ConvertConstant(*field.type, *field.value, field.optional);
  
      if (field.array)
          return "new " + ConvertTypeName(*field.type, field.optional) + "[" + std::to_string(field.N) + "]";
      else if (field.vector || field.list || field.set || field.map || field.hash)
          return "new " + ConvertTypeName(field) + "()";
      else if (field.optional)
          return "null";
      else if (!IsKnownType(*field.type))
          return "new " + ConvertTypeName(field) + "()";
  
      return ConvertDefault(*field.type);
  }
  
  std::string GeneratorJava::ConvertOutputStreamType(const std::string& type, const std::string& name, bool optional)
  {
      if (type == "bool")
          return ".append(" + name + " ? \"true\" : \"false\")";
      else if (type == "bytes")
          return ".append(\"bytes[\").append(" + name + ".array().length).append(\"]\")";
      else if ((type == "char") || (type == "wchar"))
          return ".append(\"'\").append(" + name + ").append(\"'\")";
      else if ((type == "string") || (type == "uuid"))
          return ".append(\"\\\"\").append(" + name + ").append(\"\\\"\")";
      else if (type == "timestamp")
          return ".append(" + name + ".getEpochSecond() * 1000000000 + " + name + ".getNano())";
      else
          return ".append(" + name + ")";
  }
  
  std::string GeneratorJava::ConvertOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate)
  {
      std::string comma = separate ? ".append(first ? \"\" : \",\")" : "";
  
      if (optional || (type == "bytes") || (type == "decimal") || (type == "string") || (type == "timestamp") || (type == "uuid"))
          return "if (" + name + " != null) sb" + comma + ConvertOutputStreamType(type, name, true) + "; else sb" + comma + ".append(\"null\");";
      else
          return "sb" + comma + ConvertOutputStreamType(type, name, false) + ";";
  }
  
  } // namespace FBE
  