Skip to content
Snippets Groups Projects
Commit c488af89 authored by Rico Bergmann's avatar Rico Bergmann
Browse files

Merge branch 'develop' into 'master'

Publish v0.1

Closes #6, #8, #7, #11, #10, and #5

See merge request !2
parents 3557b6af d859f286
No related branches found
No related tags found
1 merge request!2Publish v0.1
Showing
with 848 additions and 0 deletions
package org.rosi_project.model_sync.generator.acr_model
import java.util.Objects
/** Representation of Scala classes. Attributes, methods, etc. have their own wrappers which should
* be used to modify these individual parts of the class.
*
* @see [[SType]]
* @author Rico Bergmann
*/
class SClass(val name: String,
val sPackage: String = "",
var attributes: Seq[SAttribute] = Seq.empty,
var parent: STypedElement = null,
protected var methods: Seq[SMethod] = Seq.empty)
extends STypedElement {
protected var constructorStatements: Seq[SMethodStatement] = Seq.empty
/** Provides code that should be executed when invoking the constructor.
*/
def getAdditionalConstructorStatements: Seq[SMethodStatement] = constructorStatements
/** Checks, whether `this` is part of the default package
*/
def isDefaultPackage: Boolean = sPackage == ""
/** Checks, whether `this` extends any other classes. If this is the case, it is '''not'''
* considered a ''root class'', otherwise it is.
*/
def isRootClass: Boolean = parent == null
/** Provides the set of all symbols that have to be imported in order to use `this` complete
* class.
*/
def collectImports: Set[String] = getNecessaryImports.map(imp => s"${imp.pckg}.${imp.name}")
/** Augments `this` class with another method.
*
* @param m the additional method. May not be `null`
*/
def addMethod(m: SMethod): Unit = {
Objects.requireNonNull(m, "Method to add may not be null")
if (methods.contains(m)) {
return
}
methods = methods :+ m
}
def getMethods: Seq[SMethod] = {
if (methods.exists(_.name == "toString")) {
methods
} else {
methods :+ new SMethod(
name = "toString",
result = SType.String,
params = Seq.empty,
implementation = Seq(SMethodStatement((List(s""" "$name: " """) ++ attributes.map(attr => s""" "${attr.name}=" + ${attr.name} + " " """)).mkString(" + "))),
overrides = true
)
}
}
/** Augments the constructor by another statement. The statement will be executed lastly (though
* this may change due to further modifications of the constructor).
*
* @param statement the new statement. May not be `null`
*/
def augmentConstructor(statement: SMethodStatement): Unit = {
Objects.requireNonNull(statement, "Statement to add may not be null")
constructorStatements = constructorStatements :+ statement
}
/** Makes `this` class a subclass of `parent`.
*
* If `parent == this` nothing will happen.
*
* @param parent the superclass. May be `null` to indicate that there is no superclass (other
* than `AnyRef`)
*/
def setParent(parent: STypedElement): Unit = {
if (parent == this) {
return
}
this.parent = parent
}
/** Sets the attributes of `this` class.
*
* @param attrs the attributes. May be `null` to remove all current attributes.
*/
def setAttributes(attrs: Seq[SAttribute]): Unit = {
if (attrs == null) {
this.attributes = Seq.empty
} else {
this.attributes = attrs
}
}
/** Provides all types `this` classes accesses in some way.
*/
def getUsedTypes: Set[STypedElement] = {
val parentImport: Seq[STypedElement] = if (isRootClass) List() else List(parent)
val parentConstructorParamImports: Seq[STypedElement] = if (isRootClass) List() else parent match {
case parent: SClass => parent.attributes.map(_.attrType)
case _ => List()
}
val attrTypeImports: Seq[STypedElement] = attributes.map(_.attrType)
val methodResultImports: Seq[STypedElement] = methods.map(_.result)
val methodParamImports: Seq[STypedElement] = methods.flatMap(_.params).map(_.paramType)
val methodImplImports: Seq[STypedElement] = methods.flatMap(_.getUsedTypes.toList)
// create a set to eliminate duplicates
(parentImport
++ parentConstructorParamImports
++ attrTypeImports
++ methodResultImports
++ methodParamImports
++ methodImplImports).toSet
}
override def getName: String = name
override def getPackage: String = sPackage
override def getConstructorParameters: Seq[SMethodParameter] = {
val ownParams = attributes.map(attr => SMethodParameter(attr.name, attr.attrType))
val parentParams = if (isRootClass) List() else parent.getConstructorParameters
ownParams ++ parentParams
}
override def getInheritanceHierarchy: Seq[STypedElement] = if (isRootClass) List(this) else this +: parent.getInheritanceHierarchy
override def getNecessaryImports: Set[SImport] = {
val parentImport: List[SImport] = if (isRootClass) List() else includeImportIfNecessary(parent.getPackage, parent.getName)
val parentConstructorParamImports: List[SImport] = if (isRootClass) List() else parent match {
case parent: SClass =>
parent.attributes
.map(attr => includeImportIfNecessary(attr.attrType.getPackage, attr.attrType.getName))
.fold(List())((l1, l2) => l1 ++ l2)
case _ => List()
}
val attrTypeImports: List[SImport] =
attributes
.map(attr => includeImportIfNecessary(attr.attrType.getPackage, attr.getType))
.fold(List())((l1, l2) => l1 ++ l2)
val methodResultImports: List[SImport] =
methods
.map(_.result)
.map(res => includeImportIfNecessary(res.getPackage, res.getName))
.fold(List())((l1, l2) => l1 ++ l2)
val methodParamImports: List[SImport] =
methods
.map(_.params)
.fold(List())((l1, l2) => l1 ++ l2)
.map(param => includeImportIfNecessary(param.paramType.getPackage, param.paramType.getName))
.fold(List())((l1, l2) => l1 ++ l2)
val methodImplImports: List[SImport] =
methods
.map(_.getUsedTypes.toList)
.fold(List())((l1, l2) => l1 ++ l2)
.map(typ => includeImportIfNecessary(typ.getPackage, typ.getName))
.fold(List())((l1, l2) => l1 ++ l2)
// create a set to eliminate duplicates
(parentImport
++ parentConstructorParamImports
++ attrTypeImports
++ methodResultImports
++ methodParamImports
++ methodImplImports).toSet
}
override def accept(visitor: SModelVisitor): Unit = {
attributes.foreach(_.accept(visitor))
methods.foreach(_.accept(visitor))
visitor.visit(this)
}
protected def canEqual(other: Any): Boolean = other.isInstanceOf[SClass]
/** Checks if some class has to be imported in order to be usable from `this` class. If this
* is the case it will wrap the necessary import in a `List` for further usage.
*
* @param sPackage the package of the class
* @param sClass the class
* @return an empty list if the class does not need to be imported, or the necessary import
* otherwise
*/
private def includeImportIfNecessary(sPackage: String, sClass: String): List[SImport] = {
if (sPackage != this.sPackage && sPackage != "") List(SImport(sPackage, sClass)) else List()
}
override def equals(other: Any): Boolean = other match {
case that: SClass =>
(that canEqual this) &&
name == that.name &&
sPackage == that.sPackage
case _ => false
}
override def hashCode(): Int = {
val state = Seq(name, sPackage)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
override def toString: String = s"$name(${attributes.map(_.name).mkString(", ")})"
}
package org.rosi_project.model_sync.generator.acr_model
import org.rosi_project.model_sync.generator.support.ExtendedString.stringToExtended
/** Simple representation of a getter method.
*
* @author Rico Bergmann
*/
class SGetter(attr: SAttribute) extends SMethod(
name = s"get${attr.name.firstLetterToUpperCase}",
result = attr.attrType,
params = Seq.empty,
implementation = Seq(SMethodStatement(content = attr.name, usedTypes = Set(attr.attrType)))) {
}
package org.rosi_project.model_sync.generator.acr_model
/** Wraps a type that needs to be imported in order for some class to compile.
*
* @param pckg the package that contains the type to import
* @param name the name of the type to import
* @author Rico Bergmann
*/
case class SImport(pckg: String, name: String)
/** The companion provides convenience methods to deal with sets of imports.
*/
object SImport {
/** Creates the imports necessary to access the specified types.
*/
def generateImports(types: STypedElement*): Set[SImport] = removeUnnecessaryImports(types.map(t => SImport(t.getPackage, t.getName)).toSet)
/** Creates the imports necessary to access the specified types. Necessity to imports will be
* assessed from a base package.
*/
def generateImportsForBasePackage(basePckg: String, types: STypedElement*) = removeUnnecessaryImports(generateImports(types: _*), basePckg)
/** Deletes all the imports which refer to types in the default package and are therefore not
* necessary.
*/
def removeUnnecessaryImports(imports: Set[SImport]): Set[SImport] = imports.filter(_.pckg != "")
/** Deletes all the imports which refer to types in the default package or the same package as
* specified and are therefore not necessary.
*/
def removeUnnecessaryImports(imports: Set[SImport], basePckg: String): Set[SImport] = imports.filter(imp => imp.pckg != "" && imp.pckg != basePckg)
}
package org.rosi_project.model_sync.generator.acr_model
import java.util.Objects
// TODO use a simple DSL to represent the method body?
/** Abstraction of a method.
*
* @author Rico Bergmann
*/
class SMethod(val name: String,
val result: STypedElement,
val params: Seq[SMethodParameter],
var implementation: Seq[SMethodStatement],
var overrides: Boolean = false)
extends SModelElement {
/** Provides the return-type as a `String`.
*
* @return the type. Will never be `null` nor empty.
*/
def getResultType: String = result.getName
/** Replaces the current body of `this` method.
*
* @param impl the new body. May be `null` to remove all current statements, leaving the body
* empty.
*/
def updateImplementation(impl: Seq[SMethodStatement]): Unit = {
if (impl == null) {
this.implementation = Seq.empty
} else {
this.implementation = impl
}
}
/** Adds another statement to the current method body.
*
* @param statement the statement. May not be `null`
*/
def augmentImplementation(statement: SMethodStatement): Unit = {
Objects.requireNonNull(statement, "Statement to add may not be null")
this.implementation = this.implementation :+ statement
}
/** Collects all types which need to be imported for `this` method to use.
*/
def getUsedTypes: Set[STypedElement] = {
val resultType = result match {
case GenericSType(_, _, typeParam) => Seq(result, typeParam)
case SType.Unit => Seq.empty
case _ => Seq(result)
}
val paramTypes = params.map(_.paramType)
val implementationTypes = implementation.map(_.usedTypes).fold(Set.empty)((s1, s2) => s1 ++ s2)
(resultType ++ paramTypes).toSet ++ implementationTypes
}
override def accept(visitor: SModelVisitor): Unit = visitor.visit(this)
protected def canEqual(other: Any): Boolean = other.isInstanceOf[SMethod]
override def equals(other: Any): Boolean = other match {
case that: SMethod =>
(that canEqual this) &&
name == that.name &&
result == that.result &&
params == that.params
case _ => false
}
override def hashCode(): Int = {
val state = Seq(name, result, params)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
override def toString: String = s"$name(${params.map(_.name).mkString(", ")}): $getResultType"
}
package org.rosi_project.model_sync.generator.acr_model
/** Wrapper for parameters that need to be passed to a method.
*
* @author Rico Bergmann
*/
case class SMethodParameter(name: String, paramType: STypedElement) {
/** Provides the type of `this` parameter as a `String`.
*
* @return the type. Will never be `null` nor empty.
*/
def getType: String = paramType.getName
override def toString: String = s"$name: $getType"
}
package org.rosi_project.model_sync.generator.acr_model
/** Wrapper for the statements that form a method.
*
* As for now we represent the actual statement as a simple `String` the types used in the
* statement need to be given explicitly and cannot be derived as e.g. in the case of [[SClass]]
* or [[SMethod]].
*
* To get the actual statement [[getContent]] should be used instead of accessing `content`
* directly. This will make usage more robust if the content will be replaced by a more
* sophisticated structure later on (which is likely to happen).
*
* @author Rico Bergmann
*/
case class SMethodStatement(content: String, usedTypes: Set[STypedElement] = Set.empty) {
/** The actual statement as a `String`.
*
* We provide this method as a stable API to the statement. Accessing the content directly
* through the corresponding property will couple code too tightly to this current
* implementation of `content` - which is likely to be replaced by a more sophisticated
* structure to represent method statements.
*
* @return the statement. Will never be `null` nor empty.
*/
def getContent: String = content
override def toString: String = getContent
}
package org.rosi_project.model_sync.generator.acr_model
/** A model is a collection of classes that represents a specific data-driven view on the
* application.
*
* @author Rico Bergmann
*/
trait SModel extends SModelElement {
/** Provides all the classes in `this` model.
*/
def getAllClasses: Set[SClass]
/** Extends the model by a new class.
*
* @param mClass the class to add. May never `null`.
*/
def addModelClass(mClass: SClass): Unit
override def toString: String = s"SModel: classes=$getAllClasses"
}
package org.rosi_project.model_sync.generator.acr_model
/** Base class for all parts of the ''ACR''. It merely exists to enable the ''Visitor pattern''.
*
* @see [[https://en.wikipedia.org/wiki/Visitor_pattern Visitor pattern @ Wikipedia]]
* @author Rico Bergmann
*/
trait SModelElement {
/** Executes the `visitor` on the structure of `this` element.
*
* Implementations will differ naturally but the most common approach is to recursively call
* `accept` on all composed elements and run `visitor.visit(this)` at the end.
*
* @param visitor the visitor. Might not be `null`.
*/
def accept(visitor: SModelVisitor)
}
package org.rosi_project.model_sync.generator.acr_model
// TODO fix cyclic imports with classes from acr package
/** Runs an algorithm on model instances.
*
* @see [[https://en.wikipedia.org/wiki/Visitor_pattern Visitor pattern @ Wikipedia]]
* @author Rico Bergmann
*/
trait SModelVisitor {
/** Runs the algorithm as appropriate for the whole model.
*
* @param sModel the model to run the algorithm on. Whether it may be `null` is implementation
* specific.
*/
def visit(sModel: SModel): Unit
/** Runs the algorithm as appropriate for a single class of the model.
*
* @param sClass the class to run the algorithm on
*/
def visit(sClass: SClass): Unit
/** Runs the algorithm as appropriate for an attribute of a model class.
*
* @param sAttr the attribute to run the algorithm on. Whether it may be `null` is implementation
* specific.
*/
def visit(sAttr: SAttribute): Unit
/** Runs the algorithm as appropriate for a method of a model class.
*
* @param sMethod the method to run the algorithm on. Whether it may be `null` is implementation
* specific.
*/
def visit(sMethod: SMethod): Unit
/** Runs the algorithm as appropriate for a type of the model.
*
* @param sType the type to run the algorithm on. Whether it may be `null` is implementation
* specific.
*/
def visit(sType: SType): Unit
}
package org.rosi_project.model_sync.generator.acr_model
import org.rosi_project.model_sync.generator.support.ExtendedString.stringToExtended
/** Simple representation of a setter method.
*
* @author Rico Bergmann
*/
class SSetter(attr: SAttribute) extends SMethod(
name = s"set${attr.name.firstLetterToUpperCase}",
result = SType.Unit,
params = Seq(SMethodParameter(attr.name.head.toString, attr.attrType)),
implementation = Seq(SMethodStatement(content = s"${attr.name} = ${attr.name.head}", usedTypes = Set(attr.attrType)))) {
}
package org.rosi_project.model_sync.generator.acr_model
/** Represents types provided by the Java runtime (either as part of the Java/Scala standard
* libraries or other third-party projects).
*
* In general as a rule of thumb when loading a model the classes defined by the model should be
* mapped to instances of [[SClass]] whereas all those types that are used (but not defined) within
* the model and therefore function as some kind of "support-type" should become instances of this
* class.
*
* @see [[SClass]]
* @author Rico Bergmann
*/
case class SType(name: String, sPackage: String = "") extends STypedElement {
override def getName: String = name
override def getPackage: String = sPackage
override def accept(visitor: SModelVisitor): Unit = visitor.visit(this)
}
/** The companion defines frequently used types.
*/
object SType {
/** The empty type.
*/
val Unit = SType("Unit")
/** Wrapper for `AnyRef`.
*/
val AnyRef = SType("AnyRef")
/** Wrapper for `String`.
*/
val String = SType("String")
}
package org.rosi_project.model_sync.generator.acr_model
/** Repository to keep track of all types and classes of model. It ensures that all attributes,
* methods, etc. reference the same type instances and thus prevent duplication and conflicting
* states. The registry should be treated as "''single point of truth''".
*
* @author Rico Bergmann
*/
object STypeRegistry {
private var registeredTypes: Set[STypedElement] = Set()
registerDefaultTypes()
/** Registers a type if it is not already known.
*
* @param theType the new type. May never be `null`.
* @return `theType` if it was indeed unknown before. Otherwise the currently registered type.
*/
def addType(theType: STypedElement): STypedElement = {
val existing: Option[STypedElement] = registeredTypes.find(existing => existing.getName == theType.getName && existing.getPackage == theType.getPackage)
if (existing.isEmpty) {
registeredTypes += theType
theType
} else {
existing.get
}
}
/** Searches for a type.
*
* @param name the type's name
* @param sPackage the package that contains the type
* @return the type if it was found
*/
def query(name: String, sPackage: String): Option[STypedElement] = {
registeredTypes.find(t => t.getName == name && t.getPackage == sPackage)
}
/** Searches for a type based on its name. It may reside in any package.
*
* @param name the type's name
* @return the type if it was found
*/
def queryForName(name: String): Option[STypedElement] = {
registeredTypes.find(_.getName == name)
}
/** Provides all types that are currently in the repository.
*/
def allTypes: Set[STypedElement] = registeredTypes
private def registerDefaultTypes(): Unit = {
registeredTypes ++= Seq(
SType("Boolean"),
SType("Byte"),
SType("Short"),
SType("Integer"),
SType("Long"),
SType("Float"),
SType("Double"),
SType("String"),
)
}
override def toString: String = s"Registry: $registeredTypes"
}
package org.rosi_project.model_sync.generator.acr_model
/** Abstraction of "elements" that may be used in source code where a type is expected.
*
* Currently there are two different kinds of such elements:
*
* - instances of [[SClass]] are supplied by the user
* - instances of [[SType]] are provided by the Java runtime (they are classes of the standard
* Java libraries or others)
*
* @author Rico Bergmann
*/
trait STypedElement extends SModelElement {
/** The name of `this` type.
*
* @return the name. Will never be `null` nor empty.
*/
def getName: String
/** The package containing `this` type.
*
* @return
*/
def getPackage: String
/** The parameters necessary to create an instance of `this` type.
*/
def getConstructorParameters: Seq[SMethodParameter] = Seq.empty
/** The inheritance hierarchy provides information about the types `this` extends. It will start
* with `this` (thus the hierarchy will 'never' be empty) and end with the root type. `AnyRef`
* will be ignored however.
*/
def getInheritanceHierarchy: Seq[STypedElement] = Seq.empty
/** Provides all classes that need to be imported for `this` type.
*/
def getNecessaryImports: Set[SImport] = Set.empty
}
package org.rosi_project.model_sync.generator.acr_model
import org.rosi_project.model_sync.generator.support.Assert
/** Default implementation of the [[SModel]].
*
* @author Rico Bergmann
*/
class SimpleSModel extends SModel {
private var sClasses: Set[SClass] = Set.empty
override def addModelClass(mClass: SClass): Unit = {
Assert.notNull(mClass, "Class may not be null")
sClasses += mClass
}
override def getAllClasses: Set[SClass] = sClasses
override def accept(visitor: SModelVisitor): Unit = {
sClasses.foreach(_.accept(visitor))
visitor.visit(this)
}
def canEqual(other: Any): Boolean = other.isInstanceOf[SimpleSModel]
override def equals(other: Any): Boolean = other match {
case that: SimpleSModel =>
(that canEqual this) &&
sClasses == that.sClasses
case _ => false
}
override def hashCode(): Int = {
val state = Seq(sClasses)
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
}
}
package org.rosi_project.model_sync.generator
import org.rosi_project.model_sync.generator.acr_model.{SClass, SModel}
/** Abstract class representation of the Scala code used within a model.
*
* This package specifies a simplified meta-model of scala classes. Most parts of (our take on) a
* Scala class have a corresponding wrapper class that may be modified for further adaptations.
*
* @see [[SClass]]
* @see [[SModel]]
* @author Rico Bergmann
*/
package object acr_model {
}
package org.rosi_project.model_sync.generator.acr_model.types
import org.rosi_project.model_sync.generator.acr_model.{GenericSType, SType, STypedElement}
/** Contains a number of types that are part of the JSE as well as some more advanced Scala types.
*
* This should prevent creating them over and over again every time a `SType` should be set to any
* of these types.
*
* @author Rico Bergmann
*/
object PredefTypes {
/** `java.lang.Object`
*/
val Object = SType("Object")
/** `java.io.File`
*/
val File = SType("File", "java.io")
/** `java.lang.reflect.Parameter`
*/
val Parameter = SType("Parameter", "java.lang.reflect")
/** `java.lang.Class[typ]`
*
* @param typ the type parameter for the class type
*/
def classOf(typ: STypedElement): GenericSType = new GenericSType("Class", typeParam = typ)
/** `Option[typ]`
*
* @param typ the type parameter for the Option type
*/
def option(typ: STypedElement): GenericSType = new GenericSType("Option", typeParam = typ)
}
package org.rosi_project.model_sync.generator.acr_model.types
import org.rosi_project.model_sync.generator.acr_model._
/** Wraps a Scala `List` for some type.
*/
class SList(elemType: STypedElement) extends SSeq(elemType) {
override def getConstructorParameters: Seq[SMethodParameter] = Seq(SMethodParameter("elems", elemType))
}
/** The companion provides `apply` and `unapply` methods.
*/
object SList {
def apply(elemType: STypedElement): SList = new SList(elemType)
def unapply(arg: SList): Option[STypedElement] = Some(arg.elemType)
}
package org.rosi_project.model_sync.generator.acr_model.types
import org.rosi_project.model_sync.generator.acr_model.{GenericSType, SMethodParameter, STypedElement}
/** Wraps a Scala `Seq` for some type.
*/
class SSeq(val elemType: STypedElement) extends GenericSType(name = "Seq", sPackage = "", typeParam = elemType) {
override def getConstructorParameters: Seq[SMethodParameter] = Seq(SMethodParameter("elems", elemType))
}
/** The companion provides `apply` and `unapply` methods.
*/
object SSeq {
def apply(elemType: STypedElement): SSeq = new SSeq(elemType)
def unapply(arg: SSeq): Option[STypedElement] = Some(arg.elemType)
}
package org.rosi_project.model_sync.generator.conversion
/** A converter converts instance of a source type `S` to a target type `T`.
*
* @tparam S the source type
* @tparam T the target type
* @author Rico Bergmann
*/
trait Converter[S, T] {
/** Converts the source object.
*
* @param source the object to convert
* @return the converted object
*/
def convert(source: S): T
}
package org.rosi_project.model_sync.generator.conversion
import java.util.Objects
import org.eclipse.emf.ecore.EDataType
import org.rosi_project.model_sync.generator.acr_model.SType
/** Service to map an instance of [[EDataType]] to its corresponding [[SType]] (which wrap Scala's
* native types).
*
* @author Rico Bergmann
*/
object EmfTypeTranslator {
private val typeMap: Map[String, SType] = Map(
"EBoolean" -> SType("Boolean"),
"EByte" -> SType("Byte"),
"EChar" -> SType("Char"),
"EDate" -> SType(name = "Date", sPackage = "java.util"),
"EDouble" -> SType("Double"),
"EFloat" -> SType("Float"),
"EInt" -> SType("Int"),
"EJavaObject" -> SType("Object"),
"ELong" -> SType("Long"),
"EShort" -> SType("Short"),
"EString" -> SType("String"),
)
/** Maps an EMF data type to its corresponding Scala type.
*
* @param dataType the source type. May not be `null`.
* @return the matching type. If there is none, the `Option` will be empty.
*/
def getSClassFromEmf(dataType: EDataType): Option[SType] = {
Objects.requireNonNull(dataType, "Data type may not be null")
typeMap.get(dataType.getName)
}
/** Maps an EMF data type to its corresponding Scala type.
*
* @param dataType the source type. May not be `null`.
* @return the matching type. If there is none, the `Option` will be empty.
*/
def getSClassFromEmf(dataType: String): Option[SType] = {
Objects.requireNonNull(dataType, "Data type may not be null")
typeMap.get(dataType)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment