Skip to content

Commit e3c601f

Browse files
committed
#142: Add the ability to list class constructor parameter names
1 parent 347288e commit e3c601f

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package za.co.absa.db.fadb.utils
2+
3+
import za.co.absa.db.fadb.naming.NamingConvention
4+
import za.co.absa.db.fadb.naming.implementations.SnakeCaseNaming
5+
6+
import java.lang
7+
import scala.reflect.runtime.universe._
8+
9+
object ClassFieldNamesExtractor {
10+
11+
private def doExtract[T: TypeTag](namingConvention: NamingConvention): Seq[String] = {
12+
val tpe = typeOf[T]
13+
if (tpe.typeSymbol.isClass) {
14+
val cl = tpe.typeSymbol.asClass
15+
if (cl.isPrimitive) {
16+
throw new IllegalArgumentException(s"${tpe.typeSymbol} is a primitive type, extraction is not supported")
17+
}
18+
if (cl.isTrait) {
19+
throw new IllegalArgumentException(s"${tpe.typeSymbol} is a trait, extraction is not supported")
20+
}
21+
if (cl.isCaseClass || cl.isClass) {
22+
tpe
23+
.decl(termNames.CONSTRUCTOR)
24+
.asMethod
25+
.paramLists
26+
.flatten
27+
.map(_.name.decodedName.toString)
28+
.map(namingConvention.stringPerConvention)
29+
} else {
30+
throw new IllegalArgumentException(s"${tpe.typeSymbol} is not a case class nor a class")
31+
}
32+
} else {
33+
throw new IllegalArgumentException(s"${tpe.typeSymbol} is not a case class nor a class")
34+
}
35+
}
36+
37+
/**
38+
* Extracts constructor field names from case class or regular class, and converts them according to naming convention.
39+
* @param namingConvention - the naming convention to use when converting the constructor parameters names into field name
40+
* @tparam T - type to investigate and extract field names from
41+
* @return - list of field names
42+
*/
43+
def extract[T: TypeTag]()(
44+
implicit namingConvention: NamingConvention = SnakeCaseNaming.Implicits.namingConvention
45+
): Seq[String] = {
46+
doExtract[T](namingConvention)
47+
}
48+
49+
def extract[T: TypeTag](namingConvention: NamingConvention): Seq[String] = {
50+
doExtract[T](namingConvention)
51+
}
52+
53+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2025 ABSA Group Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package za.co.absa.db.fadb.utils
18+
19+
import org.scalatest.funsuite.AnyFunSuiteLike
20+
import za.co.absa.db.fadb.naming.LettersCase
21+
import za.co.absa.db.fadb.naming.implementations.AsIsNaming
22+
import za.co.absa.db.fadb.utils.ClassFieldNamesExtractorUnitTests._
23+
24+
class ClassFieldNamesExtractorUnitTests extends AnyFunSuiteLike {
25+
test("Extract from case class returns its fields") {
26+
val expected = Seq(
27+
"int_field",
28+
"string_field"
29+
)
30+
val fieldNames = ClassFieldNamesExtractor.extract[TestCaseClass]()
31+
assert(fieldNames == expected)
32+
}
33+
34+
test("Extract from class constructor fields returns its constructor fields, explicit naming convention") {
35+
val expected = Seq(
36+
"XFIELD",
37+
"YFIELD"
38+
)
39+
val fieldNames = ClassFieldNamesExtractor.extract[TestClass](new AsIsNaming(LettersCase.UpperCase))
40+
assert(fieldNames == expected)
41+
}
42+
43+
test("Extract from class constructor fields returns its constructor fields, implicit naming convention") {
44+
implicit val namingConvention: AsIsNaming = new AsIsNaming(LettersCase.LowerCase)
45+
val expected = Seq(
46+
"xfield",
47+
"yfield"
48+
)
49+
val fieldNames = ClassFieldNamesExtractor.extract[TestClass]()
50+
assert(fieldNames == expected)
51+
}
52+
53+
test("Extract from trait fails") {
54+
intercept[IllegalArgumentException] {
55+
ClassFieldNamesExtractor.extract[TestTrait]
56+
}
57+
}
58+
59+
test("Extract fails on simple type") {
60+
intercept[IllegalArgumentException] {
61+
ClassFieldNamesExtractor.extract[Boolean]
62+
}
63+
}
64+
65+
}
66+
67+
object ClassFieldNamesExtractorUnitTests {
68+
case class TestCaseClass(intField: Int, stringField: String) {
69+
def stringFunction: String = intField.toString + stringField
70+
}
71+
72+
class TestClass(val xField: Int, val yField: String) {
73+
val w: String = xField.toString + yField
74+
75+
def z: String = xField.toString + yField
76+
}
77+
78+
trait TestTrait {
79+
def foo: String
80+
}
81+
82+
}

project/Dependencies.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import sbt.*
1919
object Dependencies {
2020

2121
private def commonDependencies(scalaVersion: String): Seq[ModuleID] = Seq(
22+
"org.scala-lang" % "scala-reflect" % scalaVersion,
2223
"org.typelevel" %% "cats-core" % "2.9.0",
2324
"org.typelevel" %% "cats-effect" % "3.5.0",
2425
"org.scalatest" %% "scalatest" % "3.1.0" % Test,

0 commit comments

Comments
 (0)