Skip to content
Snippets Groups Projects
Commit 9184fac0 authored by Chrissi's avatar Chrissi
Browse files

Refactoring

+ Erweiterung für Einlesen von EMF Modellen für RSYNC
parent 2f58e066
No related branches found
No related tags found
No related merge requests found
Showing
with 231 additions and 129 deletions
......@@ -47,53 +47,48 @@ class Generator(config: GeneratorConfig) {
sModels = sModels + ((new SModelGenerator convert (ec._2.pkg, ec._1)) -> ec._2)
})
sModels.foreach(sm => {
/*general stuff, that should always be created */
//create relational compartments (are only write out if not rolesync)
new SModelRelationalCompartmentPreparationService prepareModel (sm._1)
//create the new classes for rsum and rsync
if (config.create == Creation.rolesync) {
sModels.foreach(sm => {
//instance generation for rsync
if (sm._2.obj != null) {
new SModelSyncInstanceService prepareModel (sm._1, sm._2)
}
new SModelAllGetterSetterPreparationService prepareModel (sm._1)
//add getter and setter for all methods
new SModelGetterSetterAllPreparationService prepareModel (sm._1)
//create the EMF Ecore reading models
new SModelSUMReadEMFService prepareModel (sm._1, config)
//add sync stuff
new SModelOnlySyncService prepareModel (sm._1)
//add ui provider stuff
new SModelSyncUiPreparationService prepareModel (sm._1, config.getModelConfigFromEcore(sm._1.getSourceName))
})
}
if (config.create == Creation.rolesum) {
sModels.foreach(sm => {
//create core stuff
new SModelSUMCorePreparationService prepareModel (sm._1)
//remove references
new SModelSUMRemoveRefService prepareModel (sm._1)
//create view and query stuff
new SModelSUMPreparationService prepareModel (sm._1)
})
}
} else {
//add basic setter and getter
new SModelGetterSetterAttrPreparationService prepareModel (sm._1)
if (config.create == Creation.rolecomb) {
sModels.foreach(sm => {
//create core stuff
new SModelSUMCorePreparationService prepareModel (sm._1)
//add sync stuff
new SModelOnlySyncService prepareModel (sm._1)
}
//create the EMF Ecore reading models
new SModelSUMReadEMFService prepareModel (sm._1)
new SModelSUMReadEMFService prepareModel (sm._1, config)
//instance generation for combi means rsum
if (sm._2.obj != null) {
new SModelCombiInstanceService prepareModel (sm._1, sm._2)
}
else
{
//add ref methods
new SModelCombiRefMethodService prepareModel (sm._1)
//remove references
new SModelSUMRemoveRefService prepareModel (sm._1)
}
//create view and query stuff
new SModelSUMPreparationService prepareModel (sm._1)
})
}
})
// write the model and create the JAR
println("... Writing model")
......
......@@ -18,8 +18,6 @@ class SModelCombiRefMethodService {
*/
def prepareModel(sModel: SModel): Unit = {
val refMethodVisitor = new ReferenceMethodCreationVisitor
//order is important !!!!
sModel.accept(refMethodVisitor)
}
}
\ No newline at end of file
......@@ -3,15 +3,13 @@ package org.rosi_project.model_sync.generator
import org.rosi_project.model_sync.generator.acr_model.SModel
import org.rosi_project.model_sync.generator.sync.GetterSetterGeneratingVisitor
/** Simple service to perform all the necessary adaptions of an [[SModel]] to make applicable for
* synchronization.
/** Simple service that adds getter and setter for Attributes and References.
*
* @author Rico Bergmann
* @author Christopher Werner
*/
class SModelAllGetterSetterPreparationService {
class SModelGetterSetterAllPreparationService {
/** Augments a [[SModel]] with the necessary methods and statements to make it usable in a
* synchronization context.
/** Adds getter and setter for Attributes and References.
*
* @param sModel the model to augment
*/
......
package org.rosi_project.model_sync.generator
import org.rosi_project.model_sync.generator.sync.BasicTypeGetterSetterGeneratingVisitor
import org.rosi_project.model_sync.generator.acr_model.SModel
class SModelGetterSetterAttrPreparationService {
/** Adds getter and setter for Attributes.
*
* @param sModel the model to augment
*/
def prepareModel(sModel: SModel): Unit = {
val getterSetterVisitor = new BasicTypeGetterSetterGeneratingVisitor
sModel.accept(getterSetterVisitor)
}
}
\ No newline at end of file
......@@ -3,24 +3,18 @@ package org.rosi_project.model_sync.generator
import org.rosi_project.model_sync.generator.acr_model.SModel
import org.rosi_project.model_sync.generator.sync._
/** Simple service to perform all the necessary adaptions of an [[SModel]] to make applicable for
* single underlying model.
/** Simple service to create relational compartments for each relation.
*
* @author Christopher Werner
*/
class SModelSUMCorePreparationService {
class SModelRelationalCompartmentPreparationService {
/** Augments a [[SModel]] with the necessary methods and statements to make it usable in a
* single underlying model context.
/** Create relational compartments for each relation.
*
* @param sModel the model to augment
*/
def prepareModel(sModel: SModel): Unit = {
val getterSetterVisitor = new BasicTypeGetterSetterGeneratingVisitor
val relationCompartmentVisitor = new RelationCompartmentGeneratingVisitor
//order is important !!!!
sModel.accept(getterSetterVisitor)
sModel.accept(relationCompartmentVisitor)
}
}
\ No newline at end of file
......@@ -13,8 +13,8 @@ class SModelSUMReadEMFService {
*
* @param sModel the model to augment
*/
def prepareModel(sModel: SModel): Unit = {
val sumModelReadingVisitor = new SumModelReadingVisitor
def prepareModel(sModel: SModel, config: GeneratorConfig): Unit = {
val sumModelReadingVisitor = new SumModelReadingVisitor(config)
sModel.accept(sumModelReadingVisitor)
}
}
\ No newline at end of file
......@@ -87,10 +87,14 @@ class SClassWriter(val modelClass: SClass) {
* @return the `String` representation of `m`
*/
protected def stringifyMethod(m: SMethod): String = {
s"""${if (m.overrides) "override " else ""}${getVisibilityString(m.getVisibility)}def ${m.getName}(${m.params.map(param => s"${param.getName}: ${param.getDeepTypeName}").mkString(", ")}): ${m.getResultType} = {
var result = s"""${if (m.overrides) "override " else ""}${getVisibilityString(m.getVisibility)}def ${m.getName}(${m.params.map(param => s"${param.getName}: ${param.getDeepTypeName}").mkString(", ")}): ${m.getResultType} """
if (!m.implementation.isEmpty) {
result = result + s""" = {
| ${m.implementation.map(_.getContent).mkString("\n")}
|}
""".stripMargin
"""
}
result.stripMargin
}
/**
......
......@@ -9,6 +9,7 @@ import org.rosi_project.model_sync.generator.env.{FilesCompiler, JarPackager}
import scala.reflect.io.{Directory, File, Path}
import org.rosi_project.model_sync.generator.GeneratorConfig
import org.rosi_project.model_sync.generator.Creation
/** The `FSWriter` writes a [[SModel]] as a single compiled ''JAR'' file to the File System.
*
......@@ -60,10 +61,12 @@ class SModelFSWriter(
override def visit(sModel: SModel): Unit = {
sModel.getModelEnums.foreach(writeEnum(_))
sModel.getModelClasses.foreach(writeClass(_))
sModel.getProviderClasses.foreach(writeClass(_))
if (generatorConfig.create != Creation.rolesync) {
sModel.getJoinClasses.foreach(writeClass(_))
sModel.getRelationalCompartments.foreach(writeClass(_))
sModel.getViewCompartments.foreach(writeClass(_))
sModel.getProviderClasses.foreach(writeClass(_))
}
//println(s"... Wrote files (sources) $sFilesToCompile")
println("... Starting compilation")
......
......@@ -5,20 +5,22 @@ import org.rosi_project.model_sync.generator.acr_model._
class BasicTypeGetterSetterGeneratingVisitor extends SModelVisitor {
override def visit(sModel: SModel): Unit = {
// pass
}
override def visit(sClass: SClass): Unit = {
sModel.getModelClasses.foreach(cls => {
//add all getter and setter for each normal attribute
sClass.getAttributes.foreach(attr => {
cls.getAttributes.foreach(attr => {
attr.setVisibility(MethodVisibility.protectedVis)
val getter = new SGetter(attr)
val setter = new SSetter(attr)
sClass.addMethod(getter)
sClass.addMethod(setter)
cls.addMethod(getter)
cls.addMethod(setter)
})
})
}
override def visit(sClass: SClass): Unit = {
// pass
}
override def visit(sAttr: SAttribute): Unit = {
// pass
}
......
......@@ -3,32 +3,35 @@ package org.rosi_project.model_sync.generator.sync
import org.rosi_project.model_sync.generator.acr_model._
import org.rosi_project.model_sync.generator.acr_model.types.GenericSequence
/** Service to extend [[SClass SClasses]] with getter and setter methods for all attributes.
/**
* Service to extend [[SClass SClasses]] with getter and setter methods for all attributes.
*
* @author Rico Bergmann
*/
class GetterSetterGeneratingVisitor extends SModelVisitor {
override def visit(sModel: SModel): Unit = {
// pass
}
override def visit(sClass: SClass): Unit = {
sClass.getStructuralFeatures.foreach(attr => {
sModel.getModelClasses.foreach(cls => {
//add all getter and setter for all structural features
cls.getStructuralFeatures.foreach(attr => {
attr.setVisibility(MethodVisibility.protectedVis)
val getter = new SGetter(attr)
sClass.addMethod(getter)
cls.addMethod(getter)
if (attr.getTypeElement.isInstanceOf[GenericSequence]) {
val adder = new SSetterAdd(attr, attr.getTypeElement.asInstanceOf[GenericSequence].typeParam)
sClass.addMethod(adder)
cls.addMethod(adder)
val remover = new SSetterRemove(attr, attr.getTypeElement.asInstanceOf[GenericSequence].typeParam)
sClass.addMethod(remover)
cls.addMethod(remover)
} else {
val setter = new SSetter(attr)
sClass.addMethod(setter)
cls.addMethod(setter)
}
})
})
}
override def visit(sClass: SClass): Unit = {
// pass
}
override def visit(sAttr: SAttribute): Unit = {
......
......@@ -79,12 +79,6 @@ class InstanceCombiGenerator(val clsins: ClassAndInstance) extends SModelVisitor
})
})
//remove references
sModel.getModelClasses.foreach(cls => {
//remove all references from the class
cls.setReferences(null)
})
//construction of instances
lines.foreach(l => {
example.augmentConstructor(new SMethodStatement(l.getLine(), Set(l.usedType)))
......@@ -98,9 +92,6 @@ class InstanceCombiGenerator(val clsins: ClassAndInstance) extends SModelVisitor
example.addParent(PredefTypes.App)
sModel.addProviderClass(example)
//println("++++++++++++++++++++++++++++++++++++++++++++++")
// pass
}
def setValues(eo: EObject, lines: Seq[InstanceLine]): String = {
......
......@@ -5,17 +5,23 @@ import org.rosi_project.model_sync.generator.PackageNames
import org.rosi_project.model_sync.generator.acr_model.types.PredefEcoreTypes
import org.rosi_project.model_sync.generator.acr_model.types.PredefTypes
import org.rosi_project.model_sync.generator.acr_model.types.GenericSequence
import org.rosi_project.model_sync.generator.GeneratorConfig
import org.rosi_project.model_sync.generator.Creation
/**
* Read EMF and Ecore models.
*
* @author Christopher Werner
*/
class SumModelReadingVisitor() extends SModelVisitor {
class SumModelReadingVisitor(config: GeneratorConfig) extends SModelVisitor {
override def visit(sModel: SModel): Unit = {
//println("++++++++++++++++++++++++++++++++++++++++++++++")
val creator = new SClass("Creation" + sModel.getName, PackageNames.creationPkgName)
val creatorSum = new SClass("Creation" + sModel.getName + "Sum", PackageNames.creationPkgName)
val creatorSync = new SClass("Creation" + sModel.getName + "Sync", PackageNames.creationPkgName)
val creatorInterface = new SClass("ICreation" + sModel.getName, PackageNames.creationPkgName, SClassType.normalTrait)
creatorSum.addParent(creatorInterface)
creatorSync.addParent(creatorInterface)
val loader = new SClass("Loader" + sModel.getName, PackageNames.creationPkgName)
val loadEcore = new SMethod(
......@@ -35,10 +41,10 @@ class SumModelReadingVisitor() extends SModelVisitor {
SMethodStatement(content = s"return ressourceModel.getContents().get(0)")))
loader.addMethod(loadEcore)
val createModelInstance = new SMethod(
val createModelInstanceSum = new SMethod(
name = s"create${sModel.getName}Instance",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("obj", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creator)),
params = Seq(SMethodParameter("obj", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creatorInterface)),
implementation = Seq(
SMethodStatement(content = s"createObj(obj, creator)"),
SMethodStatement(content = s"obj.eAllContents().asScala.foreach(o => {", usedTypes = Set(PredefTypes.ScalaConverter)),
......@@ -48,12 +54,12 @@ class SumModelReadingVisitor() extends SModelVisitor {
SMethodStatement(content = s"obj.eAllContents().asScala.foreach(o => {"),
SMethodStatement(content = s"createRef(o, creator)"),
SMethodStatement(content = s"})")))
loader.addMethod(createModelInstance)
loader.addMethod(createModelInstanceSum)
val createObj = new SMethod(
name = "createObj",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("obj", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creator)),
params = Seq(SMethodParameter("obj", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creatorInterface)),
implementation = Seq.empty)
var createObjImpl = Seq(SMethodStatement(content = s"var objName = obj.eClass.getName"),
SMethodStatement(content = s"objName match {"))
......@@ -61,7 +67,7 @@ class SumModelReadingVisitor() extends SModelVisitor {
val createRef = new SMethod(
name = "createRef",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("o1", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creator)),
params = Seq(SMethodParameter("o1", PredefEcoreTypes.EcoreObject), SMethodParameter("creator", creatorInterface)),
implementation = Seq.empty)
var createRefImpl = Seq(SMethodStatement(content = s"o1.eClass().getEAllReferences.forEach(sf => {"),
SMethodStatement(content = s"val sfName = sf.getName"),
......@@ -69,16 +75,37 @@ class SumModelReadingVisitor() extends SModelVisitor {
SMethodStatement(content = s"val o1Name = o1.eClass().getName"),
SMethodStatement(content = s"val o2Name = o2.eClass().getName"))
creator.augmentConstructor(SMethodStatement(content = s"var mapping: Map[EObject, Object] = Map.empty", usedTypes = Set(PredefEcoreTypes.EcoreObject)))
creatorSum.augmentConstructor(SMethodStatement(content = s"var mapping: Map[EObject, Object] = Map.empty", usedTypes = Set(PredefEcoreTypes.EcoreObject)))
creatorSync.augmentConstructor(SMethodStatement(content = s"var mapping: Map[EObject, Object] = Map.empty", usedTypes = Set(PredefEcoreTypes.EcoreObject)))
sModel.getModelClasses.filter(c => !c.isAbstract && !c.isInterface).foreach(c => {
val method = new SMethod(
//method for trait
val methodTrait = new SMethod(
name = s"create${c.getName}",
result = PredefTypes.Unit,
params = c.getAttributeConstructorParameters :+ SMethodParameter("id", PredefEcoreTypes.EcoreObject),
implementation = Seq.empty)
creatorInterface.addMethod(methodTrait)
//method for sync
val methodSum = new SMethod(
name = s"create${c.getName}",
result = PredefTypes.Unit,
params = c.getAttributeConstructorParameters :+ SMethodParameter("id", PredefEcoreTypes.EcoreObject),
implementation = Seq(
SMethodStatement(content = s"mapping += (id -> new ${c.getName}(${c.getAttributeConstructorParameters.map(m => m.getName).mkString(", ")}))", usedTypes = Set(c))))
creator.addMethod(method)
SMethodStatement(content = s"mapping += (id -> new ${c.getName}(${c.getAttributeConstructorParameters.map(m => m.getName).mkString(", ")}))", usedTypes = Set(c))),
overrides = true)
creatorSum.addMethod(methodSum)
//method for sync
val methodSync = new SMethod(
name = s"create${c.getName}",
result = PredefTypes.Unit,
params = c.getAttributeConstructorParameters :+ SMethodParameter("id", PredefEcoreTypes.EcoreObject),
implementation = Seq(
SMethodStatement(content = s"mapping += (id -> new ${c.getName}(${c.getAllConstructorParameters.map(m => if(m.getType.isInstanceOf[GenericSequence]) "Set.empty" else if (m.getType.isInstanceOf[SClass]) "null" else m.getName).mkString(", ")}))", usedTypes = Set(c))), //TODO
overrides = true)
creatorSync.addMethod(methodSync)
var s = c.getAttributeConstructorParameters.map(m => s"""obj.eGet(obj.eClass().getEStructuralFeature("${m.getName}"))${if (m.getTypeName == "Boolean") ".asInstanceOf[Boolean]" else ".toString()"} """).mkString(", ")
if (s.isEmpty()) {
......@@ -91,20 +118,53 @@ class SumModelReadingVisitor() extends SModelVisitor {
sModel.getRelationalCompartments.foreach(r => {
var rc = r.asInstanceOf[SRelationalCompartmentClass]
val method = new SMethod(
//method for trait
val methodTrait = new SMethod(
name = s"create${r.getName}",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("s", PredefEcoreTypes.EcoreObject), SMethodParameter("t", PredefEcoreTypes.EcoreObject)),
implementation = Seq.empty)
creatorInterface.addMethod(methodTrait)
//sum method
val methodSum = new SMethod(
name = s"create${r.getName}",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("s", PredefEcoreTypes.EcoreObject), SMethodParameter("t", PredefEcoreTypes.EcoreObject)),
implementation = Seq(
SMethodStatement(content = s"val s1 = mapping.get(s).get.asInstanceOf[${rc.sClass.getName}]"),
SMethodStatement(content = s"val t1 = mapping.get(t).get.asInstanceOf[${rc.tClass.getName}]"),
SMethodStatement(content = s"(new ${rc.getName}(s1, t1)).initialize()", usedTypes = Set(rc, rc.sClass, rc.tClass))))
SMethodStatement(content = s"(new ${rc.getName}(s1, t1)).initialize()", usedTypes = Set(rc, rc.sClass, rc.tClass))),
overrides = true)
creatorSum.addMethod(methodSum)
//sync method
val methodSync = new SMethod(
name = s"create${r.getName}",
result = PredefTypes.Unit,
params = Seq(SMethodParameter("s", PredefEcoreTypes.EcoreObject), SMethodParameter("t", PredefEcoreTypes.EcoreObject)),
implementation = Seq.empty,
overrides = true)
var implMethodSync = Seq(SMethodStatement(content = s"val s1 = mapping.get(s).get.asInstanceOf[${rc.sClass.getName}]", usedTypes = Set(rc, rc.sClass, rc.tClass)),
SMethodStatement(content = s"val t1 = mapping.get(t).get.asInstanceOf[${rc.tClass.getName}]"))
if (rc.connectedRef.getTypeElement.isInstanceOf[GenericSequence]) {
//add -> add method
implMethodSync = implMethodSync :+ SMethodStatement(content = s"s1.add${rc.connectedRef.getName.capitalize}(t1)")
} else {
//add -> set method
implMethodSync = implMethodSync :+ SMethodStatement(content = s"s1.set${rc.connectedRef.getName.capitalize}(t1)")
}
creator.addMethod(method)
if (rc.connectedRef.hasOpposite) {
if (rc.connectedRef.oppositeRef.getTypeElement.isInstanceOf[GenericSequence]) {
//add -> add method
implMethodSync = implMethodSync :+ SMethodStatement(content = s"t1.add${rc.connectedRef.oppositeRef.getName.capitalize}(s1)")
} else {
//add -> set method
implMethodSync = implMethodSync :+ SMethodStatement(content = s"t1.set${rc.connectedRef.oppositeRef.getName.capitalize}(s1)")
}
}
methodSync.implementation = implMethodSync
creatorSync.addMethod(methodSync)
createRefImpl = createRefImpl :+ SMethodStatement(content = s"""if (o1Name.contains("${rc.sClass.getName}") && sfName == "${rc.connectedRef.getName}" && o2Name.contains("${rc.tClass.getName}")) {""")
createRefImpl = createRefImpl :+ SMethodStatement(content = s"creator.create${rc.getName}(o1, o2)")
......@@ -121,7 +181,12 @@ class SumModelReadingVisitor() extends SModelVisitor {
loader.addMethod(createRef)
//add the new classes as model classes
sModel.addProviderClass(creator)
if (config.create == Creation.rolesync) {
sModel.addProviderClass(creatorSync)
} else {
sModel.addProviderClass(creatorSum)
}
sModel.addProviderClass(creatorInterface)
sModel.addProviderClass(loader)
}
......
......@@ -17,15 +17,19 @@ import org.rosi_project.model_sync.generator.acr_model._
class SyncEnhancingVisitor() extends SModelVisitor {
override def visit(sModel: SModel): Unit = {
// pass
sModel.getModelClasses.foreach(cls => {
//TODO: do not have in mind trait extends abstract extends trait hierarchy
if (cls.isAbsoluteRootClassOrInterface) {
cls.addParent(PredefSyncTypes.PLAYER_SYNC_STYPE)
}
cls.getMethods.foreach(m => {
extractSetterAttr(m).foreach(attr => m.augmentImplementation(SMethodStatement(s"+this $attr")))
})
})
}
override def visit(sClass: SClass): Unit = {
//TODO: do not have in mind trait extends abstract extends trait hierarchy
if (sClass.isAbsoluteRootClassOrInterface && !sClass.isInstanceOf[SRelationalCompartmentClass]) {
//if (sClass.isRootClass && !sClass.isInterface && !sClass.isInstanceOf[SRelationalCompartmentClass]) {
sClass.addParent(PredefSyncTypes.PLAYER_SYNC_STYPE)
}
// pass
}
override def visit(sAttr: SAttribute): Unit = {
......@@ -37,7 +41,7 @@ class SyncEnhancingVisitor() extends SModelVisitor {
}
override def visit(sMethod: SMethod): Unit = {
extractSetterAttr(sMethod).foreach(attr => sMethod.augmentImplementation(SMethodStatement(s"+this $attr")))
// pass
}
override def visit(sType: SType): Unit = {
......
......@@ -13,8 +13,8 @@ object ApplicationTest extends App {
//runTestAML(Creation.rolesum)
//TTC Case examples
runTTC2019(Creation.rolecomb)
//runTTC2019(Creation.rolesync)
//runTTC2019(Creation.rolecomb)
runTTC2019(Creation.rolesync)
//runTTCLive2019(Creation.rolesync)
//Run all tests
......
......@@ -8,9 +8,17 @@ import org.rosi_project.model_sync.generator.acr_model._
*/
class SyncUiVisitor(val modelCfg: ModelConfig) extends SModelVisitor {
private var additionalSyncClasses: Seq[SClass] = Seq.empty
override def visit(sModel: SModel): Unit = {
var additionalSyncClasses: Seq[SClass] = Seq.empty
sModel.getModelClasses.foreach(cls => {
if (!cls.isAbstract && !cls.isInterface) {
additionalSyncClasses = additionalSyncClasses :+ new ConstructorTemplate(cls)
}
cls.getStructuralFeatures.foreach(struc => {
additionalSyncClasses = additionalSyncClasses :+ new ModifierTemplate(cls, struc)
})
})
additionalSyncClasses.foreach(sModel.addProviderClass)
modelCfg.init.nested.foreach(_.foreach(model => sModel.addProviderClass(new InitialModelTemplate(model))))
......@@ -20,12 +28,7 @@ class SyncUiVisitor(val modelCfg: ModelConfig) extends SModelVisitor {
}
override def visit(sClass: SClass): Unit = {
if (!sClass.isAbstract && !sClass.isInterface) {
additionalSyncClasses = additionalSyncClasses :+ new ConstructorTemplate(sClass)
}
sClass.getStructuralFeatures.foreach(struc => {
additionalSyncClasses = additionalSyncClasses :+ new ModifierTemplate(sClass, struc)
})
// pass
}
override def visit(sAttr: SAttribute): Unit = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment