in bijection-core/src/main/scala/com/twitter/bijection/StringInjections.scala [31:73]
def withEncoding(encoding: String): Injection[String, Array[Byte]] =
new AbstractInjection[String, Array[Byte]] {
@transient private[this] var decRef: AtomicSharedState[(CharsetDecoder, CharBuffer)] = null
private[this] def mkSharedState =
new AtomicSharedState({ () =>
val dec =
Charset.forName(encoding).newDecoder.onUnmappableCharacter(CodingErrorAction.REPORT)
val buf = CharBuffer.allocate(1024) // something big enough, if not big enough, allocate
(dec, buf)
})
def apply(s: String) = s.getBytes(encoding)
override def invert(b: Array[Byte]) =
// toString on ByteBuffer is nicer, so use it
attempt(ByteBuffer.wrap(b)) { bb =>
// these are mutable, so it can't be shared trivially
// avoid GC pressure and (probably) perform better
if (null == decRef) {
decRef = mkSharedState
}
val decBuf = decRef.get
val dec = decBuf._1
val buf = decBuf._2
val maxSpace = (b.length * (dec.maxCharsPerByte.toDouble)).toInt + 1
val thisBuf = if (maxSpace > buf.limit()) CharBuffer.allocate(maxSpace) else buf
// this is the error free result
@inline def assertUnderFlow(cr: CoderResult): Unit =
if (!cr.isUnderflow) cr.throwException
assertUnderFlow(dec.reset.decode(bb, thisBuf, true))
assertUnderFlow(dec.flush(thisBuf))
// set the limit to be the position
thisBuf.flip
val str = thisBuf.toString
// make sure the buffer we store is clear.
buf.clear
// we don't reset with the larger buffer to avoid memory leaks
decRef.release(decBuf)
str
}
}