Skip to content

Commit 3945444

Browse files
committed
More efficient codec derivation
1 parent 70d40d4 commit 3945444

File tree

3 files changed

+114
-106
lines changed

3 files changed

+114
-106
lines changed

build.sbt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,7 @@ lazy val publishSettings = Seq(
138138
},
139139
mimaReportSignatureProblems := true,
140140
mimaBinaryIssueFilters := Seq(
141-
ProblemFilters.exclude[MissingClassProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerInstance$FieldAnnotations"),
142-
ProblemFilters.exclude[MissingClassProblem]("com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerInstance$FieldAnnotations$")
141+
ProblemFilters.exclude[Problem]("com.github.plokhotnyuk.jsoniter_scala.macros.JsonCodecMakerInstance*")
143142
)
144143
)
145144

jsoniter-scala-macros/shared/src/main/scala-2/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala

Lines changed: 49 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,8 @@ object JsonCodecMaker {
742742
def isCollection(tpe: Type): Boolean =
743743
tpe <:< typeOf[Iterable[?]] || tpe <:< typeOf[Iterator[?]] || tpe <:< typeOf[Array[?]]
744744

745+
def isJavaEnum(tpe: Type): Boolean = tpe <:< typeOf[java.lang.Enum[?]]
746+
745747
def scalaCollectionCompanion(tpe: Type): Tree =
746748
if (tpe.typeSymbol.fullName.startsWith("scala.collection.")) Ident(tpe.typeSymbol.companion)
747749
else fail(s"Unsupported type '$tpe'. Please consider using a custom implicitly accessible codec for it.")
@@ -808,26 +810,43 @@ object JsonCodecMaker {
808810
(name, q"private[this] val $name = new _root_.java.util.concurrent.ConcurrentHashMap[$keyTpe, $tpe]")
809811
})._1)
810812

811-
case class JavaEnumValueInfo(value: Tree, name: String, transformed: Boolean)
813+
sealed trait TypeInfo
812814

813-
val enumValueInfos = new mutable.LinkedHashMap[Type, List[JavaEnumValueInfo]]
815+
case class JavaEnumValueInfo(value: Tree, name: String)
814816

815-
def isJavaEnum(tpe: Type): Boolean = tpe <:< typeOf[java.lang.Enum[?]]
817+
case class JavaEnumInfo(valueInfos: List[JavaEnumValueInfo], hasTransformed: Boolean, doEncoding: Boolean) extends TypeInfo {
818+
val doLinearSearch: Boolean = valueInfos.size <= 8 && valueInfos.foldLeft(0)(_ + _.name.length) <= 64
819+
}
820+
821+
case class FieldInfo(symbol: TermSymbol, mappedName: String, tmpName: TermName, getter: MethodSymbol,
822+
defaultValue: Option[Tree], resolvedTpe: Type, isStringified: Boolean)
823+
824+
case class ClassInfo(tpe: Type, paramLists: List[List[FieldInfo]]) extends TypeInfo {
825+
val fields: List[FieldInfo] = paramLists.flatten
826+
}
816827

817-
def javaEnumValues(tpe: Type): List[JavaEnumValueInfo] = enumValueInfos.getOrElseUpdate(tpe, {
828+
val typeInfos = new mutable.HashMap[Type, TypeInfo]
829+
830+
def getJavaEnumInfo(tpe: Type): JavaEnumInfo = typeInfos.getOrElseUpdate(tpe, {
818831
val javaEnumValueNameMapper: String => String = n => cfg.javaEnumValueNameMapper.lift(n).getOrElse(n)
832+
var hasTransformed = false
833+
var doEncoding = false
819834
var values = tpe.typeSymbol.asClass.knownDirectSubclasses.toList.map { s: Symbol =>
820835
val name = s.name.toString
821836
val transformedName = javaEnumValueNameMapper(name)
822-
JavaEnumValueInfo(q"$s", transformedName, name != transformedName)
837+
hasTransformed |= name != transformedName
838+
doEncoding |= isEncodingRequired(transformedName)
839+
new JavaEnumValueInfo(q"$s", transformedName)
823840
}
824841
if (values eq Nil) {
825842
val comp = companion(tpe)
826843
values =
827844
comp.typeSignature.members.sorted.collect { case m: MethodSymbol if m.isGetter && m.returnType.dealias =:= tpe =>
828845
val name = decodeName(m)
829846
val transformedName = javaEnumValueNameMapper(name)
830-
JavaEnumValueInfo(q"$comp.${TermName(name)}", transformedName, name != transformedName)
847+
hasTransformed |= name != transformedName
848+
doEncoding |= isEncodingRequired(transformedName)
849+
new JavaEnumValueInfo(q"$comp.${TermName(name)}", transformedName)
831850
}
832851
}
833852
val nameCollisions = duplicated(values.map(_.name))
@@ -837,19 +856,19 @@ object JsonCodecMaker {
837856
s"names of the enum that are mapped by the '${typeOf[CodecMakerConfig]}.javaEnumValueNameMapper' function. " +
838857
s"Result values should be unique per enum class.")
839858
}
840-
values
841-
})
859+
new JavaEnumInfo(values, hasTransformed, doEncoding)
860+
}).asInstanceOf[JavaEnumInfo]
842861

843-
def genReadEnumValue(enumValues: Seq[JavaEnumValueInfo], unexpectedEnumValueHandler: Tree): Tree = {
862+
def genReadEnumValue(enumInfo: JavaEnumInfo, unexpectedEnumValueHandler: Tree): Tree = {
844863
def genReadCollisions(es: collection.Seq[JavaEnumValueInfo]): Tree =
845864
es.foldRight(unexpectedEnumValueHandler) { (e, acc) =>
846865
q"if (in.isCharBufEqualsTo(l, ${e.name})) ${e.value} else $acc"
847866
}
848867

849-
if (enumValues.size <= 8 && enumValues.foldLeft(0)(_ + _.name.length) <= 64) genReadCollisions(enumValues)
868+
if (enumInfo.doLinearSearch) genReadCollisions(enumInfo.valueInfos)
850869
else {
851870
val hashCode = (e: JavaEnumValueInfo) => JsonReader.toHashCode(e.name.toCharArray, e.name.length)
852-
val cases = groupByOrdered(enumValues)(hashCode).map { case (hash, fs) =>
871+
val cases = groupByOrdered(enumInfo.valueInfos)(hashCode).map { case (hash, fs) =>
853872
cq"$hash => ${genReadCollisions(fs)}"
854873
} :+ cq"_ => $unexpectedEnumValueHandler"
855874
q"""(in.charBufToHashCode(l): @_root_.scala.annotation.switch) match {
@@ -858,16 +877,7 @@ object JsonCodecMaker {
858877
}
859878
}
860879

861-
case class FieldInfo(symbol: TermSymbol, mappedName: String, tmpName: TermName, getter: MethodSymbol,
862-
defaultValue: Option[Tree], resolvedTpe: Type, isStringified: Boolean)
863-
864-
case class ClassInfo(tpe: Type, paramLists: List[List[FieldInfo]]) {
865-
val fields: List[FieldInfo] = paramLists.flatten
866-
}
867-
868-
val classInfos = new mutable.LinkedHashMap[Type, ClassInfo]
869-
870-
def getClassInfo(tpe: Type): ClassInfo = classInfos.getOrElseUpdate(tpe, {
880+
def getClassInfo(tpe: Type): ClassInfo = typeInfos.getOrElseUpdate(tpe, {
871881
case class FieldAnnotations(partiallyMappedName: Option[String], transient: Boolean, stringified: Boolean)
872882

873883
def hasSupportedAnnotation(m: TermSymbol): Boolean = {
@@ -930,7 +940,7 @@ object JsonCodecMaker {
930940
}
931941
})
932942
})
933-
})
943+
}).asInstanceOf[ClassInfo]
934944

935945
def isValueClass(tpe: Type): Boolean = !isConstType(tpe) && isNonAbstractScalaClass(tpe) &&
936946
(tpe.typeSymbol.asClass.isDerivedValueClass || cfg.inlineOneValueClasses && !isCollection(tpe) && getClassInfo(tpe).fields.size == 1)
@@ -1027,7 +1037,7 @@ object JsonCodecMaker {
10271037
}
10281038
} else if (isJavaEnum(tpe)) {
10291039
q"""val l = in.readKeyAsCharBuf()
1030-
${genReadEnumValue(javaEnumValues(tpe), q"in.enumValueError(l)")}"""
1040+
${genReadEnumValue(getJavaEnumInfo(tpe), q"in.enumValueError(l)")}"""
10311041
} else if (isConstType(tpe)) {
10321042
tpe match {
10331043
case ConstantType(Constant(v: String)) =>
@@ -1187,15 +1197,14 @@ object JsonCodecMaker {
11871197
if (cfg.useScalaEnumValueId) q"out.writeKey($x.id)"
11881198
else q"out.writeKey($x.toString)"
11891199
} else if (isJavaEnum(tpe)) {
1190-
val es = javaEnumValues(tpe)
1191-
val encodingRequired = es.exists(e => isEncodingRequired(e.name))
1192-
if (es.exists(_.transformed)) {
1193-
val cases = es.map(e => cq"${e.value} => ${e.name}") :+
1200+
val enumInfo = getJavaEnumInfo(tpe)
1201+
if (enumInfo.hasTransformed) {
1202+
val cases = enumInfo.valueInfos.map(e => cq"${e.value} => ${e.name}") :+
11941203
cq"""_ => out.encodeError("illegal enum value: " + $x)"""
1195-
if (encodingRequired) q"out.writeKey($x match { case ..$cases })"
1204+
if (enumInfo.doEncoding) q"out.writeKey($x match { case ..$cases })"
11961205
else q"out.writeNonEscapedAsciiKey($x match { case ..$cases })"
11971206
} else {
1198-
if (encodingRequired) q"out.writeKey($x.name)"
1207+
if (enumInfo.doEncoding) q"out.writeKey($x.name)"
11991208
else q"out.writeNonEscapedAsciiKey($x.name)"
12001209
}
12011210
} else if (isConstType(tpe)) {
@@ -1378,28 +1387,27 @@ object JsonCodecMaker {
13781387
case class MethodKey(tpe: Type, isStringified: Boolean, discriminator: Tree)
13791388

13801389
val decodeMethodNames = new mutable.HashMap[MethodKey, TermName]
1381-
val decodeMethodTrees = new mutable.ArrayBuffer[Tree]
1390+
val methodTrees = new mutable.ArrayBuffer[Tree]
13821391

13831392
def withDecoderFor(methodKey: MethodKey, arg: Tree)(f: => Tree): Tree = {
13841393
val decodeMethodName = decodeMethodNames.getOrElse(methodKey, {
13851394
val name = TermName(s"d${decodeMethodNames.size}")
13861395
val mtpe = methodKey.tpe
13871396
decodeMethodNames.update(methodKey, name)
1388-
decodeMethodTrees +=
1397+
methodTrees +=
13891398
q"private[this] def $name(in: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonReader, default: $mtpe): $mtpe = $f"
13901399
name
13911400
})
13921401
q"$decodeMethodName(in, $arg)"
13931402
}
13941403

13951404
val encodeMethodNames = new mutable.HashMap[MethodKey, TermName]
1396-
val encodeMethodTrees = new mutable.ArrayBuffer[Tree]
13971405

13981406
def withEncoderFor(methodKey: MethodKey, arg: Tree)(f: => Tree): Tree = {
13991407
val encodeMethodName = encodeMethodNames.getOrElse(methodKey, {
14001408
val name = TermName(s"e${encodeMethodNames.size}")
14011409
encodeMethodNames.update(methodKey, name)
1402-
encodeMethodTrees +=
1410+
methodTrees +=
14031411
q"private[this] def $name(x: ${methodKey.tpe}, out: _root_.com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter): _root_.scala.Unit = $f"
14041412
name
14051413
})
@@ -1458,7 +1466,7 @@ object JsonCodecMaker {
14581466
case _ => cannotFindValueCodecError(tpe)
14591467
}
14601468
} else if (tpe.typeSymbol.isModuleClass) q"${tpe.typeSymbol.asClass.module}"
1461-
else if (tpe <:< typeOf[AnyRef]) q"null"
1469+
else if (tpe <:< definitions.AnyRefTpe) q"null"
14621470
else q"null.asInstanceOf[$tpe]"
14631471
}
14641472

@@ -1904,7 +1912,7 @@ object JsonCodecMaker {
19041912
q"""if (in.isNextToken('"')) {
19051913
in.rollbackToken()
19061914
val l = in.readStringAsCharBuf()
1907-
${genReadEnumValue(javaEnumValues(tpe), q"in.enumValueError(l)")}
1915+
${genReadEnumValue(getJavaEnumInfo(tpe), q"in.enumValueError(l)")}
19081916
} else in.readNullOrTokenError(default, '"')"""
19091917
} else if (isTuple(tpe)) withDecoderFor(methodKey, default) {
19101918
val indexedTypes = typeArgs(tpe)
@@ -2271,15 +2279,14 @@ object JsonCodecMaker {
22712279
else q"out.writeVal(x.id)"
22722280
} else q"out.writeVal(x.toString)"
22732281
} else if (isJavaEnum(tpe)) withEncoderFor(methodKey, m) {
2274-
val es = javaEnumValues(tpe)
2275-
val encodingRequired = es.exists(e => isEncodingRequired(e.name))
2276-
if (es.exists(_.transformed)) {
2277-
val cases = es.map(e => cq"${e.value} => ${e.name}") :+
2282+
val enumInfo = getJavaEnumInfo(tpe)
2283+
if (enumInfo.hasTransformed) {
2284+
val cases = enumInfo.valueInfos.map(e => cq"${e.value} => ${e.name}") :+
22782285
cq"""_ => out.encodeError("illegal enum value: " + x)"""
2279-
if (encodingRequired) q"out.writeVal(x match { case ..$cases })"
2286+
if (enumInfo.doEncoding) q"out.writeVal(x match { case ..$cases })"
22802287
else q"out.writeNonEscapedAsciiVal(x match { case ..$cases })"
22812288
} else {
2282-
if (encodingRequired) q"out.writeVal(x.name)"
2289+
if (enumInfo.doEncoding) q"out.writeVal(x.name)"
22832290
else q"out.writeNonEscapedAsciiVal(x.name)"
22842291
}
22852292
} else if (isTuple(tpe)) withEncoderFor(methodKey, m) {
@@ -2354,8 +2361,7 @@ object JsonCodecMaker {
23542361
if (cfg.decodingOnly) q"_root_.scala.Predef.???"
23552362
else genWriteVal(q"x", types, cfg.isStringified, EmptyTree)
23562363
}
2357-
..$decodeMethodTrees
2358-
..$encodeMethodTrees
2364+
..$methodTrees
23592365
..${fields.values.map(_._2)}
23602366
..${equalsMethods.values.map(_._2)}
23612367
..${nullValues.values.map(_._2)}

0 commit comments

Comments
 (0)