Skip to content

Commit

Permalink
Merge pull request #108 from piotr-kalanski/development
Browse files Browse the repository at this point in the history
Release 0.7.7
  • Loading branch information
piotr-kalanski committed Jun 14, 2018
2 parents 10e5859 + 0fd4ed7 commit bec576f
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 97 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Data model generator based on Scala case classes.
Include dependency:

```scala
"com.github.piotr-kalanski" % "data-model-generator_2.11" % "0.7.5"
"com.github.piotr-kalanski" % "data-model-generator_2.11" % "0.7.7"
```

or
Expand All @@ -54,7 +54,7 @@ or
<dependency>
<groupId>com.github.piotr-kalanski</groupId>
<artifactId>data-model-generator_2.11</artifactId>
<version>0.7.5</version>
<version>0.7.7</version>
</dependency>
```

Expand Down Expand Up @@ -355,9 +355,9 @@ HiveServiceImpl.createHiveTable[Person]()

# Extracting class metadata

To extract class metadata you can use method `MetaDataExtractor.extractClassMetaDataForDialect`. Example:
To extract class metadata you can use method `MetaDataWithDialectExtractor.extractClassMetaDataForDialect`. Example:
```scala
MetaDataExtractor.extractClassMetaDataForDialect[T](Some(dialects.HiveDialect))
MetaDataWithDialectExtractor.extractClassMetaDataForDialect[T](Some(dialects.HiveDialect))
```

# Customizations
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name := "data-model-generator"

organization := "com.github.piotr-kalanski"

version := "0.7.6"
version := "0.7.7"

scalaVersion := "2.11.8"

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/datawizards/dmg/DataModelGenerator.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.datawizards.dmg

import com.datawizards.dmg.dialects.Dialect
import com.datawizards.dmg.dialects.{Dialect, MetaDataWithDialectExtractor}
import com.datawizards.dmg.metadata._
import org.apache.log4j.Logger

Expand Down Expand Up @@ -34,7 +34,7 @@ object DataModelGenerator {
}

private def getClassMetaData[T: ClassTag: TypeTag](dialect: Dialect): ClassTypeMetaData =
MetaDataExtractor.extractClassMetaDataForDialect[T](Some(dialect))
MetaDataWithDialectExtractor.extractClassMetaDataForDialect[T](Some(dialect))

private def generateDataModel(dialect: Dialect, classTypeMetaData: ClassTypeMetaData): String = {
dialect.generateDataModel(classTypeMetaData, generateFieldsExpressions(dialect, classTypeMetaData))
Expand Down
16 changes: 12 additions & 4 deletions src/main/scala/com/datawizards/dmg/annotations/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,23 @@ package object annotations {
/**
* @param value documentation comment
*/
final class comment(val value: String) extends StaticAnnotation
final class comment(val value: String, val dialect: Dialect) extends StaticAnnotation {
def this(value: String) = this(value, null)
}

/**
* @param value column length
*/
final class length(val value: Int) extends StaticAnnotation
final class length(val value: Int, val dialect: Dialect) extends StaticAnnotation {
def this(value: Int) = this(value, null)
}

/**
* Not null field
*/
final class notNull extends StaticAnnotation
final class notNull(val dialect: Dialect) extends StaticAnnotation {
def this() = this(null)
}

/**
* Convert table name and column names to underscore.
Expand All @@ -44,5 +50,7 @@ package object annotations {
*
* @param dialect Dialect for which make conversion
*/
final class underscore(val dialect: Dialect) extends StaticAnnotation
final class underscore(val dialect: Dialect) extends StaticAnnotation {
def this() = this(null)
}
}
6 changes: 3 additions & 3 deletions src/main/scala/com/datawizards/dmg/dialects/Dialect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ trait Dialect {
def bigDecimalType: String
def bigIntegerType: String

def fieldLength(f: ClassFieldMetaData): Option[String] = f.getAnnotationValue(Length)
def fieldLength(f: ClassFieldMetaData): Option[String] = MetaDataWithDialectExtractor.getAnnotationForDialect(Length, Some(this), f).map(_.getValue)

def comment(a: HasAnnotations): Option[String] = a.getAnnotationValue(Comment)
def comment(a: HasAnnotations): Option[String] = MetaDataWithDialectExtractor.getAnnotationForDialect(Comment, Some(this), a).map(_.getValue)

def notNull(f: ClassFieldMetaData): Boolean = f.annotationExists(NotNull)
def notNull(f: ClassFieldMetaData): Boolean = MetaDataWithDialectExtractor.getAnnotationForDialect(NotNull, Some(this), f).nonEmpty

def generateClassFieldExpression(f: ClassFieldMetaData): String =
generateClassFieldExpression(f, 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.datawizards.dmg.dialects

import com.datawizards.dmg.metadata.MetaDataExtractor.extractClassMetaData
import com.datawizards.dmg.metadata._

import scala.reflect.runtime.universe.TypeTag

object MetaDataWithDialectExtractor {
/**
* Extract class metadata and change types and fields taking into account dialect as context.
* @param dialect rename class and field names according to dialect. If dialect is null, then
*/
def extractClassMetaDataForDialect[T: TypeTag](dialect: Option[Dialect]): ClassTypeMetaData =
changeName(dialect, extractClassMetaData[T]())

private def changeName(dialect: Option[Dialect], c: ClassTypeMetaData): ClassTypeMetaData =
c.copy(
typeName = getClassName(dialect, c),
fields = c.fields.map(f => changeName(dialect, f, c))
)

private def changeName(dialect: Option[Dialect], classFieldMetaData: ClassFieldMetaData, classTypeMetaData: ClassTypeMetaData): ClassFieldMetaData =
classFieldMetaData.copy(
fieldName = getFieldName(dialect, classFieldMetaData, classTypeMetaData),
fieldType = changeName(dialect, classFieldMetaData.fieldType)
)

private def changeName(dialect: Option[Dialect], typeMetaData: TypeMetaData): TypeMetaData = typeMetaData match {
case p:PrimitiveTypeMetaData => p
case c:CollectionTypeMetaData => c
case m:MapTypeMetaData => m
case c:ClassTypeMetaData => changeName(dialect, c)
}

private val Table = "com.datawizards.dmg.annotations.table"
private val Column = "com.datawizards.dmg.annotations.column"
private val Underscore = "com.datawizards.dmg.annotations.underscore"


private def getClassName(dialect: Option[Dialect], classMetaData: ClassTypeMetaData): String = {
val dialectSpecificTableAnnotation = getAnnotationForDialect(Table, dialect, classMetaData)

dialectSpecificTableAnnotation.map(a => a.attributes.filter(_.name == "name").head.value )
.getOrElse(convertToUnderscoreIfRequired(classMetaData.typeName, dialect, classMetaData))
}

private def getFieldName(dialect: Option[Dialect], classFieldMetaData: ClassFieldMetaData, classTypeMetaData: ClassTypeMetaData): String = {
val columnAnnotations = getAnnotationForDialect(Column, dialect, classFieldMetaData)

columnAnnotations.map(a => a.attributes.filter(_.name == "name").head.value)
.getOrElse(convertToUnderscoreIfRequired(classFieldMetaData.fieldName, dialect, classTypeMetaData))
}

private def convertToUnderscoreIfRequired(name: String, dialect: Option[Dialect], classTypeMetaData: ClassTypeMetaData): String = {
val underscoreAnnotation: Option[AnnotationMetaData] = getAnnotationForDialect(Underscore, dialect, classTypeMetaData)
underscoreAnnotation.map(a => name.replaceAll("(.)(\\p{Upper})","$1_$2").toLowerCase).getOrElse(name)
}

def getAnnotationForDialect(annotationName: String, dialect: Option[Dialect], hasAnnotations: HasAnnotations): Option[AnnotationMetaData] = {
if(dialect.isDefined){
val dialectName = dialect.get.toString
val annotationsFiltered: Iterable[AnnotationMetaData] = hasAnnotations.annotations
.filter(a => a.name == annotationName)

val annotation: Option[AnnotationMetaData] = annotationsFiltered
.find(a => a.attributes.find(_.name == "dialect").map(p => p.value.endsWith(dialectName)).getOrElse(false) )
.orElse(annotationsFiltered.find(a => !a.attributes.exists(_.name == "dialect") ).headOption)
annotation
} else {
val annotation: Option[AnnotationMetaData] = hasAnnotations.annotations
.filter(a => a.name == annotationName)
.find(a => !a.attributes.exists(_.name == "dialect") )
annotation
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.datawizards.dmg.metadata

import com.datawizards.dmg.dialects.Dialect

import scala.reflect.NameTransformer
import scala.reflect.runtime.universe._

Expand All @@ -27,13 +25,6 @@ object MetaDataExtractor {
}
}

/**
* Extract class metadata and change types and fields taking into account dialect as context.
* @param dialect rename class and field names according to dialect. If dialect is null, then
*/
def extractClassMetaDataForDialect[T: TypeTag](dialect: Option[Dialect]): ClassTypeMetaData =
changeName(dialect, extractClassMetaData[T]())

def extractTypeMetaData[T : TypeTag](): TypeMetaData =
extractTypeMetaData(localTypeOf[T])

Expand Down Expand Up @@ -135,66 +126,4 @@ object MetaDataExtractor {
}
)
)

private def changeName(dialect: Option[Dialect], c: ClassTypeMetaData): ClassTypeMetaData =
c.copy(
typeName = getClassName(dialect, c),
fields = c.fields.map(f => changeName(dialect, f, c))
)

private def changeName(dialect: Option[Dialect], classFieldMetaData: ClassFieldMetaData, classTypeMetaData: ClassTypeMetaData): ClassFieldMetaData =
classFieldMetaData.copy(
fieldName = getFieldName(dialect, classFieldMetaData, classTypeMetaData),
fieldType = changeName(dialect, classFieldMetaData.fieldType)
)

private def changeName(dialect: Option[Dialect], typeMetaData: TypeMetaData): TypeMetaData = typeMetaData match {
case p:PrimitiveTypeMetaData => p
case c:CollectionTypeMetaData => c
case m:MapTypeMetaData => m
case c:ClassTypeMetaData => changeName(dialect, c)
}

private val Table = "com.datawizards.dmg.annotations.table"
private val Column = "com.datawizards.dmg.annotations.column"
private val Underscore = "com.datawizards.dmg.annotations.underscore"


private def getClassName(dialect: Option[Dialect], classMetaData: ClassTypeMetaData): String = {
val dialectSpecificTableAnnotation = getAnnotationMetadata(Table, dialect, classMetaData.annotations)

dialectSpecificTableAnnotation.map(a => a.attributes.filter(_.name == "name").head.value )
.getOrElse(convertToUnderscoreIfRequired(classMetaData.typeName, dialect, classMetaData))
}

private def getFieldName(dialect: Option[Dialect], classFieldMetaData: ClassFieldMetaData, classTypeMetaData: ClassTypeMetaData): String = {
val columnAnnotations = getAnnotationMetadata(Column, dialect, classFieldMetaData.annotations)

columnAnnotations.map(a => a.attributes.filter(_.name == "name").head.value)
.getOrElse(convertToUnderscoreIfRequired(classFieldMetaData.fieldName, dialect, classTypeMetaData))
}

private def convertToUnderscoreIfRequired(name: String, dialect: Option[Dialect], classTypeMetaData: ClassTypeMetaData): String = {
val dialectName = dialect.toString.replace("Dialect","")
val underscoreAnnotation: Option[AnnotationMetaData] = getAnnotationMetadata(Underscore, dialect, classTypeMetaData.annotations)
underscoreAnnotation.map(a => name.replaceAll("(.)(\\p{Upper})","$1_$2").toLowerCase).getOrElse(name)
}

private def getAnnotationMetadata(annotationName: String, dialect: Option[Dialect], annotations: Iterable[AnnotationMetaData]): Option[AnnotationMetaData] = {
if(dialect.isDefined){
val dialectName = dialect.get.toString
val annotationsFiltered: Iterable[AnnotationMetaData] = annotations
.filter(a => a.name == annotationName)

val annotation: Option[AnnotationMetaData] = annotationsFiltered
.find(a => a.attributes.find(_.name == "dialect").map(p => p.value.endsWith(dialectName)).getOrElse(false) )
.orElse(annotationsFiltered.find(a => !a.attributes.exists(_.name == "dialect") ).headOption)
annotation
} else {
val annotation: Option[AnnotationMetaData] = annotations
.filter(a => a.name == annotationName)
.find(a => !a.attributes.exists(_.name == "dialect") )
annotation
}
}
}
16 changes: 10 additions & 6 deletions src/main/scala/com/datawizards/dmg/metadata/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package com.datawizards.dmg
package object metadata {
case class AnnotationAttributeMetaData(name: String, value: String)

case class AnnotationMetaData(name: String, attributes: Seq[AnnotationAttributeMetaData])
trait HasValueAttribute {
val attributes: Seq[AnnotationAttributeMetaData]

def getValue: String = {
attributes.head.value
}
}

case class AnnotationMetaData(name: String, attributes: Seq[AnnotationAttributeMetaData]) extends HasValueAttribute

trait HasAnnotations {
val annotations: Iterable[AnnotationMetaData]

def getAnnotationValue(annotationName: String): Option[String] = {
val annotation = annotations.find(_.name == annotationName)
if(annotation.isDefined)
Some(annotation.get.attributes.head.value)
else
None
annotations.find(_.name == annotationName).map(_.getValue)
}

def annotationExists(annotationName: String): Boolean = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.datawizards.dmg.service

import com.datawizards.dmg.metadata.MetaDataExtractor
import com.datawizards.dmg.dialects.MetaDataWithDialectExtractor
import com.datawizards.dmg.{DataModelGenerator, dialects}
import org.apache.log4j.Logger

Expand Down Expand Up @@ -37,7 +37,7 @@ object HiveServiceImpl extends HiveService {
}

private def buildCreateTableTemplate[T: ClassTag: TypeTag](): String = {
val classTypeMetaData = MetaDataExtractor.extractClassMetaDataForDialect[T](Some(dialects.HiveDialect))
val classTypeMetaData = MetaDataWithDialectExtractor.extractClassMetaDataForDialect[T](Some(dialects.HiveDialect))
val tableName = classTypeMetaData.typeName
val createTableExpression = DataModelGenerator.generate[T](dialects.HiveDialect, classTypeMetaData)
val dropTableExpression = s"DROP TABLE IF EXISTS $tableName;\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.datawizards.dmg

import com.datawizards.dmg.metadata.{MetaDataExtractor, PersonFull}
import com.datawizards.dmg.dialects.MetaDataWithDialectExtractor
import com.datawizards.dmg.metadata.PersonFull
import org.scalatest.{FunSuite, Matchers}

class ExtractClassMetaDataForDialectTest extends FunSuite with Matchers {

test("PersonFull - Elasticsearch") {
val a = MetaDataExtractor.extractClassMetaDataForDialect[PersonFull](Some(com.datawizards.dmg.dialects.ElasticsearchDialect))
val a = MetaDataWithDialectExtractor.extractClassMetaDataForDialect[PersonFull](Some(com.datawizards.dmg.dialects.ElasticsearchDialect))

println(a)

Expand All @@ -18,7 +19,7 @@ class ExtractClassMetaDataForDialectTest extends FunSuite with Matchers {
}

test("PersonFull - MySQL") {
val a = MetaDataExtractor.extractClassMetaDataForDialect[PersonFull](Some(com.datawizards.dmg.dialects.MySQLDialect))
val a = MetaDataWithDialectExtractor.extractClassMetaDataForDialect[PersonFull](Some(com.datawizards.dmg.dialects.MySQLDialect))


println(a)
Expand All @@ -31,7 +32,7 @@ class ExtractClassMetaDataForDialectTest extends FunSuite with Matchers {


test("PersonFull - None dialect") {
val a = MetaDataExtractor.extractClassMetaDataForDialect[PersonFull](None)
val a = MetaDataWithDialectExtractor.extractClassMetaDataForDialect[PersonFull](None)

println(a)

Expand Down
24 changes: 24 additions & 0 deletions src/test/scala/com/datawizards/dmg/TestModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,28 @@ object TestModel {
case class ClassWithArrayByte(arr: Array[Byte])
case class ClassWithBigInteger(n1: BigInt)
case class ClassWithBigDecimal(n1: BigDecimal)


@comment(value="mysql comment", dialect=dialects.MySQLDialect)
@comment(value="hive comment", dialect=dialects.HiveDialect)
@underscore(dialect=dialects.HiveDialect)
case class ClassWithMultipleDialects(
@comment(value="mysql comment 2", dialect=dialects.MySQLDialect)
@comment(value="hive comment 2", dialect=dialects.HiveDialect)
@length(value=200, dialect=dialects.MySQLDialect)
someColumn: String,

@comment(value="mysql comment 3", dialect=dialects.MySQLDialect)
@comment(value="general comment 3")
@notNull(dialect=dialects.MySQLDialect)
anotherColumn: Integer
)

@underscore
case class DefaultUnderscore(
@length(123)
@notNull
@comment("asdf")
someColumn: String
)
}
Loading

0 comments on commit bec576f

Please sign in to comment.