content: -- Copyright (c) Microsoft. All rights reserved.
  -- Licensed under the MIT license. See LICENSE file in the project root
  -- for full license information.
  
  {-# LANGUAGE QuasiQuotes, OverloadedStrings, RecordWildCards #-}
  
  module Language.Bond.Codegen.Java.Class_java
      ( class_java
      , JavaFieldMapping(..)
      ) where
  
  import Prelude
  import Data.Text.Lazy (Text)
  import Text.Shakespeare.Text
  import Language.Bond.Syntax.Types
  import Language.Bond.Syntax.Util
  import Language.Bond.Util
  import Language.Bond.Codegen.TypeMapping
  import Language.Bond.Codegen.Util
  import Language.Bond.Codegen.Java.Util
  
  
  -- field -> public field
  data JavaFieldMapping = JavaPublicFields deriving Eq
  
  
  -- given the type of the field, returns the name of the struct field descriptor type (a protected nested class within StructBondType)
  structFieldDescriptorTypeName :: MappingContext -> Type -> Text
  structFieldDescriptorTypeName java = typeName
      where
          typeName (BT_Maybe BT_Int8) = [lt|org.bondlib.StructBondType.SomethingInt8StructField|]
          typeName BT_Int8 = [lt|org.bondlib.StructBondType.Int8StructField|]
          typeName (BT_Maybe BT_Int16) = [lt|org.bondlib.StructBondType.SomethingInt16StructField|]
          typeName BT_Int16 = [lt|org.bondlib.StructBondType.Int16StructField|]
          typeName (BT_Maybe BT_Int32) = [lt|org.bondlib.StructBondType.SomethingInt32StructField|]
          typeName BT_Int32 = [lt|org.bondlib.StructBondType.Int32StructField|]
          typeName (BT_Maybe BT_Int64) = [lt|org.bondlib.StructBondType.SomethingInt64StructField|]
          typeName BT_Int64 = [lt|org.bondlib.StructBondType.Int64StructField|]
          typeName (BT_Maybe BT_UInt8) = [lt|org.bondlib.StructBondType.SomethingUInt8StructField|]
          typeName BT_UInt8 = [lt|org.bondlib.StructBondType.UInt8StructField|]
          typeName (BT_Maybe BT_UInt16) = [lt|org.bondlib.StructBondType.SomethingUInt16StructField|]
          typeName BT_UInt16 = [lt|org.bondlib.StructBondType.UInt16StructField|]
          typeName (BT_Maybe BT_UInt32) = [lt|org.bondlib.StructBondType.SomethingUInt32StructField|]
          typeName BT_UInt32 = [lt|org.bondlib.StructBondType.UInt32StructField|]
          typeName (BT_Maybe BT_UInt64) = [lt|org.bondlib.StructBondType.SomethingUInt64StructField|]
          typeName BT_UInt64 = [lt|org.bondlib.StructBondType.UInt64StructField|]
          typeName (BT_Maybe BT_Float) = [lt|org.bondlib.StructBondType.SomethingFloatStructField|]
          typeName BT_Float = [lt|org.bondlib.StructBondType.FloatStructField|]
          typeName (BT_Maybe BT_Double) = [lt|org.bondlib.StructBondType.SomethingDoubleStructField|]
          typeName BT_Double = [lt|org.bondlib.StructBondType.DoubleStructField|]
          typeName (BT_Maybe BT_Bool) = [lt|org.bondlib.StructBondType.SomethingBoolStructField|]
          typeName BT_Bool = [lt|org.bondlib.StructBondType.BoolStructField|]
          typeName (BT_Maybe BT_String) = [lt|org.bondlib.StructBondType.SomethingStringStructField|]
          typeName BT_String = [lt|org.bondlib.StructBondType.StringStructField|]
          typeName (BT_Maybe BT_WString) = [lt|org.bondlib.StructBondType.SomethingWStringStructField|]
          typeName BT_WString = [lt|org.bondlib.StructBondType.WStringStructField|]
          typeName (BT_Maybe BT_MetaName) = [lt|org.bondlib.StructBondType.SomethingStringStructField|]
          typeName BT_MetaName = [lt|org.bondlib.StructBondType.StringStructField|]
          typeName (BT_Maybe BT_MetaFullName) = [lt|org.bondlib.StructBondType.SomethingStringStructField|]
          typeName BT_MetaFullName = [lt|org.bondlib.StructBondType.StringStructField|]
          typeName (BT_Maybe (BT_UserDefined e@Enum {} _)) = [lt|org.bondlib.StructBondType.SomethingEnumStructField<#{qualifiedDeclaredTypeName java e}>|]
          typeName (BT_UserDefined e@Enum {} _) = [lt|org.bondlib.StructBondType.EnumStructField<#{qualifiedDeclaredTypeName java e}>|]
          typeName (BT_Maybe (BT_UserDefined a@Alias {} args)) = structFieldDescriptorTypeName java (BT_Maybe (resolveAlias a args))
          typeName (BT_UserDefined a@Alias {} args) = structFieldDescriptorTypeName java (resolveAlias a args)
          typeName (BT_Maybe t) = [lt|org.bondlib.StructBondType.SomethingObjectStructField<#{getElementTypeName java t}>|]
          typeName t = [lt|org.bondlib.StructBondType.ObjectStructField<#{getElementTypeName java t}>|]
  
  
  -- given the type of the field, value indicating whether a struct field descriptor type is generic and hence needs an explicit type parameter
  isGenericStructFieldDescriptor :: Type -> Bool
  isGenericStructFieldDescriptor (BT_Maybe t) = not (isPrimitiveNonEnumBondType t)
  isGenericStructFieldDescriptor t = not (isPrimitiveNonEnumBondType t)
  
  
  -- given a type parameter, returns the name of a local variable containing the type descriptor
  typeParamVarName :: TypeParam -> Text
  typeParamVarName TypeParam {..} = [lt|#{paramName}|]
  
  
  -- given a type parameter, returns the declaration of a local variable containing the type descriptor
  typeParamVarDecl :: TypeParam -> Text
  typeParamVarDecl t@TypeParam {..} = [lt|org.bondlib.BondType<#{paramName}> #{typeParamVarName t}|]
  
  
  -- given a list of type parameters, returns it as comma-separated text
  typeParamNameList :: [TypeParam] -> Text
  typeParamNameList declParams = [lt|#{sepBy ", " paramName declParams}|]
  
  
  -- given a list of type parameter, returns a comma-separated list of names of local variables containing the type descriptor
  typeParamVarNameList :: [TypeParam] -> Text
  typeParamVarNameList declParams = [lt|#{sepBy ", " typeParamVarName declParams}|]
  
  
  -- given a list of type parameter, returns a comma-separated list of declarations of local variables containing the type descriptor
  typeParamVarDeclList :: [TypeParam] -> Text
  typeParamVarDeclList declParams = [lt|#{sepBy ", " typeParamVarDecl declParams}|]
  
  
  -- given a list of type parameters, returns it as comma-separated text with angles (unless the list is empty)
  typeParamAnglesNameList :: [TypeParam] -> Text
  typeParamAnglesNameList declParams = [lt|#{angles $ typeParamNameList declParams}|]
  
  
  -- given a class name and a list of type parameters, returns the full type name with type parameters (if any)
  typeNameWithParams :: String -> [TypeParam] -> Text
  typeNameWithParams declName declParams = [lt|#{declName}#{typeParamAnglesNameList declParams}|]
  
  
  -- given a class name and a list of type parameters, returns the full type descriptor name with type parameters (if any)
  typeDescriptorNameWithParams :: String -> [TypeParam] -> Text
  typeDescriptorNameWithParams declName declParams = [lt|org.bondlib.StructBondType<#{typeNameWithParams declName declParams}>|]
  
  
  -- given a class name, returns the full type descriptor name (using non-generic notation for the struct type)
  typeDescriptorName :: String -> Text
  typeDescriptorName declName = [lt|org.bondlib.StructBondType<#{declName}>|]
  
  
  -- given a variable name, returns call to ArgumentHelper.ensureNotNull method that checks the variable for null
  ensureNotNullArgument :: Text -> Text
  ensureNotNullArgument argName = [lt|org.bondlib.ArgumentHelper.ensureNotNull(#{argName}, "#{argName}")|]
  
  
  -- given a field type and optional default value, returns an expression for the parameter containing the default value,
  -- along with the leading comma; this value is used in initialization of struct field descriptors where
  -- the constructors are overloaded to take explicit default value or take none (i.e. use the implicit default)
  fieldDefaultValueInitParamExpr :: MappingContext -> Type -> Maybe Default -> Text
  fieldDefaultValueInitParamExpr _ _ (Just (DefaultBool val)) = if val
      then [lt|, true|]
      else [lt|, false|]
  fieldDefaultValueInitParamExpr _ BT_Int8 (Just (DefaultInteger val)) = [lt|, (byte)#{val}|]
  fieldDefaultValueInitParamExpr _ BT_Int16 (Just (DefaultInteger val)) = [lt|, (short)#{val}|]
  fieldDefaultValueInitParamExpr _ BT_Int32 (Just (DefaultInteger val)) = [lt|, #{val}|]
  fieldDefaultValueInitParamExpr _ BT_Int64 (Just (DefaultInteger val)) = [lt|, #{val}L|]
  fieldDefaultValueInitParamExpr _ BT_UInt8 (Just (DefaultInteger val)) = [lt|, (byte)#{twosComplement 8 val}|]
  fieldDefaultValueInitParamExpr _ BT_UInt16 (Just (DefaultInteger val)) = [lt|, (short)#{twosComplement 16 val}|]
  fieldDefaultValueInitParamExpr _ BT_UInt32 (Just (DefaultInteger val)) = [lt|, #{twosComplement 32 val}|]
  fieldDefaultValueInitParamExpr _ BT_UInt64 (Just (DefaultInteger val)) = [lt|, #{twosComplement 64 val}L|]
  fieldDefaultValueInitParamExpr _ BT_Float (Just (DefaultFloat val)) = [lt|, #{val}F|]
  fieldDefaultValueInitParamExpr _ BT_Float (Just (DefaultInteger val)) = [lt|, #{val}F|]
  fieldDefaultValueInitParamExpr _ BT_Double (Just (DefaultFloat val)) = [lt|, #{val}D|]
  fieldDefaultValueInitParamExpr _ BT_Double (Just (DefaultInteger val)) = [lt|, #{val}D|]
  fieldDefaultValueInitParamExpr _ BT_String (Just (DefaultString val)) = [lt|, "#{val}"|]
  fieldDefaultValueInitParamExpr _ BT_WString (Just (DefaultString val)) = [lt|, "#{val}"|]
  fieldDefaultValueInitParamExpr java (BT_UserDefined e@Enum {..} _) (Just (DefaultEnum val)) = [lt|, #{qualifiedDeclaredTypeName java e}.#{val}|]
  fieldDefaultValueInitParamExpr _ _ _ = mempty
  
  
  -- given a struct name and type parameters, and type, returns a type descriptor expression for the field, used in initialization of struct field descriptors
  structFieldDescriptorInitStructExpr :: MappingContext -> Type -> String -> [Type] -> Text
  structFieldDescriptorInitStructExpr java fieldType typeName params = [lt|#{typeCastExpr} getStructType(#{typeName}.class#{paramExprList params})|]
      where
          typeCastExpr = [lt|(org.bondlib.StructBondType<#{(getTypeName java) fieldType}>)|]
          paramExprList :: [Type] -> Text
          paramExprList [] = mempty
          paramExprList (x:xs) = [lt|, #{structFieldDescriptorInitTypeExpr java x}#{paramExprList xs}|]
  
  
  -- given field type, returns a type descriptor expression for the field, used in initialization of struct field descriptors
  structFieldDescriptorInitTypeExpr :: MappingContext -> Type -> Text
  structFieldDescriptorInitTypeExpr java (BT_Maybe t) = structFieldDescriptorInitTypeExpr java t
  structFieldDescriptorInitTypeExpr _ BT_Int8 = [lt|org.bondlib.BondTypes.INT8|]
  structFieldDescriptorInitTypeExpr _ BT_Int16 = [lt|org.bondlib.BondTypes.INT16|]
  structFieldDescriptorInitTypeExpr _ BT_Int32 = [lt|org.bondlib.BondTypes.INT32|]
  structFieldDescriptorInitTypeExpr _ BT_Int64 = [lt|org.bondlib.BondTypes.INT64|]
  structFieldDescriptorInitTypeExpr _ BT_UInt8 = [lt|org.bondlib.BondTypes.UINT8|]
  structFieldDescriptorInitTypeExpr _ BT_UInt16 = [lt|org.bondlib.BondTypes.UINT16|]
  structFieldDescriptorInitTypeExpr _ BT_UInt32 = [lt|org.bondlib.BondTypes.UINT32|]
  structFieldDescriptorInitTypeExpr _ BT_UInt64 = [lt|org.bondlib.BondTypes.UINT64|]
  structFieldDescriptorInitTypeExpr _ BT_Float = [lt|org.bondlib.BondTypes.FLOAT|]
  structFieldDescriptorInitTypeExpr _ BT_Double = [lt|org.bondlib.BondTypes.DOUBLE|]
  structFieldDescriptorInitTypeExpr _ BT_Bool = [lt|org.bondlib.BondTypes.BOOL|]
  structFieldDescriptorInitTypeExpr _ BT_String = [lt|org.bondlib.BondTypes.STRING|]
  structFieldDescriptorInitTypeExpr _ BT_WString = [lt|org.bondlib.BondTypes.WSTRING|]
  structFieldDescriptorInitTypeExpr _ BT_Blob = [lt|org.bondlib.BondTypes.BLOB|]
  structFieldDescriptorInitTypeExpr java (BT_Bonded t) = [lt|bondedOf(#{structFieldDescriptorInitTypeExpr java t})|]
  structFieldDescriptorInitTypeExpr java (BT_Nullable t) = [lt|nullableOf(#{structFieldDescriptorInitTypeExpr java t})|]
  structFieldDescriptorInitTypeExpr java (BT_Vector t) = [lt|vectorOf(#{structFieldDescriptorInitTypeExpr java t})|]
  structFieldDescriptorInitTypeExpr java (BT_List t) = [lt|listOf(#{structFieldDescriptorInitTypeExpr java t})|]
  structFieldDescriptorInitTypeExpr java (BT_Set t) = [lt|setOf(#{structFieldDescriptorInitTypeExpr java t})|]
  structFieldDescriptorInitTypeExpr java (BT_Map k v) = [lt|mapOf(#{structFieldDescriptorInitTypeExpr java k}, #{structFieldDescriptorInitTypeExpr java v})|]
  structFieldDescriptorInitTypeExpr _ (BT_TypeParam param) = [lt|#{paramName param}|]
  structFieldDescriptorInitTypeExpr java (BT_UserDefined e@Enum {} _) = [lt|#{qualifiedDeclaredTypeName java e}.BOND_TYPE|]
  structFieldDescriptorInitTypeExpr java t@(BT_UserDefined s@Struct {} params) = [lt|#{structFieldDescriptorInitStructExpr java t (qualifiedDeclaredTypeName java s) params}|]
  structFieldDescriptorInitTypeExpr java t@(BT_UserDefined s@Forward {} params) = [lt|#{structFieldDescriptorInitStructExpr java t (qualifiedDeclaredTypeName java s) params}|]
  structFieldDescriptorInitTypeExpr java (BT_UserDefined a@Alias {} params) = structFieldDescriptorInitTypeExpr java (resolveAlias a params)
  structFieldDescriptorInitTypeExpr _ t = error $ "invalid declaration type for structFieldDescriptorInitTypeExpr: " ++ show t
  
  
  -- given struct base type, returns a type descriptor expression
  structBaseDescriptorInitStructExpr :: MappingContext -> Maybe Type -> Text
  structBaseDescriptorInitStructExpr java t = maybe [lt|null|] (structFieldDescriptorInitTypeExpr java) t
  
  
  -- given struct class name and generic type parameters, builds text for GenericBondTypeBuilder abstract class
  -- that defines the public API to specialize a generic struct to specific generic type arguments
  -- (this class is generated only when the enclosing Bond struct class is generic)
  makeStructMember_GenericBondTypeBuilder :: String -> [TypeParam] -> Text
  makeStructMember_GenericBondTypeBuilder declName declParams = [lt|
      public static abstract class GenericBondTypeBuilder {
  
          private GenericBondTypeBuilder() {
          }
  
          public abstract #{typeParamAnglesNameList declParams} #{typeDescriptorNameWithParams declName declParams} makeGenericType(#{typeParamVarDeclList declParams});
      }
  |]
  
  
  -- given struct class name and generic type parameters, builds text for implementation
  -- of the StructBondTypeBuilderImpl.makeGenericType method
  -- (this method is generated only when the enclosing Bond struct class is generic)
  makeStructBuilderMember_makeGenericType :: String -> [TypeParam] -> Text
  makeStructBuilderMember_makeGenericType declName declParams = [lt|
              private #{typeParamAnglesNameList declParams} #{typeDescriptorNameWithParams declName declParams} makeGenericType(#{typeParamVarDeclList declParams}) {
                  #{newlineSepEnd 4 checkArg declParams}return #{castExpr} this.getInitializedFromCache(#{typeParamVarNameList declParams});
              }
  |]
      where
          checkArg t@TypeParam {..} = [lt|#{ensureNotNullArgument (typeParamVarName t)};|]
          castExpr = [lt|(StructBondTypeImpl)|]
  
  
  -- given struct class name and generic type parameters, builds text for implementation of
  -- the StructBondTypeBuilderImpl class which is responsible for building the type descriptor
  makeStructBondTypeMember_StructBondTypeBuilderImpl :: String -> [TypeParam] -> Text
  makeStructBondTypeMember_StructBondTypeBuilderImpl declName declParams = [lt|
          static final class StructBondTypeBuilderImpl extends org.bondlib.StructBondType.StructBondTypeBuilder<#{declName}> {
              #{ifThenElse (null declParams) mempty (makeStructBuilderMember_makeGenericType declName declParams)}
              @Override
              public final int getGenericTypeParameterCount() {
                  return #{length declParams};
              }
  
              @Override
              protected final #{typeDescriptorName declName} buildNewInstance(org.bondlib.BondType[] genericTypeArguments) {
                  return new StructBondTypeImpl(#{ifThenElse (null declParams) nullText "new org.bondlib.GenericTypeSpecialization(genericTypeArguments)"});
              }
  
              static void register() {
                  registerStructType(#{declName}.class, new StructBondTypeBuilderImpl());
              }
          }|]
      where
          nullText = "null" :: Text
  
  
  -- given generic type parameters, struct fields, and base type, builds text for implementation of
  -- the StructBondTypeImpl.initialize method
  makeStructBondTypeMember_initialize :: MappingContext -> [TypeParam] -> [Field] -> Maybe Type -> Text
  makeStructBondTypeMember_initialize java declParams structFields structBase = [lt|
          @Override
          protected final void initialize() {#{typeArgVarDeclList}#{fieldDescriptorInitList}
              super.initializeBaseAndFields(#{baseTypeDescriptorParam}#{fieldTypeDescriptorParamsSeparator}#{fieldTypeDescriptorParams});
          }|]
      where
          typeArgVarDeclList = newlineBeginSep 3 typeArgVarDecl indexedDeclParams
          typeArgVarDecl (index, typeParam) = [lt|#{typeParamVarDecl typeParam} = this.getGenericSpecialization().getGenericTypeArgument(#{index});|]
          indexedDeclParams = zip [0 :: Int ..] declParams
  
          fieldDescriptorInitList = newlineBeginSep 3 fieldDescriptorInit structFields
          fieldDescriptorInit Field {..} = [lt|this.#{fieldName} = new #{structFieldDescriptorTypeName java fieldType}(#{constructorParams});|]
            where
              constructorParams = [lt|this#{fieldTypeParam}, #{fieldOrdinal}, "#{fieldName}", #{modifierConstantName fieldModifier}#{fieldDefaultValueParam}|]
              fieldTypeParam = if isGenericStructFieldDescriptor fieldType
                  then [lt|, #{structFieldDescriptorInitTypeExpr java fieldType}|]
                  else mempty
              fieldDefaultValueParam = fieldDefaultValueInitParamExpr java fieldType fieldDefault
  
          baseTypeDescriptorParam = structBaseDescriptorInitStructExpr java structBase
          fieldTypeDescriptorParamsSeparator = ifThenElse (null structFields) mempty [lt|, |]
          fieldTypeDescriptorParams = sepBy ", " structFieldReference structFields
          structFieldReference Field {..} = [lt|this.#{fieldName}|]
  
  
  -- given struct class name, generic type parameters, and struct fields, builds text for implementation of
  -- the StructBondTypeImpl.serializeStructFields method
  makeStructBondTypeMember_serializeStructFields :: String -> [TypeParam] -> [Field] -> Text
  makeStructBondTypeMember_serializeStructFields declName declParams structFields = [lt|
          @Override
          protected final void serializeStructFields(#{methodParamDecl}) throws java.io.IOException {#{newlineBeginSep 3 serializeField structFields}
          }|]
              where
                  methodParamDecl = [lt|org.bondlib.BondType.SerializationContext context, #{typeNameWithParams declName declParams} value|]
                  serializeField Field {..} = [lt|this.#{fieldName}.serialize(context, value.#{fieldName});|]
  
  
  -- given struct class name, generic type parameters, and struct fields, builds text for implementation of
  -- the StructBondTypeImpl.deserializeStructFields method for TaggedProtocolReaders
  makeStructBondTypeMember_deserializeStructFields_tagged :: String -> [TypeParam] -> [Field] -> Text
  makeStructBondTypeMember_deserializeStructFields_tagged declName declParams structFields = [lt|
          @Override
          protected final void deserializeStructFields(#{methodParamDecl}) throws java.io.IOException {#{newlineBeginSep 3 declareLocalVariable structFields}
              while (this.readField(context)) {
                  switch (context.readFieldResult.id) {#{newlineBeginSep 5 deserializeField structFields}
                      default:
                          context.reader.skip(context.readFieldResult.type);
                          break;
                  }
              }#{newlineBeginSep 3 verifyField structFields}
          }|]
              where
                  methodParamDecl = [lt|org.bondlib.BondType.TaggedDeserializationContext context, #{typeNameWithParams declName declParams} value|]
                  declareLocalVariable Field {..} = [lt|boolean __has_#{fieldName} = false;|]
                  deserializeField Field {..} = [lt|#{switchCasePart}#{newLine 6}#{deserializePart}#{newLine 6}#{setBooleanPart}#{newLine 6}break;|]
                    where
                      switchCasePart = [lt|case #{fieldOrdinal}:|]
                      deserializePart = [lt|value.#{fieldName} = this.#{fieldName}.deserialize(context, __has_#{fieldName});|]
                      setBooleanPart = [lt|__has_#{fieldName} = true;|]
  
                  verifyField Field {..} = [lt|this.#{fieldName}.verifyDeserialized(__has_#{fieldName});|]
  
  
  -- given struct class name, generic type parameters, and struct fields, builds text for implementation of
  -- the StructBondTypeImpl.deserializeStructFields method for UntaggedProtocolReaders
  makeStructBondTypeMember_deserializeStructFields_untagged :: String -> [TypeParam] -> [Field] -> Text
  makeStructBondTypeMember_deserializeStructFields_untagged declName declParams structFields = [lt|
          @Override
          protected final void deserializeStructFields(#{methodParamDecl}) throws java.io.IOException {#{newlineBeginSep 3 declareLocalVariable structFields}
              for (final org.bondlib.FieldDef field : structDef.fields) {
                  switch (field.id) {#{newlineBeginSep 5 deserializeField structFields}
                      default:
                          context.reader.skip(context.schema, field.type);
                          break;
                  }
              }#{newlineBeginSep 3 verifyField structFields}
          }|]
              where
                  methodParamDecl = [lt|org.bondlib.BondType.UntaggedDeserializationContext context, org.bondlib.StructDef structDef, #{typeNameWithParams declName declParams} value|]
                  declareLocalVariable Field {..} = [lt|boolean __has_#{fieldName} = false;|]
                  deserializeField Field {..} = [lt|#{switchCasePart}#{newLine 6}#{deserializePart}#{newLine 6}#{setBooleanPart}#{newLine 6}break;|]
                    where
                      switchCasePart = [lt|case #{fieldOrdinal}:|]
                      deserializePart = [lt|value.#{fieldName} = this.#{fieldName}.deserialize(context, field.type);|]
                      setBooleanPart = [lt|__has_#{fieldName} = true;|]
  
                  verifyField Field {..} = [lt|this.#{fieldName}.verifyDeserialized(__has_#{fieldName});|]
  
  
  -- given class name, generic type parameters, and struct fields, builds text for implementation of
  -- the StructBondTypeImpl.initializeStructFields method
  makeStructBondTypeMember_initializeStructFields :: String -> [TypeParam] -> [Field] -> Text
  makeStructBondTypeMember_initializeStructFields declName declParams structFields = [lt|
          @Override
          protected final void initializeStructFields(#{methodParamDecl}) {#{newlineBeginSep 3 initializeField structFields}
          }|]
              where
                  methodParamDecl = [lt|#{typeNameWithParams declName declParams} value|]
                  initializeField Field {..} = [lt|value.#{fieldName} = this.#{fieldName}.initialize();|]
  
  
  -- given class name, generic type parameters, and struct fields, builds text for implementation of
  -- the StructBondTypeImpl.copyStructFields method
  makeStructBondTypeMember_cloneStructFields :: String -> [TypeParam] -> [Field] -> Text
  makeStructBondTypeMember_cloneStructFields declName declParams structFields = [lt|
          @Override
          protected final void cloneStructFields(#{methodParamDecl}) {#{newlineBeginSep 3 cloneField structFields}
          }|]
              where
                  methodParamDecl = [lt|#{typeNameWithParams declName declParams} fromValue, #{typeNameWithParams declName declParams} toValue|]
                  cloneField Field {..} = [lt|toValue.#{fieldName} = this.#{fieldName}.clone(fromValue.#{fieldName});|]
  
  
  -- builds text for anonymous implementation of the GenericBondTypeBuilder abstract class and assignment to the BOND_TYPE variable
  bondTypeStaticVariableDeclAsGenericBondTypeBuilder :: String -> [TypeParam] -> Text
  bondTypeStaticVariableDeclAsGenericBondTypeBuilder declName declParams = [lt|public static final GenericBondTypeBuilder BOND_TYPE = new GenericBondTypeBuilder() {
  
          final StructBondTypeImpl.StructBondTypeBuilderImpl builder = new StructBondTypeImpl.StructBondTypeBuilderImpl();
  
          @Override
          public final <#{paramList}> org.bondlib.StructBondType<#{declName}<#{paramList}>> makeGenericType(#{sepBy ", " methodArg declParams}) {
              return this.builder.makeGenericType(#{paramList});
          }
      };|]
      where
          paramList = sepBy ", " paramName declParams
          methodArg TypeParam {..} = [lt|org.bondlib.BondType<#{paramName}> #{paramName}|]
  
  
  -- builds text for public constructor of non-generic Bond struct class
  publicConstructorDeclForNonGenericStruct :: MappingContext -> String -> Maybe Type -> Text
  publicConstructorDeclForNonGenericStruct java declName maybeBase = [lt|
      public #{declName}() {
          super(#{superConstructorArgs maybeBase});
          ((StructBondTypeImpl)BOND_TYPE).initializeStructFields(this);
      };
  |]
      where
          superConstructorArgs Nothing = mempty
          superConstructorArgs (Just t) = if isGenericBondStructType t
              then [lt|(org.bondlib.StructBondType<#{getTypeName java t}>)BOND_TYPE.getBaseStructType()|]
              else mempty
  
  
  -- builds text for public constructor of generic Bond struct class
  publicConstructorDeclForGenericStruct :: MappingContext -> String -> [TypeParam] -> Maybe Type -> Text
  publicConstructorDeclForGenericStruct java declName declParams maybeBase = [lt|
  public #{declName}(org.bondlib.StructBondType<#{declName}<#{paramList}>> genericType) {
          super(#{superConstructorArgs maybeBase});
          this.__genericType = (StructBondTypeImpl<#{paramList}>)genericType;
          this.__genericType.initializeStructFields(this);
      };
  |]
      where
          paramList = sepBy ", " paramName declParams
          superConstructorArgs Nothing = mempty
          superConstructorArgs (Just t) = if isGenericBondStructType t
              then [lt|(org.bondlib.StructBondType<#{getTypeName java t}>)org.bondlib.ArgumentHelper.ensureNotNull(genericType, "genericType").getBaseStructType()|]
              else mempty
  
  
  -- builds text for Object.equals(Object) override
  object_equals :: String -> [Field] -> Maybe Type -> Text
  object_equals declName fields structBase = [lt|@Override
      public boolean equals(Object o) {
          if (!(o instanceof #{declName})) return false;
          #{compareBase structBase}
          final #{declName} other = (#{declName}) o;
          #{newlineSep 2 compareField fields}
          return true;
      }
  |]
      where
          compareBase (Just _)     = [lt|if (!(super.equals(o))) return false;|]
          compareBase Nothing      = mempty
          compareField Field {..}  = [lt|if (!(#{equalsMember fieldType fieldName})) return false;|]
          equalsMember BT_Float f  = [lt|org.bondlib.FloatingPointHelper.floatEquals(this.#{f}, other.#{f})|]
          equalsMember BT_Double f = [lt|org.bondlib.FloatingPointHelper.doubleEquals(this.#{f}, other.#{f})|]
          equalsMember BT_Bool f    = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_Int8 f    = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_Int16 f   = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_Int32 f   = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_Int64 f   = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_UInt8 f   = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_UInt16 f  = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_UInt32 f  = [lt|this.#{f} == other.#{f}|]
          equalsMember BT_UInt64 f  = [lt|this.#{f} == other.#{f}|]
          equalsMember (BT_UserDefined a@Alias {} args) f = equalsMember (resolveAlias a args) f
          equalsMember _ f          = [lt|(this.#{f} == null && other.#{f} == null) || (this.#{f} != null && this.#{f}.equals(other.#{f}))|]
  
  
  -- builds text for Object.hashCode() override
  object_hashCode :: [Field] -> Maybe Type -> Text
  object_hashCode fields structBase = [lt|@Override
      public int hashCode() {
          int result = 17;
          #{newlineSep 2 hash hashInputs}
          return result;
      }
  |]
      where
          hash codeExpr = [lt|result += #{codeExpr};
          result *= 0xeadbeef;
          result ^= result >> 16;|]
  
          hashInputs = (hashBase structBase) ++ hashFields
  
          hashBase (Just _) = [[lt|super.hashCode()|]]
          hashBase Nothing  = []
  
          hashFields = map (\Field {..} -> hashCode fieldType fieldName) fields
          hashCode BT_Bool f    = [lt|(#{f} ? 0 : 1)|]
          hashCode BT_Int64 f   = [lt|#{f} ^ (#{f} >>> 32)|]
          hashCode BT_UInt64 f  = [lt|#{f} ^ (#{f} >>> 32)|]
          hashCode BT_Float f   = [lt|org.bondlib.FloatingPointHelper.floatHashCode(#{f})|]
          hashCode BT_Double f  = [lt|org.bondlib.FloatingPointHelper.doubleHashCode(#{f})|]
          hashCode BT_Int8 f    = [lt|#{f}|]
          hashCode BT_Int16 f   = [lt|#{f}|]
          hashCode BT_Int32 f   = [lt|#{f}|]
          hashCode BT_UInt8 f   = [lt|#{f}|]
          hashCode BT_UInt16 f  = [lt|#{f}|]
          hashCode BT_UInt32 f  = [lt|#{f}|]
          hashCode (BT_UserDefined a@Alias {} args) f = hashCode (resolveAlias a args) f
          hashCode _ f          = [lt|#{f} == null ? 0 : #{f}.hashCode()|]
  
  
  -- We implement Externalizable, rather than Serializable, so that
  -- ser/deserialization will result in a single call on the most derived class,
  -- rather than one call for each type in the inheritance chain. By reading into
  -- a byte[] instead of trying to deserialize from the ObjectInput{,Stream} we're
  -- passed, we can start from a ByteArrayInputStream, which we already know how
  -- to clone for Bonded fields.
  --
  -- We write the length of the serialized data because we can't assume the
  -- ObjectInput{,Stream} actually ends after the object we care about.
  --
  -- serialVersionUID is always 0 so that Java will always delegate compatibility
  -- checking of serialized data against current deserialization code to us.
  javaNativeSerializationGlue :: String -> Text
  javaNativeSerializationGlue declName = [lt|
      // Java native serialization
      private static final long serialVersionUID = 0L;
      private #{declName} __deserializedInstance;
  
      @Override
      public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
          final java.io.ByteArrayOutputStream outStream = new java.io.ByteArrayOutputStream();
          final org.bondlib.ProtocolWriter writer = new org.bondlib.CompactBinaryWriter(outStream, 1);
          org.bondlib.Marshal.marshal(this, writer);
  
          final byte[] marshalled = outStream.toByteArray();
          out.write(0);   // This type is not generic and has zero type parameters.
          out.writeInt(marshalled.length);
          out.write(marshalled);
      }
  
      @Override
      public void readExternal(java.io.ObjectInput in) throws java.io.IOException, java.lang.ClassNotFoundException {
          if (in.read() != 0) throw new java.io.IOException("type is not generic, but serialized data has type parameters.");
          final int marshalledLength = in.readInt();
          final byte[] marshalled = new byte[marshalledLength];
          in.readFully(marshalled);
  
          final java.io.ByteArrayInputStream inStream = new java.io.ByteArrayInputStream(marshalled);
          this.__deserializedInstance = org.bondlib.Unmarshal.unmarshal(inStream, getBondType()).deserialize();
      }
  
      private Object readResolve() throws java.io.ObjectStreamException {
          return this.__deserializedInstance;
      }
      // end Java native serialization
      |]
  
  
  javaNativeSerializationUnimpl :: Text
  javaNativeSerializationUnimpl = [lt|
      // Java native serialization
      @Override
      public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
          throw new java.lang.IllegalArgumentException("java.io.Serializable support is not implemented for generic types");
      }
  
      @Override
      public void readExternal(java.io.ObjectInput in) throws java.io.IOException, java.lang.ClassNotFoundException {
          // This may actually fail before reaching this line with an InvalidClassException because
          // generic types don't have the nullary constructor required by the Java serialization
          // framework.
          throw new java.lang.IllegalArgumentException("java.io.Serializable support is not implemented for generic types");
      }
      // end Java native serialization
      |]
  
  -- Template for struct -> Java class.
  class_java :: MappingContext -> [Import] -> Declaration -> Text
  class_java java _ declaration = [lt|
  package #{javaPackage};
  #{typeDefinition declaration}
  |]
      where
          javaType = getTypeName java
          javaPackage = sepBy "." toText $ getNamespace java
  
          -- struct -> Java class
          typeDefinition s@Struct {..} = [lt|
  #{generatedClassAnnotations}
  public class #{typeNameWithParams declName declParams}#{maybe interface baseClass structBase} {
      #{ifThenElse (null declParams) mempty (makeStructMember_GenericBondTypeBuilder declName declParams)}
      private static final class StructBondTypeImpl#{typeParamAnglesNameList declParams} extends #{typeDescriptorNameWithParams declName declParams} {
          #{makeStructBondTypeMember_StructBondTypeBuilderImpl declName declParams}
  
          #{doubleLineSep 2 fieldDescriptorFieldDecl structFields}
  
          private StructBondTypeImpl(org.bondlib.GenericTypeSpecialization genericTypeSpecialization) {
              super(genericTypeSpecialization);
          }
          #{makeStructBondTypeMember_initialize java declParams structFields structBase}
  
          @Override
          public final java.lang.String getName() {
              return "#{declName}";
          }
  
          @Override
          public final java.lang.String getQualifiedName() {
              return "#{qualifiedDeclaredTypeName idl s}";
          }
  
          @Override
          public final java.lang.Class<#{typeNameWithParams declName declParams}> getValueClass() {
              return (java.lang.Class<#{typeNameWithParams declName declParams}>) (java.lang.Class) #{declName}.class;
          }
  
          @Override
          public final #{typeNameWithParams declName declParams} newInstance() {
              return new #{typeNameWithParams declName declParams}(#{ifThenElse (null declParams) mempty thisText});
          }
          #{makeStructBondTypeMember_serializeStructFields declName declParams structFields}
          #{makeStructBondTypeMember_deserializeStructFields_tagged declName declParams structFields}
          #{makeStructBondTypeMember_deserializeStructFields_untagged declName declParams structFields}
          #{makeStructBondTypeMember_initializeStructFields declName declParams structFields}
          #{makeStructBondTypeMember_cloneStructFields declName declParams structFields}
      }
  
      #{bondTypeStaticVariableDecl}
  
      public static void initializeBondType() {
          StructBondTypeImpl.StructBondTypeBuilderImpl.register();
      }
  
      static {
          initializeBondType();
      }
      #{bondTypeDescriptorInstanceVariableDecl}
  
      #{ifThenElse (null declParams) (javaNativeSerializationGlue declName) javaNativeSerializationUnimpl}
  
      #{doubleLineSep 1 publicFieldDecl structFields}
      #{publicConstructorDecl}
      #{object_equals declName structFields structBase}
      #{object_hashCode structFields structBase}
      @Override
      public org.bondlib.StructBondType<? extends #{typeNameWithParams declName declParams}> getBondType() {
          return #{getBondTypeReturnValue};
      }
  }|]
              where
                  idl = MappingContext idlTypeMapping [] [] []
                  interface = [lt| implements org.bondlib.BondSerializable|]
                  baseClass x = [lt| extends #{javaType x}|]
                  publicFieldDecl Field {..} = [lt|public #{javaType fieldType} #{fieldName};|]
                  fieldDescriptorFieldDecl Field {..} = [lt|private #{structFieldDescriptorTypeName java fieldType} #{fieldName};|]
                  bondTypeStaticVariableDecl = if null declParams
                      then [lt|public static final org.bondlib.StructBondType<#{declName}> BOND_TYPE = new StructBondTypeImpl.StructBondTypeBuilderImpl().getInitializedFromCache();|]
                      else bondTypeStaticVariableDeclAsGenericBondTypeBuilder declName declParams
                  bondTypeDescriptorInstanceVariableDecl = if null declParams
                      then mempty
                      else [lt|private final StructBondTypeImpl#{typeParamAnglesNameList declParams} __genericType;|]
                  getBondTypeReturnValue = if null declParams
                      then [lt|BOND_TYPE|]
                      else [lt|this.__genericType|]
                  publicConstructorDecl = if null declParams
                      then publicConstructorDeclForNonGenericStruct java declName structBase
                      else publicConstructorDeclForGenericStruct java declName declParams structBase
  
                  thisText = "this" :: Text
  
          typeDefinition _ = mempty
  