Skip to content
This repository was archived by the owner on Mar 8, 2020. It is now read-only.

Commit 5668ebd

Browse files
authored
Merge pull request #101 from bzz/api-update
High-level helpers around UAST decode/load
2 parents 198e7ba + c7b6e9e commit 5668ebd

File tree

5 files changed

+190
-7
lines changed

5 files changed

+190
-7
lines changed

src/main/scala/org/bblfsh/client/v2/BblfshClient.scala

+16-3
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,13 @@ object BblfshClient {
130130
libuast.decode(buf)
131131
}
132132

133-
// Enables API: resp.uast.decode()
133+
/** Enables API: resp.uast.decode() */
134134
implicit class UastMethods(val buf: ByteString) {
135135
/**
136-
* Decodes bytes from wired format of bblfsh protocol.v2.
137-
* Copies a buffer in Direct mode.
136+
* Decodes bytes from wire format of bblfsh protocol.v2.
137+
*
138+
* Always copies memory to a new buffer in Direct mode,
139+
* to be able to pass it to JNI.
138140
*/
139141
def decode(): ContextExt = {
140142
val bufDirectCopy = ByteBuffer.allocateDirect(buf.size)
@@ -143,6 +145,17 @@ object BblfshClient {
143145
}
144146
}
145147

148+
/** Enables API: resp.get() */
149+
implicit class ResponseMethods(val resp: ParseResponse) {
150+
def get(): JNode = {
151+
val ctx = resp.uast.decode()
152+
val node = ctx.root().load()
153+
ctx.dispose()
154+
node
155+
}
156+
}
157+
158+
146159
// TODO(bzz): implement iterator
147160
// def iterator(node: NodeExt, treeOrder: Int): Libuast.NodeIterator = {
148161
// new Libuast.NodeIterator(node, treeOrder)

src/main/scala/org/bblfsh/client/v2/NodeExt.scala

+64-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.bblfsh.client.v2
22

33
import java.io.Serializable
4+
import java.nio.ByteBuffer
45

56
import scala.collection.mutable
67

@@ -23,6 +24,21 @@ case class NodeExt(ctx: Long, handle: Long) {
2324
* This is equivalent of pyuast.Node API.
2425
*/
2526
sealed abstract class JNode {
27+
def toByteArray: Array[Byte] = {
28+
val buf = toByteBuffer
29+
val arr = new Array[Byte](buf.capacity())
30+
buf.get(arr)
31+
buf.rewind()
32+
arr
33+
}
34+
35+
def toByteBuffer: ByteBuffer = {
36+
val ctx = Context()
37+
val bb = ctx.encode(this)
38+
ctx.dispose()
39+
bb
40+
}
41+
2642
/* Dynamic dispatch is a convenience to be called from JNI */
2743
def children: Seq[JNode] = this match {
2844
case JObject(l) => l map (_._2)
@@ -49,10 +65,57 @@ sealed abstract class JNode {
4965
}
5066

5167
def apply(k: String): JNode = this match {
52-
case o: JObject => o.obj.filter( _._1 == k ).head._2
68+
case o: JObject => o.obj.filter(_._1 == k).head._2
5369
case _ => JNothing
5470
}
71+
}
72+
73+
object JNode {
74+
private def decodeFrom(bytes: ByteBuffer): JNode = {
75+
val ctx = BblfshClient.decode(bytes)
76+
val node = ctx.root().load()
77+
ctx.dispose()
78+
node
79+
}
5580

81+
/**
82+
* Decodes UAST from the given Buffer.
83+
*
84+
* If the buffer is Direct, it will avoid extra memory allocation,
85+
* otherwise it will copy the content to a new Direct buffer.
86+
*
87+
* @param original UAST encoded in wire format of protocol.v2
88+
* @return JNode of the UAST root
89+
*/
90+
def parseFrom(original: ByteBuffer): JNode = {
91+
val bufDirect = if (!original.isDirect) {
92+
val bufDirectCopy = ByteBuffer.allocateDirect(original.capacity())
93+
original.rewind()
94+
bufDirectCopy.put(original)
95+
original.rewind()
96+
bufDirectCopy.flip()
97+
bufDirectCopy
98+
} else {
99+
original
100+
}
101+
decodeFrom(bufDirect)
102+
}
103+
104+
/**
105+
* Decodes UAST from the given bytes.
106+
*
107+
* It will copy memory into temporary Direct buffer,
108+
* otherwise it will copy the content to a new Direct buffer.
109+
*
110+
* @param bytes UAST encoded in wire format of protocol.v2
111+
* @return JNode of the UAST root
112+
*/
113+
def parseFrom(bytes: Array[Byte]): JNode = {
114+
val bufDirect = ByteBuffer.allocateDirect(bytes.size)
115+
bufDirect.put(bytes)
116+
bufDirect.flip()
117+
decodeFrom(bufDirect)
118+
}
56119
}
57120

58121
case object JNothing extends JNode // 'zero' value for JNode

src/test/scala/org/bblfsh/client/v2/BblfshClientLoadTest.scala

+4-3
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ class BblfshClientLoadTest extends BblfshClientBaseTest {
1717
val root = rootNode.load()
1818

1919
root should not be Nil
20-
root.getClass shouldBe classOf[JObject]
20+
root shouldBe a [JObject]
2121
root.children should not be empty
2222
root.children.size shouldBe 6
2323

2424
val arr = root.children(1)
2525
arr should not be (null)
26-
arr.getClass shouldBe classOf[JArray]
26+
arr shouldBe a [JArray]
2727
arr.children.size shouldBe 1
2828

2929
val str = arr.children(0)
30-
str.getClass shouldBe classOf[JString]
30+
str shouldBe a[JString]
3131

3232
val nil = root("imports")
3333
nil should be (JNull())
@@ -48,6 +48,7 @@ class BblfshClientLoadTest extends BblfshClientBaseTest {
4848

4949
val ctx = Context()
5050
val bb: ByteBuffer = ctx.encode(rootTree)
51+
ctx.dispose()
5152

5253
bb should not be (null)
5354
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.bblfsh.client.v2
2+
3+
import java.nio.ByteBuffer
4+
5+
import com.google.protobuf.ByteString
6+
7+
8+
/**
9+
* Tests for high-level API wrappers around decode/load,
10+
* checking result equivalence to the low-level counterpart.
11+
*/
12+
class BblfshClientUastApiTest extends BblfshClientBaseTest {
13+
14+
import BblfshClient._ // enables uast.* methods
15+
16+
override val fileName = "src/test/resources/Tiny.java"
17+
18+
// Depends on having bblfshd JavaScript and Java drivers
19+
"SupportedLanguages" should "include aliases" in {
20+
val resp = client.supportedLanguages()
21+
22+
val supportedLangs = resp.languages.flatMap(x => Seq(x.language) ++ x.aliases)
23+
24+
supportedLangs.length should be > resp.languages.length
25+
resp.languages.size should be >= 2
26+
}
27+
28+
"Parse + decode + load UAST" should "result in new JNode" in {
29+
val node1 = resp.uast.decode.root.load()
30+
31+
val node2 = resp.get()
32+
33+
node2 should not be (null)
34+
node2 shouldBe a [JNode]
35+
node1 should equal(node2)
36+
}
37+
38+
"Parse + get binary" should "result in ByteBuffer" in {
39+
resp.uast shouldBe a [ByteString]
40+
}
41+
42+
"Decode binary to JVM memory" should "result in new JNode" in {
43+
val bytes: Array[Byte] = resp.uast.toByteArray
44+
val node1 = resp.uast.decode.root.load()
45+
46+
val node2 = JNode.parseFrom(bytes)
47+
node2 should not be (null)
48+
node2 shouldBe a [JNode]
49+
node1 should equal(node2)
50+
51+
val node3 = JNode.parseFrom(resp.uast.asReadOnlyByteBuffer())
52+
node3 should not be (null)
53+
node3 shouldBe a [JNode]
54+
node1 should equal(node3)
55+
}
56+
57+
"Encode JNode to the binary" should "result in bytes" in {
58+
val node: JNode = JArray(
59+
JObject(
60+
"k1" -> JString("v1")
61+
),
62+
JString("test")
63+
)
64+
65+
val ctx = Context()
66+
val bytes1 = ctx.encode(node)
67+
ctx.dispose()
68+
69+
val bytes2 = node.toByteBuffer
70+
bytes2 should not be (null)
71+
bytes2 shouldBe a[ByteBuffer]
72+
bytes2 should equal(bytes1)
73+
74+
val bytes3 = node.toByteArray
75+
bytes3 should not be (null)
76+
bytes3 shouldBe a[Array[Byte]]
77+
ByteBuffer.wrap(bytes3) should equal(bytes1)
78+
}
79+
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package org.bblfsh.client.v2
2+
3+
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers}
4+
5+
import scala.io.Source
6+
7+
class ContextTest extends FlatSpec
8+
with BeforeAndAfter
9+
with Matchers {
10+
11+
12+
"ContextExt" should "be able to .dispose() twice" in {
13+
val client = BblfshClient("localhost", 9432)
14+
val fileName = "src/test/resources/SampleJavaFile.java"
15+
val fileContent = Source.fromFile(fileName).getLines.mkString("\n")
16+
val resp = client.parse(fileName, fileContent)
17+
18+
import BblfshClient._ // enables uast.* methods
19+
20+
val ctx = resp.uast.decode()
21+
ctx.dispose()
22+
ctx.dispose()
23+
}
24+
25+
26+
}

0 commit comments

Comments
 (0)