string_bytes: write strings using new API
authorTrevor Norris <trev.norris@gmail.com>
Tue, 11 Jun 2013 21:30:16 +0000 (14:30 -0700)
committerTrevor Norris <trev.norris@gmail.com>
Wed, 12 Jun 2013 21:43:37 +0000 (14:43 -0700)
StringBytes::Write now uses new v8 API and also does preliminary check
if the string is external, then will use external memory instead.

src/string_bytes.cc
test/simple/test-stringbytes-external.js [new file with mode: 0644]

index e162fd7..416bc5a 100644 (file)
@@ -36,9 +36,9 @@
 
 namespace node {
 
-using v8::Local;
 using v8::Handle;
 using v8::HandleScope;
+using v8::Local;
 using v8::Object;
 using v8::String;
 using v8::Value;
@@ -227,41 +227,65 @@ static inline size_t hex_decode(char* buf,
 }
 
 
+bool GetExternalParts(Handle<Value> val, const char** data, size_t* len) {
+  if (Buffer::HasInstance(val)) {
+    *data = Buffer::Data(val);
+    *len = Buffer::Length(val);
+    return true;
+
+  }
+
+  assert(val->IsString());
+  Local<String> str = Local<String>::New(val.As<String>());
+
+  if (str->IsExternalAscii()) {
+    const String::ExternalAsciiStringResource* ext;
+    ext = str->GetExternalAsciiStringResource();
+    *data = ext->data();
+    *len = ext->length();
+    return true;
+
+  } else if (str->IsExternal()) {
+    const String::ExternalStringResource* ext;
+    ext = str->GetExternalStringResource();
+    *data = reinterpret_cast<const char*>(ext->data());
+    *len = ext->length();
+    return true;
+  }
+
+  return false;
+}
+
+
 size_t StringBytes::Write(char* buf,
                           size_t buflen,
                           Handle<Value> val,
                           enum encoding encoding,
                           int* chars_written) {
-
   HandleScope scope(node_isolate);
+  const char* data;
   size_t len = 0;
-  bool is_buffer = Buffer::HasInstance(val);
-
-  // sometimes we use 'binary' when we mean 'buffer'
-  if (is_buffer && (encoding == BINARY || encoding == BUFFER)) {
-    // fast path, copy buffer data
-    Local<Object> valObj = Local<Object>::New(val.As<Object>());
-    const char* data = Buffer::Data(valObj);
-    size_t size = Buffer::Length(valObj);
-    size_t len = size < buflen ? size : buflen;
-    memcpy(buf, data, len);
-    return len;
-  }
+  bool is_extern = GetExternalParts(val, &data, &len);
 
   Local<String> str = val->ToString();
+  len = len < buflen ? len : buflen;
 
   int flags = String::NO_NULL_TERMINATION |
               String::HINT_MANY_WRITES_EXPECTED;
 
   switch (encoding) {
     case ASCII:
-      len = str->WriteOneByte(reinterpret_cast<uint8_t*>(buf),
-                              0,
-                              buflen,
-                              flags);
-      if (chars_written != NULL) {
+    case BINARY:
+    case BUFFER:
+      if (is_extern)
+        memcpy(buf, data, len);
+      else
+        len = str->WriteOneByte(reinterpret_cast<uint8_t*>(buf),
+                                0,
+                                buflen,
+                                flags);
+      if (chars_written != NULL)
         *chars_written = len;
-      }
       break;
 
     case UTF8:
@@ -269,50 +293,38 @@ size_t StringBytes::Write(char* buf,
       break;
 
     case UCS2:
-      len = str->Write(reinterpret_cast<uint16_t*>(buf), 0, buflen, flags);
-      if (chars_written != NULL) {
+      if (is_extern)
+        memcpy(buf, data, len * 2);
+      else
+        len = str->Write(reinterpret_cast<uint16_t*>(buf), 0, buflen, flags);
+      if (chars_written != NULL)
         *chars_written = len;
-      }
       len = len * sizeof(uint16_t);
       break;
 
-    case BASE64: {
-      String::AsciiValue value(str);
-      len = base64_decode(buf, buflen, *value, value.length());
-      if (chars_written != NULL) {
-        *chars_written = len;
-      }
-      break;
-    }
-
-    case BINARY:
-    case BUFFER: {
-      // TODO(isaacs): THIS IS AWFUL!!!
-      uint16_t* twobytebuf = new uint16_t[buflen];
-
-      len = str->Write(twobytebuf, 0, buflen, flags);
-
-      for (size_t i = 0; i < buflen && i < len; i++) {
-        unsigned char* b = reinterpret_cast<unsigned char*>(&twobytebuf[i]);
-        buf[i] = b[0];
+    case BASE64:
+      if (is_extern) {
+        base64_decode(buf, buflen, data, len);
+      } else {
+        String::AsciiValue value(str);
+        len = base64_decode(buf, buflen, *value, value.length());
       }
-
       if (chars_written != NULL) {
         *chars_written = len;
       }
-
-      delete[] twobytebuf;
       break;
-    }
 
-    case HEX: {
-      String::AsciiValue value(str);
-      len = hex_decode(buf, buflen, *value, value.length());
+    case HEX:
+      if (is_extern) {
+        hex_decode(buf, buflen, data, len);
+      } else {
+        String::AsciiValue value(str);
+        len = hex_decode(buf, buflen, *value, value.length());
+      }
       if (chars_written != NULL) {
         *chars_written = len * 2;
       }
       break;
-    }
 
     default:
       assert(0 && "unknown encoding");
diff --git a/test/simple/test-stringbytes-external.js b/test/simple/test-stringbytes-external.js
new file mode 100644 (file)
index 0000000..87823cd
--- /dev/null
@@ -0,0 +1,74 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+// minimum string size to overflow into external string space
+var EXTERN_APEX = 0xFBEE9;
+
+// manually controlled string for checking binary output
+var ucs2_control = 'a\u0000';
+var write_str = 'a';
+
+
+// first do basic checks
+var b = new Buffer(write_str, 'ucs2');
+var c = b.toString('binary');
+assert.equal(b[0], 0x61);
+assert.equal(b[1], 0);
+assert.equal(ucs2_control, c);
+
+
+// grow the strings to proper length
+while (write_str.length <= EXTERN_APEX) {
+  write_str += write_str;
+  ucs2_control += ucs2_control;
+}
+write_str += write_str.substr(0, EXTERN_APEX - write_str.length);
+ucs2_control += ucs2_control.substr(0, EXTERN_APEX * 2 - ucs2_control.length);
+
+
+// check resultant buffer and output string
+var b = new Buffer(write_str, 'ucs2');
+// check fist Buffer created from write string
+for (var i = 0; i < b.length; i += 2) {
+  assert.equal(b[i], 0x61);
+  assert.equal(b[i + 1], 0);
+}
+// create another string to create an external string
+var b_bin = b.toString('binary');
+var b_ucs = b.toString('ucs2');
+// check control against external binary string
+assert.equal(ucs2_control, b_bin);
+// create buffer copy from external
+var c_bin = new Buffer(b_bin, 'binary');
+var c_ucs = new Buffer(b_ucs, 'ucs2');
+// make sure they're the same length
+assert.equal(c_bin.length, c_ucs.length);
+// make sure Buffers from externals are the same
+for (var i = 0; i < c_bin.length; i++) {
+  assert.equal(c_bin[i], c_ucs[i], c_bin[i] + ' == ' + c_ucs[i] +
+               ' : index ' + i);
+}
+// check resultant strings
+assert.equal(c_bin.toString('ucs2'), c_ucs.toString('ucs2'));
+assert.equal(c_bin.toString('binary'), ucs2_control);
+assert.equal(c_ucs.toString('binary'), ucs2_control);