Skip to content

Commit

Permalink
Collapse Java getters and setters into fields
Browse files Browse the repository at this point in the history
  • Loading branch information
gnagy committed Jan 23, 2024
1 parent 4554b56 commit e310e5e
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 25 deletions.
28 changes: 27 additions & 1 deletion src/main/kotlin/hu/webhejj/perspektive/ClassDiagram.kt
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,14 @@ class ClassDiagram(
}

private fun scanMembers(kClass: KClass<*>): List<UmlMember> {
return kClass.declaredMemberProperties.filter { scanConfig.isAllowed(kClass, it) }.map { scanKProperty(it, isStatic = false) } +
val umlMembers = kClass.declaredMemberProperties.filter { scanConfig.isAllowed(kClass, it) }.map { scanKProperty(it, isStatic = false) } +
kClass.staticProperties.filter { scanConfig.isAllowed(kClass, it) }.map { scanKProperty(it, isStatic = true) } +
kClass.declaredMemberFunctions.filter { scanConfig.isAllowed(kClass, it) }.map { mapper.toMethodMember(it, isStatic = false) } +
kClass.staticFunctions.filter { scanConfig.isAllowed(kClass, it) }.map { mapper.toMethodMember(it, isStatic = true) } +
kClass.enumValues().map { mapper.toEnumMember(it) }
// try { kClass.declaredMemberFunctions.umlMethods(kClass, isStatic = false) } catch (e: Throwable) { listOf() } +

return collapseJavaBeanProperties(umlMembers)
}

private fun scanKProperty(kProperty: KProperty<*>, isStatic: Boolean): UmlMember {
Expand All @@ -105,6 +107,30 @@ class ClassDiagram(
return mapper.toPropertyMember(kProperty, isStatic = isStatic)
}

private fun collapseJavaBeanProperties(members: List<UmlMember>): List<UmlMember> {
val mutableMembers = members.toMutableList()
members
.filter { it.kind == UmlMember.Kind.PROPERTY }
.forEach { umlMember ->
val isGetterName = "is${umlMember.name.capitalize()}"
val getterName = "get${umlMember.name.capitalize()}"
val setterName = "set${umlMember.name.capitalize()}"
members
.find { it.kind == UmlMember.Kind.METHOD && it.name in listOf(isGetterName, getterName) }
?.also {
mutableMembers.remove(it)
umlMember.stereotypes.add("get")
}
members
.find { it.kind == UmlMember.Kind.METHOD && it.name == setterName }
?.also {
mutableMembers.remove(it)
umlMember.stereotypes.add("set")
}
}
return mutableMembers.toList()
}

fun renderWithPlantUml(file: File, renderingOptions: PlantUmlOptions = PlantUmlOptions()) {
PlantUmlWriter().also {
it.write(file, this, renderingOptions)
Expand Down
14 changes: 10 additions & 4 deletions src/main/kotlin/hu/webhejj/perspektive/plantuml/PlantUmlWriter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class PlantUmlWriter {
umlClass.typeParameters.joinToString(separator = ",", prefix = "<", postfix = ">") { it.name }
}

val stereotypes = if (umlClass.stereotypes.isEmpty()) "" else umlClass.stereotypes.joinToString(prefix = "<< ", postfix = " >>")
val stereotypes = stereotypesString(umlClass.stereotypes)

output.println("$abstract$kind ${umlClass.name.qualified}$generics $spot $stereotypes {")
}
Expand All @@ -109,6 +109,7 @@ class PlantUmlWriter {
classDiagram: ClassDiagram,
output: PrintWriter,
) {
val stereotypes = stereotypesString(umlClass.stereotypes)
umlClass.members
.filter { it.kind == UmlMember.Kind.PROPERTY }
.filter { prop -> classDiagram.umlClasses.none { it.name == prop.type } }
Expand All @@ -117,10 +118,11 @@ class PlantUmlWriter {
val static = if (prop.isStatic) "{static} " else ""
output.print(" $abstract$static${prop.visibility.plantumlPrefix}${prop.name}: ${prop.type.simple}${genericsString(prop.typeProjections)}")
if (prop.cardinality == UmlCardinality.OPTIONAL) {
output.println("?")
output.print("?")
} else {
output.println()
output.print("")
}
output.println(stereotypesString(prop.stereotypes))
}
}

Expand All @@ -138,8 +140,9 @@ class PlantUmlWriter {
.forEach {
val abstract = if (it.isAbstract) "{abstract} " else ""
val static = if (it.isStatic) "{static} " else ""
val returnType = if(it.type.simple == "Unit") "" else ": ${it.type.simple}"
val generics = genericsString(it.typeProjections)
output.println(" $abstract$static${it.visibility.plantumlPrefix}${it.name}(${it.parameters.joinToString()}): ${it.type.simple}$generics")
output.println(" $abstract$static${it.visibility.plantumlPrefix}${it.name}(${it.parameters.joinToString()})${returnType}$generics")
}
}

Expand Down Expand Up @@ -211,3 +214,6 @@ private val UmlVisibility?.plantumlPrefix: String
UmlVisibility.INTERNAL -> "~"
null -> ""
}

private fun stereotypesString(stereotypes: List<String>) =
if (stereotypes.isEmpty()) "" else stereotypes.joinToString(prefix = "<< ", postfix = " >>")
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class KotlinReflectionToUmlMapper {
isAbstract = kProperty.isAbstract,
isStatic = isStatic,
cardinality = cardinality,
stereotypes = mutableListOf(),
)
}

Expand All @@ -92,10 +93,11 @@ class KotlinReflectionToUmlMapper {
type = kFunction.returnType.umlName,
typeProjections = kFunction.returnType.arguments.map { it.uml },
// dropping first method parameter (`this` reference)
parameters = kFunction.parameters.drop(1).map { it.name ?: "" },
parameters = kFunction.parameters.drop(1).map { it.type.umlName.simple },
isAbstract = kFunction.isAbstract,
isStatic = isStatic,
cardinality = UmlCardinality.SCALAR, // TODO
stereotypes = mutableListOf(),
)
}

Expand All @@ -110,6 +112,7 @@ class KotlinReflectionToUmlMapper {
isAbstract = false,
isStatic = false,
cardinality = UmlCardinality.SCALAR,
stereotypes = mutableListOf(),
)
}
}
1 change: 1 addition & 0 deletions src/main/kotlin/hu/webhejj/perspektive/uml/UmlModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ data class UmlMember(
val isAbstract: Boolean,
val isStatic: Boolean,
val cardinality: UmlCardinality,
val stereotypes: MutableList<String>,
) {
enum class Kind {
PROPERTY,
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/hu/webhejj/perspektive/testmodel/JavaBeanModel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package hu.webhejj.perspektive.testmodel;

import java.util.Objects;

public class JavaBeanModel {

private String field;
private Boolean bool;

public String getField() {
return field;
}

public void setField(String field) {
this.field = field;
}

public Boolean isBool() {
return bool;
}

public void setBool(Boolean bool) {
this.bool = bool;
}

public String method(String arg) {
return arg;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JavaBeanModel that = (JavaBeanModel) o;
return Objects.equals(field, that.field) && Objects.equals(bool, that.bool);
}

@Override
public int hashCode() {
return Objects.hash(field, bool);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package hu.webhejj.perspektive.testmodel;

public class TestJavaModel {
public class JavaStaticModel {

public static String staticField = "staticField";

Expand Down
51 changes: 33 additions & 18 deletions src/test/kotlin/hu/webhejj/perspektive/TestModelTest.kt
Original file line number Diff line number Diff line change
@@ -1,59 +1,74 @@
package hu.webhejj.perspektive

import hu.webhejj.perspektive.testmodel.AbstractClass
import hu.webhejj.perspektive.testmodel.ComplexDataClass
import hu.webhejj.perspektive.testmodel.GenericDataClass
import hu.webhejj.perspektive.testmodel.GenericDataClass2
import hu.webhejj.perspektive.testmodel.ListContainer
import hu.webhejj.perspektive.testmodel.MapContainer
import hu.webhejj.perspektive.testmodel.SubClass
import hu.webhejj.perspektive.testmodel.TestJavaModel
import hu.webhejj.perspektive.testmodel.JavaBeanModel
import hu.webhejj.perspektive.testmodel.JavaStaticModel
import org.junit.jupiter.api.Test
import java.io.File

class TestModelTest {

private val targetDir = File("build/ktuml/")
private val targetDir = File("build/perspektive/")

@Test
fun testModel() {
fun `data class`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(SubClass::class)
classDiagram.scanKClass(GenericDataClass2::class)
classDiagram.renderWithPlantUml(File(targetDir, "testModel.plantuml"))
classDiagram.scanKClass(ComplexDataClass::class)
classDiagram.renderWithPlantUml(File(targetDir, "data-class.plantuml"))
}

@Test
fun testTypeParameters() {
fun `generic data class`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(GenericDataClass::class)
classDiagram.renderWithPlantUml(File(targetDir, "testTypeParameters.plantuml"))
classDiagram.renderWithPlantUml(File(targetDir, "generic-data-class.plantuml"))
}

@Test
fun testListFields() {
fun subclass() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(SubClass::class)
classDiagram.scanKClass(GenericDataClass2::class)
classDiagram.renderWithPlantUml(File(targetDir, "subclass.plantuml"))
}

@Test
fun `list container`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(ListContainer::class)
classDiagram.renderWithPlantUml(File(targetDir, "testListFields.plantuml"))
classDiagram.renderWithPlantUml(File(targetDir, "list-container.plantuml"))
}

@Test
fun testMapFields() {
fun `map container`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(MapContainer::class)
classDiagram.renderWithPlantUml(File(targetDir, "testMapFields.plantuml"))
classDiagram.renderWithPlantUml(File(targetDir, "map-container.plantuml"))
}

@Test
fun testAbstractClass() {
fun `abstract class`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(AbstractClass::class)
classDiagram.renderWithPlantUml(File(targetDir, "testAbstractClass.plantuml"))
classDiagram.renderWithPlantUml(File(targetDir, "abstract-class.plantuml"))
}

@Test
fun `java static`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(JavaStaticModel::class)
classDiagram.renderWithPlantUml(File(targetDir, "java-static.plantuml"))
}

@Test
fun testStatic() {
fun `java bean`() {
val classDiagram = ClassDiagram()
classDiagram.scanKClass(TestJavaModel::class)
classDiagram.renderWithPlantUml(File(targetDir, "testStatic.plantuml"))
classDiagram.scanKClass(JavaBeanModel::class)
classDiagram.renderWithPlantUml(File(targetDir, "java-bean.plantuml"))
}
}
13 changes: 13 additions & 0 deletions src/test/kotlin/hu/webhejj/perspektive/testmodel/TestModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ data class SimpleDataClass(
val field: String,
)


data class ComplexDataClass(
val readonly: String,
var writable: String,
val nullable: String?,
val withDefault: String = "default",
) {
fun method(): String {
return "function"
}
}


data class GenericDataClass<K, V : String?>(
val key: K,
val value: V,
Expand Down

0 comments on commit e310e5e

Please sign in to comment.