@@ -45,6 +45,7 @@ import scala.util.{Failure, Success}
45
45
object Metric extends LazyLogging {
46
46
val Delimiter : Char = '\t '
47
47
val DelimiterAsString : String = s " $Delimiter"
48
+ val DefaultCollectionDelimiter : Char = ','
48
49
49
50
/** A typedef for [[scala.Long ]] to be used when representing counts. */
50
51
type Count = Long
@@ -101,7 +102,9 @@ object Metric extends LazyLogging {
101
102
102
103
/** Reads metrics from a set of lines. The first line should be the header with the field names. Each subsequent
103
104
* line should be a single metric. */
104
- def iterator [T <: Metric ](lines : Iterator [String ], source : Option [String ] = None )(implicit tt : ru.TypeTag [T ]): Iterator [T ] = {
105
+ def iterator [T <: Metric ](lines : Iterator [String ],
106
+ source : Option [String ] = None ,
107
+ collectionDelimiter : Char = DefaultCollectionDelimiter )(implicit tt : ru.TypeTag [T ]): Iterator [T ] = {
105
108
val clazz : Class [T ] = ReflectionUtil .typeTagToClass[T ]
106
109
107
110
def fail (lineNumber : Int ,
@@ -127,15 +130,28 @@ object Metric extends LazyLogging {
127
130
128
131
parser.zipWithIndex.map { case (row, rowIndex) =>
129
132
forloop(from = 0 , until = names.length) { i =>
133
+
130
134
reflectiveBuilder.argumentLookup.forField(names(i)) match {
131
135
case Some (arg) =>
132
- val value = {
136
+ val value : String = {
133
137
val tmp = row[String ](i)
134
138
if (tmp.isEmpty && arg.argumentType == classOf [Option [_]]) ReflectionUtil .SpecialEmptyOrNoneToken else tmp
135
139
}
136
-
137
- val argumentValue = ReflectionUtil .constructFromString(arg.argumentType, arg.unitType, value) match {
138
- case Success (v) => v
140
+ val values : Seq [String ] = {
141
+
142
+ // If we have a collection, then we need to check for the delimiter to rebuild it
143
+ if (value != ReflectionUtil .SpecialEmptyOrNoneToken &&
144
+ ReflectionUtil .isCollectionClass(arg.argumentType)) {
145
+ // If the argument type is equal to the unit type, then we need to return a single string value,
146
+ // otherwise, one value per unit
147
+ if (arg.argumentType == arg.unitType) Seq (value) else value.split(collectionDelimiter)
148
+ }
149
+ else {
150
+ Seq (value)
151
+ }
152
+ }
153
+ val argumentValue = ReflectionUtil .constructFromString(arg.argumentType, arg.unitType, values:_* ) match {
154
+ case Success (v) => v
139
155
case Failure (thr) =>
140
156
fail(lineNumber= rowIndex+ 2 , message= s " Could not construct value for column ' ${arg.name}' of type ' ${arg.typeDescription}' from ' $value' " , Some (thr))
141
157
}
@@ -152,16 +168,24 @@ object Metric extends LazyLogging {
152
168
}
153
169
}
154
170
171
+ /** Reads metrics from the given path. The first line should be the header with the field names. Each subsequent
172
+ * line should be a single metric. */
173
+ def iterator [T <: Metric ](path : Path , collectionDelimiter : Char )(implicit tt : ru.TypeTag [T ]): Iterator [T ] = iterator[T ](Io .readLines(path), Some (path.toString), collectionDelimiter)
174
+
155
175
/** Reads metrics from the given path. The first line should be the header with the field names. Each subsequent
156
176
* line should be a single metric. */
157
177
def iterator [T <: Metric ](path : Path )(implicit tt : ru.TypeTag [T ]): Iterator [T ] = iterator[T ](Io .readLines(path), Some (path.toString))
158
178
159
179
/** Reads metrics from a set of lines. The first line should be the header with the field names. Each subsequent
160
180
* line should be a single metric. */
161
- def read [T <: Metric ](lines : Iterator [String ], source : Option [String ] = None )(implicit tt : ru.TypeTag [T ]): Seq [T ] = {
162
- iterator(lines, source).toSeq
181
+ def read [T <: Metric ](lines : Iterator [String ], source : Option [String ] = None , collectionDelimiter : Char = DefaultCollectionDelimiter )(implicit tt : ru.TypeTag [T ]): Seq [T ] = {
182
+ iterator(lines, source, collectionDelimiter ).toSeq
163
183
}
164
184
185
+ /** Reads metrics from the given path. The first line should be the header with the field names. Each subsequent
186
+ * line should be a single metric. */
187
+ def read [T <: Metric ](path : Path , collectionDelimiter : Char )(implicit tt : ru.TypeTag [T ]): Seq [T ] = read[T ](Io .readLines(path), Some (path.toString), collectionDelimiter)
188
+
165
189
/** Reads metrics from the given path. The first line should be the header with the field names. Each subsequent
166
190
* line should be a single metric. */
167
191
def read [T <: Metric ](path : Path )(implicit tt : ru.TypeTag [T ]): Seq [T ] = read[T ](Io .readLines(path), Some (path.toString))
@@ -234,6 +258,9 @@ trait Metric extends Product with Iterable[(String,String)] {
234
258
/** Gets an iterator over the fields of this metric in the order they were defined. Returns tuples of names and values */
235
259
override def iterator : Iterator [(String ,String )] = this .names.zip(this .values).iterator
236
260
261
+ /** The delimiter for collection types. */
262
+ protected def collectionDelimiter : Char = Metric .DefaultCollectionDelimiter
263
+
237
264
/** @deprecated use [[formatValue ]] instead. */
238
265
@ deprecated(message= " Use formatValue instead." , since= " 0.5.0" )
239
266
protected def formatValues (value : Any ): String = formatValue(value)
@@ -251,6 +278,27 @@ trait Metric extends Product with Iterable[(String,String)] {
251
278
case d : Double if d.isNaN || d.isInfinity => d.toString
252
279
case d : Double => Metric .BigDoubleFormat .synchronized { Metric .BigDoubleFormat .format(d) }
253
280
case e : EnumEntry => e.entryName
281
+ case other if ReflectionUtil .isCollectionClass(other.getClass) =>
282
+ val resultType = other.getClass
283
+ // Condition for the collection type
284
+ val collection : Seq [String ] = if (ReflectionUtil .isJavaCollectionClass(resultType)) {
285
+ other.asInstanceOf [java.util.Collection [AnyRef ]].map(formatValue).toSeq
286
+ }
287
+ else if (ReflectionUtil .isSeqClass(resultType) || ReflectionUtil .isSetClass(resultType)) {
288
+ other.asInstanceOf [Iterable [_]].map(formatValue).toList
289
+ }
290
+ else {
291
+ throw new IllegalArgumentException (s " Unknown collection type ' ${resultType.getSimpleName}' " )
292
+ }
293
+ // No commas in the values allowed.
294
+ if (collection.exists(_.contains(collectionDelimiter))) {
295
+ throw new IllegalArgumentException (s " Metric collection value contained a comma: $value" )
296
+ }
297
+ if (collection.isEmpty) {
298
+ ReflectionUtil .SpecialEmptyOrNoneToken
299
+ } else {
300
+ collection.mkString(collectionDelimiter.toString)
301
+ }
254
302
case other => other.toString
255
303
}
256
304
0 commit comments