All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.bijection.StringInjections.scala Maven / Gradle / Ivy

The newest version!
/*
Copyright 2012 Twitter, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
 */

package com.twitter.bijection

import com.twitter.bijection.Inversion.attempt
import java.net.{URLDecoder, URLEncoder, URL}
import java.nio.charset.{Charset, CharsetDecoder, CoderResult, CodingErrorAction}
import java.nio.{ByteBuffer, CharBuffer}
import java.util.UUID
import scala.util.Try

trait StringInjections extends NumericInjections {
  import StringCodec.URLEncodedString

  implicit val utf8: Injection[String, Array[Byte]] = withEncoding("UTF-8")

  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
        }
    }

  // Some bijections with string from standard java/scala classes:
  implicit val url2String: Injection[URL, String] =
    new AbstractInjection[URL, String] {
      def apply(u: URL) = u.toString
      override def invert(s: String) = attempt(s)(new URL(_))
    }

  implicit val uuid2String: Injection[UUID, String] =
    new AbstractInjection[UUID, String] {
      def apply(uuid: UUID) = uuid.toString
      override def invert(s: String) = attempt(s)(UUID.fromString(_))
    }

  implicit val string2UrlEncodedString: Injection[String, URLEncodedString] =
    new AbstractInjection[String, URLEncodedString] {
      override def apply(a: String): URLEncodedString =
        URLEncodedString(URLEncoder.encode(a, "UTF-8"))

      override def invert(b: URLEncodedString): Try[String] =
        attempt(b)(s => URLDecoder.decode(s.encodedString, "UTF-8"))
    }
}

object StringCodec extends StringInjections {
  case class URLEncodedString(encodedString: String) extends AnyVal
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy