Skip to content

Commit

Permalink
Merge pull request #177 from KarelCemus/bytes
Browse files Browse the repository at this point in the history
Support of plain arrays in JavaRedis
  • Loading branch information
KarelCemus authored Jul 11, 2018
2 parents 1960f1a + 276cf79 commit 24afc73
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@

## Changelog

### [:link: 2.2.0](https://github.com/KarelCemus/play-redis/tree/2.2.0)

Support of plain arrays in JavaRedis [#176](https://github.com/KarelCemus/play-redis/pull/176).

### [:link: 2.1.2](https://github.com/KarelCemus/play-redis/tree/2.1.2)

JDK 10 compatibility. Replace deprecated com.sun.misc.BASE64* usages with jdk8 java.util.Base64 [#170](https://github.com/KarelCemus/play-redis/pull/170).
Expand Down
29 changes: 26 additions & 3 deletions src/main/scala/play/api/cache/redis/impl/JavaRedis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
// set the value
internal.set( key, value, duration ),
// and set its type to be able to read it
internal.set( s"classTag::$key", if ( value == null ) "" else value.getClass.getCanonicalName, duration )
internal.set( s"classTag::$key", classTagOf( value ), duration )
)
).map {
case Seq( done, _ ) => done
Expand All @@ -57,8 +57,7 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
implicit val context = HttpExecutionContext.fromThread( runtime.context )
// get the tag and decode it
def getClassTag = internal.get[ String ]( s"classTag::$key" )
def decodeClassTag( name: String ): ClassTag[ T ] = if ( name == "" ) ClassTag.Null.asInstanceOf[ ClassTag[ T ] ] else ClassTag( environment.classLoader.loadClass( name ) )
def decodedClassTag( tag: Option[ String ] ) = tag.map( decodeClassTag )
def decodedClassTag( tag: Option[ String ] ) = tag.map( classTagFrom[ T ] )
// if tag is defined, get Option[ value ] otherwise None
def getValue = getClassTag.map( decodedClassTag ).flatMap {
case Some( ClassTag.Null ) => Future.successful( Some( null.asInstanceOf[ T ] ) )
Expand All @@ -81,6 +80,15 @@ private[ impl ] class JavaRedis( internal: CacheAsyncApi, environment: Environme
}

def removeAll( ) = internal.invalidate().toJava

protected def classTagOf( value: Any ): String = {
if ( value == null ) "" else value.getClass.getCanonicalName
}

protected def classTagFrom[ T ]( tag: String ): ClassTag[ T ] = {
if ( tag == "" ) ClassTag.Null.asInstanceOf[ ClassTag[ T ] ]
else ClassTag( classTagNameToClass( tag, environment ) )
}
}

private[ impl ] object JavaRedis {
Expand All @@ -93,4 +101,19 @@ private[ impl ] object JavaRedis {
private[ impl ] implicit class ScalaCompatibility[ T ]( val future: CompletionStage[ T ] ) extends AnyVal {
@inline def toScala: Future[ T ] = FutureConverters.toScala( future )
}

// $COVERAGE-OFF$
/** java primitives are serialized into their type names instead of classes */
private[ impl ] def classTagNameToClass( name: String, environment: Environment ): Class[ _ ] = name match {
case "boolean[]" => classOf[ Array[ java.lang.Boolean ] ]
case "byte[]" => classOf[ Array[ java.lang.Byte ] ]
case "char[]" => classOf[ Array[ java.lang.Character ] ]
case "short[]" => classOf[ Array[ java.lang.Short ] ]
case "int[]" => classOf[ Array[ java.lang.Integer ] ]
case "long[]" => classOf[ Array[ java.lang.Long ] ]
case "float[]" => classOf[ Array[ java.lang.Float ] ]
case "double[]" => classOf[ Array[ java.lang.Double ] ]
case clazz => environment.classLoader.loadClass( clazz )
}
// $COVERAGE-ON$
}
10 changes: 10 additions & 0 deletions src/test/java/play/api/cache/redis/JavaTypes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package play.api.cache.redis;

/**
* @author Karel Cemus
*/
public class JavaTypes {

public static final byte byteValue = 5;
public static final byte[] bytesValue = new byte[]{1, 2, 3};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.util.Date
import scala.reflect.ClassTag

import play.api.inject.guice.GuiceApplicationBuilder
import play.api.cache.redis._

import org.joda.time.{DateTime, DateTimeZone}
import org.specs2.mock.Mockito
Expand All @@ -24,6 +25,11 @@ class SerializerSpecs extends Specification with Mockito {

"byte" in {
0xAB.toByte.encoded mustEqual "-85"
JavaTypes.byteValue.encoded mustEqual "5"
}

"byte[]" in {
JavaTypes.bytesValue.encoded mustEqual "AQID"
}

"char" in {
Expand Down Expand Up @@ -93,7 +99,6 @@ class SerializerSpecs extends Specification with Mockito {
|TGlzdFNlcmlhbGl6ZUVuZCSKXGNb91MLbQIAAHhweA==
""".stripMargin.lines.map(_.trim).mkString
}

}

"AkkaDecoder" should "decode" >> {
Expand All @@ -102,6 +107,10 @@ class SerializerSpecs extends Specification with Mockito {
"-85".decoded[ Byte ] mustEqual 0xAB.toByte
}

"byte[]" in {
"YWJj".decoded[ Array[ Byte ] ] mustEqual Array( "a".head.toByte, "b".head.toByte, "c".head.toByte )
}

"char" in {
"a".decoded[ Char ] mustEqual 'a'
"b".decoded[ Char ] mustEqual 'b'
Expand Down
32 changes: 30 additions & 2 deletions src/test/scala/play/api/cache/redis/impl/JavaRedisSpecs.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package play.api.cache.redis.impl

import java.util.concurrent.Callable

import scala.concurrent.duration.Duration

import play.api.cache.redis._
Expand Down Expand Up @@ -105,5 +103,35 @@ class JavaRedisSpecs( implicit ee: ExecutionEnv ) extends Specification with Red
async.invalidate() returns execDone
cache.removeAll().toScala must beDone.await
}

"get and set 'byte'" in new MockedJavaRedis {
val byte = JavaTypes.byteValue

// set a value
// note: there should be hit on "byte" but the value is wrapped instead
async.set( anyString, beEq( byte ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "byte" ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "java.lang.Byte" ), any[ Duration ] ) returns execDone
cache.set( key, byte ).toScala must beDone.await

// hit on GET
async.get[ Byte ]( beEq( key ) )( anyClassTag ) returns Some( byte )
async.get[ String ]( beEq( s"classTag::$key" ) )( anyClassTag ) returns Some( "java.lang.Byte" )
cache.get[ Byte ]( key ).toScala must beEqualTo( byte ).await
}

"get and set 'byte[]'" in new MockedJavaRedis {
val bytes = JavaTypes.bytesValue

// set a value
async.set( anyString, beEq( bytes ), any[ Duration ] ) returns execDone
async.set( anyString, beEq( "byte[]" ), any[ Duration ] ) returns execDone
cache.set( key, bytes ).toScala must beDone.await

// hit on GET
async.get[ Array[ Byte ] ]( beEq( key ) )( anyClassTag ) returns Some( bytes )
async.get[ String ]( beEq( s"classTag::$key" ) )( anyClassTag ) returns Some( "byte[]" )
cache.get[ Array[ Byte ] ]( key ).toScala must beEqualTo( bytes ).await
}
}
}

0 comments on commit 24afc73

Please sign in to comment.