From 17ea5437cc67a7ce0f79bed5b8be8807341b9bfd Mon Sep 17 00:00:00 2001
From: Rico Bergmann <rico.bergmann1@tu-dresden.de>
Date: Tue, 18 Sep 2018 09:26:00 +0200
Subject: [PATCH] [WIP] Add converter from EMF to SModel
Still WIP as not all features are available yet (e.g. some data type
mappings are still missing)
---
.gitignore | 1 +
.gitmodules | 6 ++
assets/ttc17.ecore | 9 +++
build.sbt | 34 ++++++-----
lib/ModelSyncProvider | 1 +
project/plugins.sbt | 1 +
.../model_sync/generator/Generator.scala | 39 ++++++++----
.../model_sync/generator/acr/SClass.scala | 10 +++-
.../conversion/EmfTypeTranslator.scala | 17 ++++++
.../conversion/SClassConverter.scala | 59 +++++++++++++++++++
.../conversion/SModelGenerator.scala | 7 ++-
.../conversion/SModelGeneratorTest.scala | 21 +++++++
.../UnconvertibleEmfException.scala | 12 ++++
.../generator/conversion/package.scala | 20 +++++++
.../model_sync/generator/io/EmfImporter.scala | 8 +++
.../generator/io/SClassWriter.scala | 2 +
.../generator/io/SModelFSWriter.scala | 3 -
.../generator/sync_model/SModel.scala | 2 +
.../generator/sync_model/STypeRegistry.scala | 7 ++-
.../sync_model/SyncEnhancingVisitor.scala | 18 +++---
20 files changed, 230 insertions(+), 47 deletions(-)
create mode 100644 .gitmodules
create mode 100644 assets/ttc17.ecore
create mode 160000 lib/ModelSyncProvider
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala
create mode 100644 src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala
diff --git a/.gitignore b/.gitignore
index b871d0c..e6431cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -174,3 +174,4 @@ local.properties
.idea/
.classpath
.project
+output/
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d32bc54
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "lib/ModelSyncProvider"]
+ path = lib/ModelSyncProvider
+ url = https://git-st.inf.tu-dresden.de/cwerner/role_model_synchronization_provider.git
+[submodule "lib/SCROLL"]
+ path = lib/SCROLL
+ url = https://github.com/portux/SCROLL.git
diff --git a/assets/ttc17.ecore b/assets/ttc17.ecore
new file mode 100644
index 0000000..7f2edd4
--- /dev/null
+++ b/assets/ttc17.ecore
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="ttc17" nsURI="http://www.example.org/ttc17" nsPrefix="ttc17">
+ <eClassifiers xsi:type="ecore:EClass" name="Person">
+ <eStructuralFeatures xsi:type="ecore:EAttribute" name="fullName" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
+ </eClassifiers>
+ <eClassifiers xsi:type="ecore:EClass" name="Male" eSuperTypes="#//Person"/>
+ <eClassifiers xsi:type="ecore:EClass" name="Female" eSuperTypes="#//Person"/>
+</ecore:EPackage>
diff --git a/build.sbt b/build.sbt
index d6e9bab..a2f6a46 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,21 +1,23 @@
+import sbt.Keys.{libraryDependencies, scalacOptions, version}
+
val emfcommonVersion = "2.12.0"
val emfecoreVersion = "2.12.0"
val scrollVersion = "1.6"
-name := "CodeGenerator"
-
-version := "0.1"
-
-scalaVersion := "2.12.6"
-
-libraryDependencies ++= Seq(
- "org.scala-lang" % "scala-reflect" % scalaVersion.value,
- "org.scala-lang" % "scala-compiler" % scalaVersion.value,
- "org.eclipse.emf" % "org.eclipse.emf.common" % emfcommonVersion,
- "org.eclipse.emf" % "org.eclipse.emf.ecore" % emfecoreVersion,
- "com.github.max-leuthaeuser" %% "scroll" % scrollVersion
-)
+val syncProvider = RootProject(file("lib/ModelSyncProvider"))
-scalacOptions ++= Seq(
- "-language:implicitConversions"
-)
+lazy val generator = (project in file("."))
+ .settings(
+ name := "CodeGenerator",
+ version := "0.1",
+ scalaVersion := "2.12.6",
+ libraryDependencies ++= Seq(
+ "org.scala-lang" % "scala-reflect" % scalaVersion.value,
+ "org.scala-lang" % "scala-compiler" % scalaVersion.value,
+ "org.eclipse.emf" % "org.eclipse.emf.common" % emfcommonVersion,
+ "org.eclipse.emf" % "org.eclipse.emf.ecore" % emfecoreVersion,
+ ),
+ scalacOptions ++= Seq(
+ "-language:implicitConversions"
+ )
+ ).dependsOn(syncProvider)
diff --git a/lib/ModelSyncProvider b/lib/ModelSyncProvider
new file mode 160000
index 0000000..ace8bdf
--- /dev/null
+++ b/lib/ModelSyncProvider
@@ -0,0 +1 @@
+Subproject commit ace8bdf5799dd89bd66d01782237123172bb0ec0
diff --git a/project/plugins.sbt b/project/plugins.sbt
index a4e6fcc..45a6f02 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1 +1,2 @@
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
+addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "1.0.0")
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala b/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala
index 14c46b7..409478b 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/Generator.scala
@@ -1,7 +1,14 @@
package org.rosi_project.model_sync.generator
import org.eclipse.emf.ecore._
+import org.eclipse.emf.ecore.resource.Resource
import org.rosi_project.model_sync.generator.conversion.SModelGenerator
+import org.rosi_project.model_sync.generator.io.SModelFSWriter
+import org.rosi_project.model_sync.generator.io.SModelFSWriterTest.{model, personClass}
+import org.rosi_project.model_sync.generator.sync_model.{GetterSetterGeneratingVisitor, SModel, SyncEnhancingVisitor}
+import scroll.internal.ecore.ECoreImporter
+
+import scala.reflect.io.File
/**
@@ -11,23 +18,29 @@ object Generator extends App {
val ecoreModel = loadEcore()
- SModelGenerator.convert(ecoreModel)
+ val sModel = SModelGenerator.convert(ecoreModel)
- def loadEcore(): EPackage = {
- val factory: EcoreFactory = EcoreFactory.eINSTANCE
- val ecorePackage: EcorePackage = EcorePackage.eINSTANCE
- val ePackage: EPackage = factory.createEPackage()
+ prepareModel(sModel)
+
+ sModel.accept(new SModelFSWriter(workingDir = File("output/raw"), keepClassFiles = true))
- val ePerson = factory.createEClass()
- ePerson.setName("Person")
- ePackage.getEClassifiers().add(ePerson)
+ def loadEcore(): EPackage = {
+ val importer = new ECoreImporter {
+ def lm(): Resource = loadModel()
+ }
+ importer.path = "assets/ttc17.ecore"
+ val res = importer.lm()
+ println(s"We done: $res")
+
+ res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).map((p: EObject) => p.asInstanceOf[EPackage]).orNull
+ }
- val eName: EAttribute = factory.createEAttribute()
- eName.setName("name")
- eName.setEType(ecorePackage.getEString)
- ePerson.getEStructuralFeatures.add(eName)
+ def prepareModel(sModel: SModel): Unit = {
+ val getterSetterVisitor = new GetterSetterGeneratingVisitor
+ val syncNotificationVisitor = new SyncEnhancingVisitor
- ePackage
+ sModel.accept(getterSetterVisitor)
+ sModel.accept(syncNotificationVisitor)
}
}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala b/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala
index cb9a73a..e778250 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/acr/SClass.scala
@@ -7,12 +7,16 @@ import org.rosi_project.model_sync.generator.sync_model.SModelVisitor
* @author Rico Bergmann
*/
class SClass(val name: String,
- val attributes: Seq[SAttribute],
+ var attributes: Seq[SAttribute] = Seq.empty,
val sPackage: String = "",
var parent: STypedElement = null,
var methods: Seq[SMethod] = Seq.empty)
extends STypedElement {
+ private var constructorStatements: Seq[SMethodStatement] = Seq.empty
+
+ def getAdditionalConstructorStatements: Seq[SMethodStatement] = constructorStatements
+
def isDefaultPackage: Boolean = sPackage == ""
def isRootClass: Boolean = parent == null
@@ -50,8 +54,12 @@ class SClass(val name: String,
def addMethod(m: SMethod): Unit = methods = methods :+ m
+ def augmentConstructor(statement: SMethodStatement): Unit = constructorStatements = constructorStatements :+ statement
+
def setParent(parent: STypedElement): Unit = this.parent = parent
+ def setAttributes(attrs: Seq[SAttribute]): Unit = this.attributes = attrs
+
override def getName: String = name
override def getPackage: String = sPackage
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala
new file mode 100644
index 0000000..463f272
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/EmfTypeTranslator.scala
@@ -0,0 +1,17 @@
+package org.rosi_project.model_sync.generator.conversion
+
+import org.eclipse.emf.ecore.EDataType
+import org.rosi_project.model_sync.generator.acr.SType
+
+/**
+ * @author Rico Bergmann
+ */
+object EmfTypeTranslator {
+
+ private val typeMap: Map[String, SType] = Map(
+ "EString" -> SType("String")
+ )
+
+ def getSClassFromEmf(dataType: EDataType): Option[SType] = typeMap.get(dataType.getName)
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala
new file mode 100644
index 0000000..e56125f
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SClassConverter.scala
@@ -0,0 +1,59 @@
+package org.rosi_project.model_sync.generator.conversion
+
+import org.eclipse.emf.ecore.{EAttribute, EClass}
+import org.rosi_project.model_sync.generator.acr.{SAttribute, SClass, STypedElement}
+import org.rosi_project.model_sync.generator.sync_model.STypeRegistry
+
+/**
+ * @author Rico Bergmann
+ */
+class SClassConverter extends Converter[EClass, SClass] {
+
+ override def convert(source: EClass): SClass = {
+ var attrs: List[SAttribute] = List.empty
+
+ (source.getEAttributes: List[EAttribute]).foreach(eAttr => {
+ val attrType: STypedElement = STypeRegistry.query(eAttr.getEAttributeType.getName, eAttr.getEAttributeType.getEPackage.getName).getOrElse {
+ val newAttr = EmfTypeTranslator.getSClassFromEmf(eAttr.getEAttributeType).getOrElse(new SClass(eAttr.getEAttributeType.getName, sPackage = eAttr.getEAttributeType.getEPackage.getName))
+ STypeRegistry.addType(newAttr)
+ }
+ attrs ::= SAttribute(eAttr.getName, attrType)
+ })
+
+ val parents: List[EClass] = source.getESuperTypes.toArray(new Array[EClass](0)).toList
+
+ if (violatesSingleInheritance(parents)) {
+ throw new UnconvertibleEmfException(source.getEPackage, s"For class: $source")
+ }
+
+ val parent: STypedElement = parents.headOption.map((p: EClass) => {
+ STypeRegistry.queryForName(p.getName).getOrElse {
+ val parentSClass: SClass = new SClass(p.getName, sPackage = p.getEPackage.getName)
+ STypeRegistry.addType(parentSClass)
+ parentSClass
+ }
+ }).orNull
+
+
+ // TODO add methods to SClass
+
+ val currentClass: Option[STypedElement] = STypeRegistry.query(source.getName, source.getEPackage.getName)
+
+ currentClass.map {
+ case clazz: SClass =>
+ clazz.attributes = attrs
+ clazz.parent = parent
+ clazz
+ case sType =>
+ sys.error(s"sType should have been a class: $sType")
+ }.getOrElse {
+ val createdClass: SClass = new SClass(source.getName, attrs, source.getEPackage.getName, parent)
+ STypeRegistry.addType(createdClass)
+ createdClass
+ }
+
+ }
+
+ private def violatesSingleInheritance(parents: List[EClass]): Boolean = parents.length > 1
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala
index b87387f..170416c 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGenerator.scala
@@ -17,18 +17,21 @@ object SModelGenerator extends Converter[EPackage, SModel] {
val model = new SimpleSModel(packageName)
- contents.foreach{
+ contents.foreach {
case ec: EClass =>
+ model.addModelClass(new SClassConverter convert ec)
+
STypeRegistry.addType(SType(ec.getName, packageName))
println(s"Found class $ec")
case et: EGenericType=>
- STypeRegistry.addType(SType(et.eClass().getName, packageName))
+ //STypeRegistry.addType(SType(et.eClass().getName, packageName))
println(s"Found type $et")
case obj =>
println(s"Found something else: $obj")
}
println(s"The TypeRegistry:\n${STypeRegistry.allTypes}")
+ println(s"The model:\n$model")
model
}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala
new file mode 100644
index 0000000..fc8987d
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/SModelGeneratorTest.scala
@@ -0,0 +1,21 @@
+package org.rosi_project.model_sync.generator.conversion
+
+import org.eclipse.emf.ecore.{EObject, EPackage}
+import org.eclipse.emf.ecore.resource.Resource
+import scroll.internal.ecore.ECoreImporter
+
+/**
+ * @author Rico Bergmann
+ */
+object SModelGeneratorTest extends App {
+
+ val importer = new ECoreImporter {
+ def lm(): Resource = loadModel()
+ }
+ importer.path = "assets/ttc17.ecore"
+ val res = importer.lm()
+ println(s"We done: $res")
+
+ res.getContents.toArray(new Array[EObject](0)).toList.find(_.isInstanceOf[EPackage]).foreach(e => SModelGenerator.convert(e.asInstanceOf[EPackage]))
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala
new file mode 100644
index 0000000..86b2857
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/UnconvertibleEmfException.scala
@@ -0,0 +1,12 @@
+package org.rosi_project.model_sync.generator.conversion
+
+import org.eclipse.emf.ecore.EPackage
+
+/**
+ * @author Rico Bergmann
+ */
+class UnconvertibleEmfException(val model: EPackage, val message: String = "") extends RuntimeException(message) {
+
+ override def toString: String = s"UnconvertibleException: $model ${if (message.isEmpty) "" else s"($message)"}"
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala b/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala
new file mode 100644
index 0000000..436c3f6
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/conversion/package.scala
@@ -0,0 +1,20 @@
+package org.rosi_project.model_sync.generator
+
+import org.eclipse.emf.common.util.EList
+
+/**
+ * @author Rico Bergmann
+ */
+package object conversion {
+
+ implicit def elist2slist[T](elst: EList[T]): List[T] = {
+ if (elst == null) {
+ null
+ } else {
+ var res: List[T] = List()
+ elst.forEach(elem => res = res :+ elem)
+ res
+ }
+ }
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala
new file mode 100644
index 0000000..f1eb7ee
--- /dev/null
+++ b/src/main/scala/org/rosi_project/model_sync/generator/io/EmfImporter.scala
@@ -0,0 +1,8 @@
+package org.rosi_project.model_sync.generator.io
+
+/**
+ * @author Rico Bergmann
+ */
+class EmfImporter {
+
+}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala
index dcf1396..748a7e6 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SClassWriter.scala
@@ -19,6 +19,8 @@ class SClassWriter(val modelClass: SClass) {
|
|class $clazzFixture {
|
+ | ${modelClass.getAdditionalConstructorStatements.map(_.content).mkString("\n")}
+ |
| ${modelClass.methods.map(stringifyMethod).mkString("\n")}
|
|}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala
index 296252d..133c07a 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/io/SModelFSWriter.scala
@@ -21,9 +21,6 @@ class SModelFSWriter(
outputDir: Directory = File("output").toDirectory,
keepClassFiles: Boolean = false) extends SModelVisitor {
- // TODO enable different output dirs
- // TODO enable retaining the .scala files
-
workingDir.jfile.mkdirs()
outputDir.jfile.mkdirs()
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala
index 7c212dc..b2bd788 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SModel.scala
@@ -11,4 +11,6 @@ trait SModel extends SModelElement {
def getAllClasses: Set[SClass]
+ override def toString: String = s"SModel: pckg=$getPackageName, classes=$getAllClasses"
+
}
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala
index 16ede16..a0be35f 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/STypeRegistry.scala
@@ -11,8 +11,11 @@ object STypeRegistry {
registerDefaultTypes()
- def addType(theType: STypedElement): Unit = {
- registeredTypes += theType
+ def addType(theType: STypedElement): STypedElement = {
+ if (!registeredTypes.contains(theType)) {
+ registeredTypes += theType
+ }
+ theType
}
def query(name: String, sPackage: String): Option[STypedElement] = {
diff --git a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala
index 6f27ff1..8306d15 100644
--- a/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala
+++ b/src/main/scala/org/rosi_project/model_sync/generator/sync_model/SyncEnhancingVisitor.scala
@@ -1,6 +1,6 @@
package org.rosi_project.model_sync.generator.sync_model
import org.rosi_project.model_sync.generator.acr._
-import scroll.internal.Compartment
+import org.rosi_project.model_sync.sync.PlayerSync
/**
* @author Rico Bergmann
@@ -12,14 +12,12 @@ class SyncEnhancingVisitor extends SModelVisitor {
}
override def visit(sClass: SClass): Unit = {
- // TODO extend PlayerSync and call setup method in constructor
-
sClass.getInheritanceHierarchy.reverse.headOption.foreach{
- case root: SClass => root.setParent(SyncEnhancingVisitor.COMPARTMENT_STYPE)
+ case root: SClass => root.setParent(SyncEnhancingVisitor.PLAYER_SYNC_STYPE)
case tp => sys.error(s"May not enhance $tp as it is a type and not a class")
}
- println(s"Class hierarchy for $sClass : ${sClass.getInheritanceHierarchy}")
+ sClass.augmentConstructor(SyncEnhancingVisitor.PLAYER_SYNC_INIT)
}
@@ -37,7 +35,7 @@ class SyncEnhancingVisitor extends SModelVisitor {
private def extractSetterAttr(sMethod: SMethod): Option[String] = {
sMethod.name match {
- case SyncEnhancingVisitor.SetterRegex(attrName) =>
+ case SyncEnhancingVisitor.Setter(attrName) =>
Option(attrName)
case _ =>
None
@@ -49,11 +47,11 @@ class SyncEnhancingVisitor extends SModelVisitor {
object SyncEnhancingVisitor {
- private val SetterRegex = """set([A-Z][a-zA-z0-9]*)""".r
+ private val Setter = """set([A-Z][a-zA-z0-9]*)""".r
- private val COMPARTMENT_CLASS = classOf[Compartment]
+ private val PLAYER_SYNC_CLASS = classOf[PlayerSync]
+ private val PLAYER_SYNC_STYPE = SType(PLAYER_SYNC_CLASS.getSimpleName, PLAYER_SYNC_CLASS.getPackage.getName)
- // TODO enhance with player sync instead of compartment! (where to get the class from?)
- private val COMPARTMENT_STYPE = SType(COMPARTMENT_CLASS.getSimpleName, COMPARTMENT_CLASS.getPackage.getName)
+ private val PLAYER_SYNC_INIT = SMethodStatement("buildClass()")
}
--
GitLab