Skip to content

Commit

Permalink
Merge pull request #390 from anatoliykmetyuk/mutable
Browse files Browse the repository at this point in the history
Mutable binding classes
  • Loading branch information
eed3si9n committed Aug 16, 2016
2 parents 108d65c + 68e2bd5 commit c42cc11
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 39 deletions.
2 changes: 2 additions & 0 deletions cli/src/main/scala/scalaxb/compiler/Config.scala
Expand Up @@ -74,6 +74,7 @@ case class Config(items: Map[String, ConfigEntry]) {
copy(items = items.updated(item.name, item))
def remove(item: ConfigEntry): Config =
copy(items = items - item.name)
def generateMutable: Boolean = values contains GenerateMutable
}

object Config {
Expand Down Expand Up @@ -128,4 +129,5 @@ object ConfigEntry {
case class DispatchVersion(value: String) extends ConfigEntry
case object VarArg extends ConfigEntry
case object IgnoreUnknown extends ConfigEntry
case object GenerateMutable extends ConfigEntry
}
2 changes: 2 additions & 0 deletions cli/src/main/scala/scalaxb/compiler/Main.scala
Expand Up @@ -126,6 +126,8 @@ object Arguments {
verbose = true
c
}
opt[Unit]("mutable") text("generates mutable classes") action { (_,c) =>
c.update(GenerateMutable).remove(VarArg) }
help("help") text("display this message")
version("version") text("display version info")
arg[File]("<schema_file>...") unbounded() text("input schema to be converted") action { (x, c) =>
Expand Down
108 changes: 72 additions & 36 deletions cli/src/main/scala/scalaxb/compiler/xsd/GenSource.scala
Expand Up @@ -136,11 +136,12 @@ abstract class GenSource(val schema: SchemaDecl,
// val name = context.typeNames(pkg)(sch)
// "import " + pkg.map(_ + ".").getOrElse("") + buildDefaultProtocolName(name) + "._"
// }


def valOrVar = if (config.generateMutable) "var" else "val"

val traitCode = <source>{ buildComment(decl) }trait {localName}{extendString} {{
{
val vals = for (param <- paramList)
yield "val " + param.toTraitScalaCode
val vals = for (param <- paramList) yield s"$valOrVar ${param.toTraitScalaCode(false)}"
vals.mkString(newline + indent(1))}
}}</source>

Expand Down Expand Up @@ -258,10 +259,16 @@ abstract class GenSource(val schema: SchemaDecl,
case _ => (0 to flatParticles.size - 1).toList map { i => buildArg(flatParticles(i), i) }
}

val accessors = (primary match {
case Some(all: AllDecl) if longAll => generateAccessors(all)
case _ => generateAccessors(paramList, splitSequences(decl))
}) ::: (if (longAttribute) generateAccessors(attributes) else Nil)
def gettersAndIfMutableSetters: List[(String, Option[String])] =
(primary match {
case Some(all: AllDecl) if longAll => generateAccessors(all)
case _ => generateAccessors(paramList, splitSequences(decl))
}) :::
(if (longAttribute) generateAccessors(attributes) else Nil)

// There should be a better way to flatten a List[(String, Option[String])] to List[String]
val accessors: List[String] = gettersAndIfMutableSetters.map{ p => List(List(p._1), p._2.toList)}.flatten.flatten

logger.debug("makeCaseClassWithType: generateAccessors " + accessors)

val compositors = context.compositorParents.filter(
Expand All @@ -276,7 +283,7 @@ abstract class GenSource(val schema: SchemaDecl,
def paramsString = if (hasSequenceParam) makeParamName(paramList.head.name, false) + ": " +
paramList.head.singleTypeName + "*"

else paramList.map(_.toScalaCode).mkString("," + newline + indent(1))
else paramList.map(_.toScalaCode_possiblyMutable).mkString("," + newline + indent(1))

val simpleFromXml: Boolean = if (flatParticles.isEmpty && !effectiveMixed) true
else (decl.content, primary) match {
Expand Down Expand Up @@ -463,7 +470,7 @@ abstract class GenSource(val schema: SchemaDecl,
(!paramList.head.attribute)
val paramsString = if (hasSequenceParam)
makeParamName(paramList.head.name, false) + ": " + paramList.head.singleTypeName + "*"
else paramList.map(_.toScalaCode).mkString("," + newline + indent(1))
else paramList.map(_.toScalaCode_possiblyMutable).mkString("," + newline + indent(1))
def makeWritesXML = <source> def writes(__obj: {fqn}, __namespace: Option[String], __elementLabel: Option[String],
__scope: scala.xml.NamespaceBinding, __typeAttribute: Boolean): scala.xml.NodeSeq =
{childString}</source>
Expand Down Expand Up @@ -543,7 +550,7 @@ abstract class GenSource(val schema: SchemaDecl,
case x => buildArg(x)
}
val paramsString = paramList.map(
_.toScalaCode).mkString("," + newline + indent(1))
_.toScalaCode_possiblyMutable).mkString("," + newline + indent(1))
val argsString = argList.mkString("," + newline + indent(3))
val attributeString = attributes.map(x => buildAttributeString(x)).mkString(newline + indent(2))

Expand Down Expand Up @@ -892,43 +899,71 @@ object {localName} {{
val symbol = content.base.asInstanceOf[ReferenceTypeSymbol]
List(buildSymbolElement(symbol))
}

def generateAccessors(all: AllDecl): List[String] = {
val wrapperName = makeParamName("all", false)


def lazyValOrDef = if (config.generateMutable) "def" else "lazy val"

def getterDeclaration(paramName: String, wrapperName: String, quotedNodeName: String, typeName: String, isOptional: Boolean) =
if (isOptional) s"$lazyValOrDef $paramName = $wrapperName.get($quotedNodeName) map { _.as[$typeName]}"
else s"$lazyValOrDef $paramName = $wrapperName($quotedNodeName).as[$typeName]"

def setterDeclaration(paramName: String, wrapperName: String, quotedNodeName: String, typeName: String, isOptional: Boolean): Option[String] =
if (config.generateMutable) {
val t = if (isOptional) s"Option[$typeName]" else typeName
Some(s"def ${paramName}_=(_value: $t)(implicit evidence: scalaxb.CanWriteXML[$t]) = $wrapperName += $quotedNodeName -> scalaxb.DataRecord(_value)")
}
else None

def generateAccessors(all: AllDecl): List[(String, Option[String])] = {
// by spec, there are only elements under <all>
val wrapperName = makeParamName("all", false)
all.particles collect {
case elem: ElemDecl => elem
case ref: ElemRef => buildElement(ref)
} map { elem => toCardinality(elem.minOccurs, elem.maxOccurs) match {
case Optional => "lazy val " + makeParamName(elem.name, false) + " = " +
wrapperName + ".get(" + quote(buildNodeName(elem, true)) + ") map { _.as[" + buildTypeName(elem.typeSymbol) + "] }"
case _ => "lazy val " + makeParamName(elem.name, false) + " = " +
wrapperName + "(" + quote(buildNodeName(elem, true)) + ").as[" + buildTypeName(elem.typeSymbol) + "]"
}
case ref : ElemRef => buildElement(ref)
} map { elem => val paramName = makeParamName(elem.name, false)
val quotedNodeName = quote(buildNodeName(elem , true ))
val typeName = buildTypeName(elem.typeSymbol)
val isOptional = toCardinality(elem.minOccurs, elem.maxOccurs) == Optional

( getterDeclaration(paramName, wrapperName, quotedNodeName, typeName, isOptional)
, setterDeclaration(paramName, wrapperName, quotedNodeName, typeName, isOptional))

//s"lazy val ${makeParamName(elem.name, false)} = $wrapperName.get(${quote(buildNodeName(elem, true))}) map { _.as[${buildTypeName(elem.typeSymbol)}] }"
//s"lazy val ${makeParamName(elem.name, false)} = $wrapperName (${quote(buildNodeName(elem, true))}) .as[${buildTypeName(elem.typeSymbol)}]"
//case Optional => "lazy val " + makeParamName(elem.name, false) + " = " +
// wrapperName + ".get(" + quote(buildNodeName(elem, true)) + ") map { _.as[" + buildTypeName(elem.typeSymbol) + "] }"
//case _ => "lazy val " + makeParamName(elem.name, false) + " = " +
// wrapperName + "(" + quote(buildNodeName(elem, true)) + ").as[" + buildTypeName(elem.typeSymbol) + "]"
//}
}
}

def generateAccessors(attributes: List[AttributeLike]): List[String] = {
def generateAccessors(attributes: List[AttributeLike]): List[(String, Option[String])] = {
val wrapperName = makeParamName(ATTRS_PARAM, false)

attributes collect {
case attr: AttributeDecl => (attr, toCardinality(attr))
case ref: AttributeRef =>
val attr = buildAttribute(ref)
(attr, toCardinality(attr))
case attr: AttributeDecl => (attr, toCardinality(attr))
case ref: AttributeRef => val attr = buildAttribute(ref); (attr, toCardinality(attr))
case group: AttributeGroupDecl => (group, Single)
} collect {
case (attr: AttributeDecl, Optional) =>
"lazy val " + makeParamName(buildParam(attr).name, true) + " = " +
wrapperName + ".get(" + quote(buildNodeName(attr, false)) + ") map { _.as[" + buildTypeName(attr.typeSymbol, true) + "] }"
case (attr: AttributeDecl, Single) =>
"lazy val " + makeParamName(buildParam(attr).name, true) + " = " +
wrapperName + "(" + quote(buildNodeName(attr, false)) + ").as[" + buildTypeName(attr.typeSymbol, true) + "]"
} collect { case (attr: AttributeDecl, cardinality: Cardinality) =>
val paramName = makeParamName(buildParam(attr).name, true)
val quotedNodeName = quote(buildNodeName(attr , false ))
val typeName = buildTypeName(attr.typeSymbol, true)
val isOptional = cardinality == Optional

( getterDeclaration(paramName, wrapperName, quotedNodeName, typeName, isOptional)
, setterDeclaration(paramName, wrapperName, quotedNodeName, typeName, isOptional))


//case (attr: AttributeDecl, Optional) =>
// "lazy val " + makeParamName(buildParam(attr).name, true) + " = " +
// wrapperName + ".get(" + quote(buildNodeName(attr, false)) + ") map { _.as[" + buildTypeName(attr.typeSymbol, true) + "] }"
//case (attr: AttributeDecl, Single) =>
// "lazy val " + makeParamName(buildParam(attr).name, true) + " = " +
// wrapperName + "(" + quote(buildNodeName(attr, false)) + ").as[" + buildTypeName(attr.typeSymbol, true) + "]"
}
}

def generateAccessors(params: List[Param], splits: List[SequenceDecl]) = params flatMap {
def generateAccessors(params: List[Param], splits: List[SequenceDecl]): List[(String, Option[String])] = params flatMap {
case param@Param(_, _, ReferenceTypeSymbol(decl@ComplexTypeDecl(_, _, _, _, _, _, _, _)), _, _, _, _, _) if
compositorWrapper.contains(decl) &&
splits.contains(compositorWrapper(decl)) =>
Expand All @@ -942,8 +977,9 @@ object {localName} {{
}

val paramList = particles map { buildParam }
paramList map { p =>
"lazy val " + makeParamName(p.name, false) + " = " + wrapperName + "." + makeParamName(p.name, false)
paramList map { p => val paramName = makeParamName(p.name, false)
( s"$lazyValOrDef $paramName = $wrapperName.$paramName"
, if (config.generateMutable) Some("/*TBD: setter */") else None)
}
case _ => Nil
}
Expand Down
8 changes: 5 additions & 3 deletions cli/src/main/scala/scalaxb/compiler/xsd/Params.scala
Expand Up @@ -83,10 +83,12 @@ trait Params extends Lookup {
case _ => false
})

def toTraitScalaCode: String = toParamName + ": " + typeName
def toTraitScalaCode(doMutable: Boolean): String = s"${if (doMutable) "var " else ""}$toParamName: $typeName"

def toScalaCode: String =
toTraitScalaCode + (cardinality match {
def toScalaCode_possiblyMutable: String = toScalaCode(config.generateMutable)

def toScalaCode(doMutable: Boolean): String =
toTraitScalaCode(doMutable) + (cardinality match {
case Single if typeSymbol == XsLongAttribute => " = Map()"
case Optional => " = None"
case Multiple => " = Nil"
Expand Down
21 changes: 21 additions & 0 deletions integration/src/test/resources/anotherSimpleSchema.xsd
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns="http://simple/main"
targetNamespace="http://simple/main"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">

<xsd:element name="aElem" type="AElem"/>

<xsd:complexType name="AElem">
<xsd:sequence>
<xsd:element name="bElem" type="BElem" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="dummy" type="xsd:string" use="optional" default="foo"/>
</xsd:complexType>

<xsd:complexType name="BElem">
<xsd:sequence>
<xsd:element name="cElem" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
67 changes: 67 additions & 0 deletions integration/src/test/scala/MutableTest.scala
@@ -0,0 +1,67 @@
import scalaxb.compiler.Config
import scalaxb.compiler.ConfigEntry._

object MutableTest extends TestBase {
val pkgname = "mutabletest"
val ns = "http://simple/main"

val schema = resource("anotherSimpleSchema.xsd")
val targetXml =
(<n:aElem xmlns:n={ns}>
<n:bElem>
<n:cElem>Foo</n:cElem>
</n:bElem>
<n:bElem>
<n:cElem>Bar</n:cElem>
</n:bElem>
<n:bElem>
<n:cElem>Boo</n:cElem>
</n:bElem>
</n:aElem>).toString.replace("\n", "")

val setup = s"""
import $pkgname._

val elem = scalaxb.fromXML[AElem]($targetXml)
"""

def genXml(elemName: String) = s"""
scalaxb.toXML(
obj = $elemName
, namespace = Some("$ns")
, elementLabel = Some("aElem")
, scope = scalaxb.toScope(Some("n") -> "$ns")
, typeAttribute = false
)
"""


lazy val generated = module.processFiles(
List(schema),
Config.default
.update(PackageNames(Map(None -> Some(pkgname))))
.update(Outdir(tmp))
.update(GenerateMutable)
.remove(VarArg) // VarArg is removed automatically in the command line app
)

"It should be possible to modify the nodes" in repl(generated)(s"""
$setup

elem.bElem = Nil
${genXml("elem")}.toString
""",
"""<n:aElem xmlns:n="http://simple/main"/>"""
)

"It should be possible to modify the attributes" in repl(generated)(s"""
$setup

elem.dummy = "bar"
elem.bElem = Nil
${genXml("elem")}.toString
""",
"""<n:aElem dummy="bar" xmlns:n="http://simple/main"/>"""
)

}
4 changes: 4 additions & 0 deletions integration/src/test/scala/TestBase.scala
Expand Up @@ -11,4 +11,8 @@ trait TestBase extends Specification with CompilerMatcher with matcher.FileMatch

if (tmp.exists) deleteAll(tmp)
tmp.mkdirs() // you need this for copyFileFromResource

/** Compile the `generated` files, execute `replLines` in the REPL, check whether the result is `expectedResult`.*/
def repl(generated: Seq[File])(replLines: String, expectedResult: String) =
(Seq(replLines), generated) must evaluateTo(expectedResult, "./tmp")
}

0 comments on commit c42cc11

Please sign in to comment.