packages feed

bond-0.13.0.0: src/Language/Bond/Codegen/Java/Class_java.hs

-- 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