Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Morethan254fields #189

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package avrohugger
package format
package specific
package methods

import converters.JavaConverter
import avrohugger.matchers.TypeMatcher
import treehugger.forest._
import definitions._
import treehugger.forest
import treehuggerDSL._

/**
* This generator is an alternative to GetGenerator. with these differences
* It is used for schemas with more than 254 fields which cannot have a constructor and have class members instead.
* A further issue with large numbers of fields is that the case class produced by GetGenerator.toDef
* can exceed the JVM limit of 65536 bytes resulting in a "Method too large" compilation error.
* This class generates a smaller get method by delegating to private methods
* Thus generator differs from GetGenerator in the following respects
* 1. It generates a list of function definitions
* 2. It generates a private method for each field that converts to an AnyRef
* 3. The match expression public def get(field$: Int): AnyRef delegates to the appropriate private method
*/
object GetsGenerator {
def toDefs(
indexedFields: List[IndexedField],
classSymbol: ClassSymbol,
typeMatcher: TypeMatcher,
targetScalaPartialVersion: String) :List[forest.DefDef]= {

def fieldGetMethodName(field: IndexedField) = s"get${field.avroField.name}AnyRef"
def asGetMethod(
field: IndexedField,
classSymbol: ClassSymbol,
typeMatcher: TypeMatcher,
targetScalaPartialVersion: String) = {

DEFINFER(fieldGetMethodName(field)).withType(AnyRefClass).withFlags(Flags.PRIVATE):= {
BLOCK(JavaConverter.convertToJava(
field.avroField.schema,
REF("getSchema").DOT("getFields").APPLY().DOT("get").APPLY(LIT(field.idx)).DOT("schema").APPLY(),
false,
REF(FieldRenamer.rename(field.avroField.name)),
classSymbol,
typeMatcher,
targetScalaPartialVersion)).AS(AnyRefClass)
}
}

def asGetCase(field: IndexedField) = {
CASE (LIT(field.idx)) ==> REF(fieldGetMethodName(field))
}

val errorCase = CASE(WILDCARD) ==> NEW("org.apache.avro.AvroRuntimeException", LIT("Bad index"))
val casesGet = indexedFields.map(field => asGetCase(field)):+errorCase
val methodsDef: List[forest.DefDef] = indexedFields.map(field => asGetMethod(field, classSymbol, typeMatcher, targetScalaPartialVersion))

val getDef = DEF("get", AnyRefClass) withParams(PARAM("field$", IntClass)) := BLOCK(
REF("field$") withAnnots(ANNOT("switch")) MATCH(casesGet)
)
getDef:: methodsDef
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import scala.jdk.CollectionConverters._

object SpecificCaseClassTree {

// The java platform does not allow parameter lists longer than 254
val PLATFORM_MAX_PARAMS = 254

def toCaseClassDef(
classStore: ClassStore,
namespace: Option[String],
Expand All @@ -29,160 +32,172 @@ object SpecificCaseClassTree {
val classSymbol = RootClass.newClass(schema.getName)
val avroFields = schema.getFields().asScala.toList

val shouldGenerateSimpleClass = restrictedFields && avroFields.size > 22

// generate list of constructor parameters
val params: List[ValDef] = avroFields.map { f =>
val fieldName = FieldRenamer.rename(f.name)
val fieldType = typeMatcher.toScalaType(classStore, namespace, f.schema)
val defaultValue = DefaultValueMatcher.getDefaultValue(
val tooManyParams = avroFields.length > PLATFORM_MAX_PARAMS
if(tooManyParams) {
SpecificClassPublicVarMembersTree.toTooManyFieldsForAConstructorClassDef(
classStore,
namespace,
f,
schema,
typeMatcher,
fieldName == fieldType.safeToString)
VAR(fieldName, fieldType) := defaultValue
}
maybeBaseTrait,
maybeFlags,
targetScalaPartialVersion)
} else {

// extension
val baseClassName = "org.apache.avro.specific.SpecificRecordBase"
val baseClass = RootClass.newClass(baseClassName)
val shouldGenerateSimpleClass = restrictedFields && avroFields.size > 22

// no-arg constructor: make arbitrary default if none is provided
val defaultParams: List[Tree] = avroFields.zip(params).map(f => {
val (avroField, defaultValue) = (f._1, f._2.rhs)
if (defaultValue == EmptyTree)
DefaultParamMatcher.asDefaultParam(classStore, avroField.schema, typeMatcher)
else
defaultValue
})
val defThis = DEFTHIS.withParams(PARAM("")).tree := {
THIS APPLY(defaultParams)
}
// generate list of constructor parameters
val params: List[ValDef] = avroFields.map { f =>
val fieldName = FieldRenamer.rename(f.name)
val fieldType = typeMatcher.toScalaType(classStore, namespace, f.schema)
val defaultValue = DefaultValueMatcher.getDefaultValue(
classStore,
namespace,
f,
typeMatcher,
fieldName == fieldType.safeToString)
VAR(fieldName, fieldType) := defaultValue
}

// methods - first add an index the the schema's fields
val indexedFields = avroFields.zipWithIndex.map(p => {
val avroField = p._1
val index = p._2
IndexedField(avroField, index)
})
val defGetSchema = namespace.fold(GetSchemaGenerator(classSymbol).toDef)(ns => GetSchemaGenerator(RootClass.newClass(s"$ns.${classSymbol}")).toDef)
val defGet = GetGenerator.toDef(indexedFields, classSymbol, typeMatcher, targetScalaPartialVersion)
val defPut = PutGenerator.toDef(
classStore,
namespace,
indexedFields,
typeMatcher,
classSymbol,
targetScalaPartialVersion)
// extension
val baseClassName = "org.apache.avro.specific.SpecificRecordBase"
val baseClass = RootClass.newClass(baseClassName)

val maybeFlagsWithCaseClassFinal =
if (shouldGenerateSimpleClass) maybeFlags
else maybeFlags.map { flags =>
if (flags.contains(Flags.FINAL)) flags
else flags :+ Flags.FINAL.toLong
// no-arg constructor: make arbitrary default if none is provided
val defaultParams: List[Tree] = avroFields.zip(params).map(f => {
val (avroField, defaultValue) = (f._1, f._2.rhs)
if (defaultValue == EmptyTree)
DefaultParamMatcher.asDefaultParam(classStore, avroField.schema, typeMatcher)
else
defaultValue
})
val defThis = DEFTHIS.withParams(PARAM("")).tree := {
THIS APPLY (defaultParams)
}

// methods - first add an index the the schema's fields
val indexedFields = avroFields.zipWithIndex.map(p => {
val avroField = p._1
val index = p._2
IndexedField(avroField, index)
})
val defGetSchema = namespace.fold(GetSchemaGenerator(classSymbol).toDef)(ns => GetSchemaGenerator(RootClass.newClass(s"$ns.${classSymbol}")).toDef)
val defGet = GetGenerator.toDef(indexedFields, classSymbol, typeMatcher, targetScalaPartialVersion)
val defPut = PutGenerator.toDef(
classStore,
namespace,
indexedFields,
typeMatcher,
classSymbol,
targetScalaPartialVersion)

// define the class def with the members previously defined
// There could be base traits, flags, or both, and could have no fields
val caseClassDef = (maybeBaseTrait, maybeFlagsWithCaseClassFinal) match {
case (Some(baseTrait), Some(flags)) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(params)
.withParents(baseClass)
.withParents(baseTrait)
}
else if (avroFields.nonEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(params)
.withParents(baseClass)
.withParents(baseTrait)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(PARAM(""))
.withParents(baseClass)
.withParents(baseTrait)
}
case (Some(baseTrait), None) =>
if (!avroFields.isEmpty) {
CASECLASSDEF(classSymbol)
.withParams(params)
.withFlags(Flags.FINAL)
.withParents(baseClass)
.withParents(baseTrait)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withParams(PARAM(""))
.withFlags(Flags.FINAL)
.withParents(baseClass)
.withParents(baseTrait)
val maybeFlagsWithCaseClassFinal =
if (shouldGenerateSimpleClass) maybeFlags
else maybeFlags.map { flags =>
if (flags.contains(Flags.FINAL)) flags
else flags :+ Flags.FINAL.toLong
}
case (None, Some(flags)) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(params)
.withParents(baseClass)
.withParents("Serializable")
}
else if (avroFields.nonEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(params)
.withParents(baseClass)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withFlags(flags:_*)
.withParams(PARAM(""))
.withParents(baseClass)
}
case (None, None) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withParams(params)
.withParents(baseClass)
.withParents("Serializable")
}
else if (!avroFields.isEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(Flags.FINAL)
.withParams(params)
.withParents(baseClass)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withParams(PARAM(""))
.withParents(baseClass)
}
}

val caseClassTree = {
// for "empty" records: empty params and no no-arg ctor
if (!avroFields.isEmpty) caseClassDef := BLOCK(
defThis,
defGet,
defPut,
defGetSchema)
else caseClassDef := BLOCK(
defGet,
defPut,
defGetSchema)
}

val treeWithScalaDoc = ScalaDocGenerator.docToScalaDoc(
Left(schema),
caseClassTree)
// define the class def with the members previously defined
// There could be base traits, flags, or both, and could have no fields
val caseClassDef = (maybeBaseTrait, maybeFlagsWithCaseClassFinal) match {
case (Some(baseTrait), Some(flags)) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(params)
.withParents(baseClass)
.withParents(baseTrait)
}
else if (avroFields.nonEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(params)
.withParents(baseClass)
.withParents(baseTrait)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(PARAM(""))
.withParents(baseClass)
.withParents(baseTrait)
}
case (Some(baseTrait), None) =>
if (!avroFields.isEmpty) {
CASECLASSDEF(classSymbol)
.withParams(params)
.withFlags(Flags.FINAL)
.withParents(baseClass)
.withParents(baseTrait)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withParams(PARAM(""))
.withFlags(Flags.FINAL)
.withParents(baseClass)
.withParents(baseTrait)
}
case (None, Some(flags)) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(params)
.withParents(baseClass)
.withParents("Serializable")
}
else if (avroFields.nonEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(params)
.withParents(baseClass)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withFlags(flags: _*)
.withParams(PARAM(""))
.withParents(baseClass)
}
case (None, None) =>
if (shouldGenerateSimpleClass) {
CLASSDEF(classSymbol)
.withParams(params)
.withParents(baseClass)
.withParents("Serializable")
}
else if (!avroFields.isEmpty) {
CASECLASSDEF(classSymbol)
.withFlags(Flags.FINAL)
.withParams(params)
.withParents(baseClass)
}
else { // for "empty" records: empty params and no no-arg ctor
CASECLASSDEF(classSymbol)
.withParams(PARAM(""))
.withParents(baseClass)
}
}

val caseClassTree = {
// for "empty" records: empty params and no no-arg ctor
if (!avroFields.isEmpty) caseClassDef := BLOCK(
defThis,
defGet,
defPut,
defGetSchema)
else caseClassDef := BLOCK(
defGet,
defPut,
defGetSchema)
}

treeWithScalaDoc
val treeWithScalaDoc = ScalaDocGenerator.docToScalaDoc(
Left(schema),
caseClassTree)

treeWithScalaDoc
}
}


Expand Down
Loading
Loading