From 8604a77f2ad6a268d0b0a23481ea4f7ccfe6c426 Mon Sep 17 00:00:00 2001 From: Konstantin Sobolev Date: Wed, 8 Nov 2017 15:00:46 -0800 Subject: [PATCH] added field asm suppliers codegen --- .../scala/ws/epigraph/java/JavaGenNames.scala | 4 +- .../assemblers/FieldAssemblersGen.scala | 131 ++++++++++++++++++ .../service/assemblers/RecordAsmGen.scala | 58 ++++++-- .../req/ReqTypeProjectionGen.scala | 2 +- 4 files changed, 178 insertions(+), 17 deletions(-) create mode 100644 java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/FieldAssemblersGen.scala diff --git a/java/codegen/src/main/scala/ws/epigraph/java/JavaGenNames.scala b/java/codegen/src/main/scala/ws/epigraph/java/JavaGenNames.scala index 0a544c8b2..b0a3009c2 100644 --- a/java/codegen/src/main/scala/ws/epigraph/java/JavaGenNames.scala +++ b/java/codegen/src/main/scala/ws/epigraph/java/JavaGenNames.scala @@ -171,9 +171,9 @@ object JavaGenNames { def qnameArgs(fqn: Qn): Seq[String] = fqn.last() +: fqn.removeLastSegment().segments.toSeq - def javaTypeName(ln: String): String = if (ReservedTypeNames.contains(ln)) ln + '_' else ln + def javaTypeName(ln: String): String = if (ReservedTypeNames.contains(ln)) ln + '_' else JavaNames.javaName(ln) - def javaFieldName(fn: String): String = if (ReservedFieldNames.contains(fn)) fn + '_' else fn + def javaFieldName(fn: String): String = if (ReservedFieldNames.contains(fn)) fn + '_' else JavaNames.javaName(fn) /** set of type names that conflict with our own generated java classes */ private val ReservedTypeNames: Set[String] = Set( diff --git a/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/FieldAssemblersGen.scala b/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/FieldAssemblersGen.scala new file mode 100644 index 000000000..5aea4c259 --- /dev/null +++ b/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/FieldAssemblersGen.scala @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Sumo Logic + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ws.epigraph.java.service.assemblers + +import java.nio.file.Path + +import ws.epigraph.compiler.CDatumType +import ws.epigraph.java._ +import ws.epigraph.java.NewlineStringInterpolator.NewlineHelper +import ws.epigraph.java.JavaGenNames.{ln, lqn2} +import ws.epigraph.java.service.projections.req.output.ReqOutputRecordModelProjectionGen +import ws.epigraph.lang.Qn +import ws.epigraph.util.JavaNames + +/** + * @author Konstantin Sobolev + */ +class FieldAssemblersGen(rag: RecordAsmGen, val ctx: GenContext) extends JavaGen { + private val cType: CDatumType = JavaGenUtils.toCType(rag.g.op.`type`()) + + val namespace: Qn = rag.g.namespace + + val shortClassName: String = ln(cType) + "FieldAssemblers" + + val fullClassName: String = namespace.append(shortClassName).toString + + override def relativeFilePath: Path = JavaGenUtils.fqnToPath(namespace).resolve(shortClassName + ".java") + + private lazy val importManager: ImportManager = new ImportManager(namespace) + + private lazy val parentOpt: Option[FieldAssemblersGen] = rag.g.parentClassGenOpt.map( + pg => pg.asInstanceOf[ReqOutputRecordModelProjectionGen].assemblerGen.fieldAssemblersGen + ) + + def methodName(fieldName: String): String = JavaNames.javaName(fieldName) + + case class AsmSupplier(fieldName: String, overloaded: Boolean, pg: ReqOutputRecordModelProjectionGen, importManager: ImportManager) { + private val fieldPart = rag.fieldPart(fieldName).get + + val projectionType: importManager.ImportedName = importManager.use(fieldPart.fieldGen.fullClassName) + + val assemblerResultType: importManager.ImportedName = importManager.use( + lqn2( + fieldPart.fieldType, + namespace.toString + ) + ) + + val resultTypeSuffix: String = if (fieldPart.isEntity) "" else ".Value" + + def gen: String = /*@formatter:off*/sn"""\ + /** + * Builds {@code $fieldName} field value + * + * @param dto data transfer object + * @param projection request projection + * @param ctx assembly context + * + * @return field value + */ + public ${Imports.notNull}$assemblerResultType$resultTypeSuffix ${methodName(fieldName)}(${Imports.notNull}D dto, ${Imports.notNull}$projectionType projection, ${Imports.notNull}${Imports.assemblerContext} context); +"""/*@formatter:on*/ + } + + val asmSuppliers: Seq[AsmSupplier] = rag.g.fieldProjections.toSeq.map { case (fieldName, (parentGenOpt, _)) => + AsmSupplier( + fieldName, + parentGenOpt.isDefined, + parentGenOpt.getOrElse(rag.g).asInstanceOf[ReqOutputRecordModelProjectionGen], + importManager + ) + } + + + override protected def generate: String = { + if (rag.g.invalidParentClassGenerator) { + throw new TryLaterException(s"Can't generate $fullClassName because parent projection wasn't created yet") + } + + val parentImp = parentOpt.map(p => importManager.use(p.fullClassName)) + + closeImports() + + val extendsClause: String = parentImp.map(ip => s"extends $ip ").getOrElse("") + + /*@formatter:off*/sn"""\ +${JavaGenUtils.topLevelComment} +package $namespace; + +${JavaGenUtils.generateImports(importManager.imports)} + +/** + * Field assemblers for {@code ${ln(cType)}} type + */ +${JavaGenUtils.generatedAnnotation(this)} +public interface $shortClassName $extendsClause{ +${asmSuppliers.map(_.gen).mkString("\n")}\ +}"""/*@formatter:on*/ + } + + protected object Imports { + val notNull: ImportManager.Imported = + if (ctx.java8Annotations) importManager.use("org.jetbrains.annotations.NotNull").prepend("@").append(" ") else ImportManager.empty + val nullable: ImportManager.Imported = + if (ctx.java8Annotations) importManager.use("org.jetbrains.annotations.Nullable").prepend("@").append(" ") else ImportManager.empty + val func: ImportManager.Imported = importManager.use("java.util.function.Function") + val assembler: ImportManager.Imported = importManager.use("ws.epigraph.assembly.Asm") + val assemblerContext: ImportManager.Imported = importManager.use("ws.epigraph.assembly.AsmContext") + val _type: ImportManager.Imported = importManager.use("ws.epigraph.types.Type") + val errValue: ImportManager.Imported = importManager.use("ws.epigraph.errors.ErrorValue") + } + + protected def closeImports(): Unit = { + val _ = Imports.assembler // cause lazy eval + importManager.close() + } +} diff --git a/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/RecordAsmGen.scala b/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/RecordAsmGen.scala index 56f325618..e70f5f0f8 100644 --- a/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/RecordAsmGen.scala +++ b/java/codegen/src/main/scala/ws/epigraph/java/service/assemblers/RecordAsmGen.scala @@ -29,11 +29,15 @@ import scala.collection.immutable.ListMap * @author Konstantin Sobolev */ class RecordAsmGen( - override protected val g: ReqOutputRecordModelProjectionGen, + override val g: ReqOutputRecordModelProjectionGen, val ctx: GenContext) extends JavaGen with ModelAsmGen { override protected type G = ReqOutputRecordModelProjectionGen + lazy val fieldAssemblersGen: FieldAssemblersGen = new FieldAssemblersGen(this, ctx) + + override def children = Iterable(fieldAssemblersGen) + import Imports._ case class FieldParts(field: CField, fieldGen: ReqProjectionGen) extends Comparable[FieldParts] { @@ -43,7 +47,7 @@ class RecordAsmGen( def isEntity: Boolean = fieldType.kind == CTypeKind.ENTITY - val fieldGenType: importManager.ImportedName = importManager.use(fieldGen.fullClassName) + val fieldProjection: importManager.ImportedName = importManager.use(fieldGen.fullClassName) val assemblerResultType: importManager.ImportedName = importManager.use( lqn2( @@ -52,7 +56,7 @@ class RecordAsmGen( ) ) - def fieldAsmType: String = s"$assembler" + def fieldAsmType: String = s"$assembler" def fbf: String = field.name + "FieldAsm" @@ -75,6 +79,8 @@ class RecordAsmGen( FieldParts(f, fg.dataProjectionGen) }.toSeq.sorted + def fieldPart(fieldName: String): Option[FieldParts] = fps.find(_.field.name == fieldName) + private val obj = importManager.use("java.lang.Object") protected override lazy val defaultBuild: String = { @@ -94,6 +100,8 @@ ${if (hasMeta) s" b.setMeta(metaAsm.assemble(dto, p.meta(), ctx));\n" else ""}\ } override protected def generate: String = { + val fieldAssembersImp = importManager.use(fieldAssemblersGen.fullClassName) + closeImports() /*@formatter:off*/sn"""\ @@ -106,15 +114,15 @@ ${JavaGenUtils.generateImports(importManager.imports)} * Value assembler for {@code ${ln(cType)}} type, driven by request output projection */ ${JavaGenUtils.generatedAnnotation(this)} -public class $shortClassName implements $assembler { -${if (hasTails) s" private final $notNull $func typeExtractor;\n" else "" }\ +public class $shortClassName implements $assembler { +${if (hasTails) s" private final $notNull$func typeExtractor;\n" else "" }\ //field assemblers -${fps.map { fp => s" private final $notNull ${fp.fieldAsmType} ${fp.fbf};"}.mkString("\n") }\ -${if (hasTails) tps.map { tp => s" private final $notNull ${tp.assemblerType} ${tp.assembler};"}.mkString("\n //tail assemblers\n","\n","") else "" }\ -${if (hasMeta) s" //meta assembler\n private final $notNull $metaAsmType metaAsm;" else ""} +${fps.map { fp => s" private final $notNull${fp.fieldAsmType} ${fp.fbf};"}.mkString("\n") }\ +${if (hasTails) tps.map { tp => s" private final $notNull${tp.assemblerType} ${tp.assembler};"}.mkString("\n //tail assemblers\n","\n","") else "" }\ +${if (hasMeta) s" //meta assembler\n private final $notNull$metaAsmType metaAsm;" else ""} /** - * Asm constructor + * Asm constructor from individual field assemblers * ${if (hasTails) s" * @param typeExtractor data type extractor, used to determine DTO type\n" else ""}\ ${fps.map { fp => s" * @param ${fp.javadoc}"}.mkString("\n") }\ @@ -122,10 +130,10 @@ ${if (hasTails) tps.map { tp => s" * @param ${tp.javadoc}"}.mkString("\n","\n" ${if (hasMeta) s"\n * @param metaAsm metadata assembler" else ""} */ public $shortClassName( -${if (hasTails) s" $notNull $func typeExtractor,\n" else "" }\ -${fps.map { fp => s" $notNull ${fp.fieldAsmType} ${fp.fbf}"}.mkString(",\n") }\ -${if (hasTails) tps.map { tp => s" $notNull ${tp.assemblerType} ${tp.assembler}"}.mkString(",\n", ",\n", "") else ""}\ -${if (hasMeta) s",\n $notNull $metaAsmType metaAsm" else ""} +${if (hasTails) s" $notNull$func typeExtractor,\n" else "" }\ +${fps.map { fp => s" $notNull${fp.fieldAsmType} ${fp.fbf}"}.mkString(",\n") }\ +${if (hasTails) tps.map { tp => s" $notNull${tp.assemblerType} ${tp.assembler}"}.mkString(",\n", ",\n", "") else ""}\ +${if (hasMeta) s",\n $notNull$metaAsmType metaAsm" else ""} ) { ${if (hasTails) s" this.typeExtractor = typeExtractor;\n" else "" }\ ${fps.map { fp => s" this.${fp.fbf} = ${fp.fbf};"}.mkString("\n") }\ @@ -133,6 +141,28 @@ ${if (hasTails) tps.map { tp => s" this.${tp.assembler} = ${tp.assembler};"}. ${if (hasMeta) s"\n this.metaAsm = metaAsm;" else ""} } + /** + * Asm factory using field assemblers supplier object + * +${if (hasTails) s" * @param typeExtractor data type extractor, used to determine DTO type\n" else ""}\ + * @param fieldAssemblers field assemblers supplier object +${if (hasTails) tps.map { tp => s" * @param ${tp.javadoc}"}.mkString("","\n","\n") else "" }\ +${if (hasMeta) s"\n * @param metaAsm metadata assembler\n" else ""}\ + */ + public static $shortClassName fromFieldAssemblers( +${if (hasTails) s" $notNull$func typeExtractor,\n" else "" }\ + $notNull$fieldAssembersImp fieldAssemblers\ +${if (hasTails) tps.map { tp => s"$notNull${tp.assemblerType} ${tp.assembler}"}.mkString(",\n ", ",\n ","") else ""}\ +${if (hasMeta) s",\n $notNull$metaAsmType metaAsm" else ""} + ) { + return new $shortClassName(\ +${if (hasTails) s"\n typeExtractor," else "" }\ +${fps.map {fp => s"\n fieldAssemblers::${fieldAssemblersGen.methodName(fp.field.name)}"}.mkString("", ",", if(hasTails||hasMeta) "," else "")}\ +${if (hasTails) tps.map { tp => s" ${tp.assembler}"}.mkString("\n", ",\n", if(hasMeta) "," else "") else ""}\ +${if (hasMeta) "\n metaAsm" else ""} + ); + } + /** * Assembles {@code $t} value from DTO * @@ -143,7 +173,7 @@ ${if (hasMeta) s"\n this.metaAsm = metaAsm;" else ""} * @return {@code $t} value object */ @Override - public $notNull $t.Value assemble(D dto, $notNull $projectionName p, $notNull $assemblerContext ctx) { + public $notNull$t.Value assemble(D dto, $notNull$projectionName p, $notNull$assemblerContext ctx) { if (dto == null) return $t.type.createValue($errValue.NULL); else ${if (hasTails) tailsBuild else nonTailsBuild} diff --git a/java/codegen/src/main/scala/ws/epigraph/java/service/projections/req/ReqTypeProjectionGen.scala b/java/codegen/src/main/scala/ws/epigraph/java/service/projections/req/ReqTypeProjectionGen.scala index f4d73a05c..8c50e1992 100644 --- a/java/codegen/src/main/scala/ws/epigraph/java/service/projections/req/ReqTypeProjectionGen.scala +++ b/java/codegen/src/main/scala/ws/epigraph/java/service/projections/req/ReqTypeProjectionGen.scala @@ -79,7 +79,7 @@ trait ReqTypeProjectionGen extends ReqProjectionGen { generate0 } - protected def invalidParentClassGenerator: Boolean = + def invalidParentClassGenerator: Boolean = parentClassGenOpt match { case Some(parentGen) => parentGen.invalidParentClassGenerator case None => normalizedFromGenOpt.isDefined