Add dbus::PopDataAsValue

BUG=chromium-os:16557
TEST=dbus_unittests --gtest_filter="ValuesUtilTest.*"


Review URL: http://codereview.chromium.org/9702094

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@127287 0039d316-1c4b-4281-b951-d872f2087c98


CrOS-Libchrome-Original-Commit: a5aae12d00dd5efc48921d180e94208841a49f35
diff --git a/dbus/dbus.gyp b/dbus/dbus.gyp
index 296ac53..0e30503 100644
--- a/dbus/dbus.gyp
+++ b/dbus/dbus.gyp
@@ -32,6 +32,8 @@
         'property.cc',
         'property.h',
         'scoped_dbus_error.h',
+        'values_util.cc',
+        'values_util.h',
       ],
     },
     {
@@ -88,6 +90,7 @@
         'property_unittest.cc',
         'test_service.cc',
         'test_service.h',
+        'values_util_unittest.cc',
       ],
       'include_dirs': [
         '..',
diff --git a/dbus/values_util.cc b/dbus/values_util.cc
new file mode 100644
index 0000000..c422374
--- /dev/null
+++ b/dbus/values_util.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/values_util.h"
+
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "dbus/message.h"
+
+namespace dbus {
+
+namespace {
+
+// Returns whether |value| is exactly representable by double or not.
+template<typename T>
+bool IsExactlyRepresentableByDouble(T value) {
+  return value == static_cast<T>(static_cast<double>(value));
+}
+
+// Pops values from |reader| and appends them to |list_value|.
+bool PopListElements(MessageReader* reader, ListValue* list_value) {
+  while (reader->HasMoreData()) {
+    Value* element_value = PopDataAsValue(reader);
+    if (!element_value)
+      return false;
+    list_value->Append(element_value);
+  }
+  return true;
+}
+
+// Pops dict-entries from |reader| and sets them to |dictionary_value|
+bool PopDictionaryEntries(MessageReader* reader,
+                          DictionaryValue* dictionary_value) {
+  while (reader->HasMoreData()) {
+    DCHECK_EQ(Message::DICT_ENTRY, reader->GetDataType());
+    MessageReader entry_reader(NULL);
+    if (!reader->PopDictEntry(&entry_reader))
+      return false;
+    // Get key as a string.
+    std::string key_string;
+    if (entry_reader.GetDataType() == Message::STRING) {
+      // If the type of keys is STRING, pop it directly.
+      if (!entry_reader.PopString(&key_string))
+        return false;
+    } else {
+      // If the type of keys is not STRING, convert it to string.
+      scoped_ptr<Value> key(PopDataAsValue(&entry_reader));
+      if (!key.get())
+        return false;
+      // Use JSONWriter to convert an arbitrary value to a string.
+      base::JSONWriter::Write(key.get(), &key_string);
+    }
+    // Get the value and set the key-value pair.
+    Value* value = PopDataAsValue(&entry_reader);
+    if (!value)
+      return false;
+    dictionary_value->Set(key_string, value);
+  }
+  return true;
+}
+
+}  // namespace
+
+Value* PopDataAsValue(MessageReader* reader) {
+  Value* result = NULL;
+  switch (reader->GetDataType()) {
+    case Message::INVALID_DATA:
+      // Do nothing.
+      break;
+    case Message::BYTE: {
+      uint8 value = 0;
+      if (reader->PopByte(&value))
+        result = Value::CreateIntegerValue(value);
+      break;
+    }
+    case Message::BOOL: {
+      bool value = false;
+      if (reader->PopBool(&value))
+        result = Value::CreateBooleanValue(value);
+      break;
+    }
+    case Message::INT16: {
+      int16 value = 0;
+      if (reader->PopInt16(&value))
+        result = Value::CreateIntegerValue(value);
+      break;
+    }
+    case Message::UINT16: {
+      uint16 value = 0;
+      if (reader->PopUint16(&value))
+        result = Value::CreateIntegerValue(value);
+      break;
+    }
+    case Message::INT32: {
+      int32 value = 0;
+      if (reader->PopInt32(&value))
+        result = Value::CreateIntegerValue(value);
+      break;
+    }
+    case Message::UINT32: {
+      uint32 value = 0;
+      if (reader->PopUint32(&value))
+        result = Value::CreateDoubleValue(value);
+      break;
+    }
+    case Message::INT64: {
+      int64 value = 0;
+      if (reader->PopInt64(&value)) {
+        DLOG_IF(WARNING, !IsExactlyRepresentableByDouble(value)) <<
+            value << " is not exactly representable by double";
+        result = Value::CreateDoubleValue(value);
+      }
+      break;
+    }
+    case Message::UINT64: {
+      uint64 value = 0;
+      if (reader->PopUint64(&value)) {
+        DLOG_IF(WARNING, !IsExactlyRepresentableByDouble(value)) <<
+            value << " is not exactly representable by double";
+        result = Value::CreateDoubleValue(value);
+      }
+      break;
+    }
+    case Message::DOUBLE: {
+      double value = 0;
+      if (reader->PopDouble(&value))
+        result = Value::CreateDoubleValue(value);
+      break;
+    }
+    case Message::STRING: {
+      std::string value;
+      if (reader->PopString(&value))
+        result = Value::CreateStringValue(value);
+      break;
+    }
+    case Message::OBJECT_PATH: {
+      ObjectPath value;
+      if (reader->PopObjectPath(&value))
+        result = Value::CreateStringValue(value.value());
+      break;
+    }
+    case Message::ARRAY: {
+      MessageReader sub_reader(NULL);
+      if (reader->PopArray(&sub_reader)) {
+        // If the type of the array's element is DICT_ENTRY, create a
+        // DictionaryValue, otherwise create a ListValue.
+        if (sub_reader.GetDataType() == Message::DICT_ENTRY) {
+          scoped_ptr<DictionaryValue> dictionary_value(new DictionaryValue);
+          if (PopDictionaryEntries(&sub_reader, dictionary_value.get()))
+            result = dictionary_value.release();
+        } else {
+          scoped_ptr<ListValue> list_value(new ListValue);
+          if (PopListElements(&sub_reader, list_value.get()))
+            result = list_value.release();
+        }
+      }
+      break;
+    }
+    case Message::STRUCT: {
+      MessageReader sub_reader(NULL);
+      if (reader->PopStruct(&sub_reader)) {
+        scoped_ptr<ListValue> list_value(new ListValue);
+        if (PopListElements(&sub_reader, list_value.get()))
+          result = list_value.release();
+      }
+      break;
+    }
+    case Message::DICT_ENTRY:
+      // DICT_ENTRY must be popped as an element of an array.
+      NOTREACHED();
+      break;
+    case Message::VARIANT: {
+      MessageReader sub_reader(NULL);
+      if (reader->PopVariant(&sub_reader))
+        result = PopDataAsValue(&sub_reader);
+      break;
+    }
+  }
+  return result;
+}
+
+}  // namespace dbus
diff --git a/dbus/values_util.h b/dbus/values_util.h
new file mode 100644
index 0000000..cfb31a5
--- /dev/null
+++ b/dbus/values_util.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DBUS_VALUES_UTIL_H_
+#define DBUS_VALUES_UTIL_H_
+#pragma once
+
+namespace base {
+class Value;
+}
+
+namespace dbus {
+
+class MessageReader;
+
+// Pops a value from |reader| as a base::Value.
+// Returns NULL if an error occurs.
+// Note: Integer values larger than int32 (including uint32) are converted to
+// double.  Non-string diciontary keys are converted to strings.
+base::Value* PopDataAsValue(MessageReader* reader);
+
+}  // namespace dbus
+
+#endif  // DBUS_VALUES_UTIL_H_
diff --git a/dbus/values_util_unittest.cc b/dbus/values_util_unittest.cc
new file mode 100644
index 0000000..1f97cdc
--- /dev/null
+++ b/dbus/values_util_unittest.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "dbus/values_util.h"
+
+#include <vector>
+
+#include "base/float_util.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "dbus/message.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+TEST(ValuesUtilTest, PopBasicTypes) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append basic type values.
+  dbus::MessageWriter writer(response.get());
+  const uint8 kByteValue = 42;
+  writer.AppendByte(kByteValue);
+  const bool kBoolValue = true;
+  writer.AppendBool(kBoolValue);
+  const int16 kInt16Value = -43;
+  writer.AppendInt16(kInt16Value);
+  const uint16 kUint16Value = 44;
+  writer.AppendUint16(kUint16Value);
+  const int32 kInt32Value = -45;
+  writer.AppendInt32(kInt32Value);
+  const uint32 kUint32Value = 46;
+  writer.AppendUint32(kUint32Value);
+  const int64 kInt64Value = -47;
+  writer.AppendInt64(kInt64Value);
+  const uint64 kUint64Value = 48;
+  writer.AppendUint64(kUint64Value);
+  const double kDoubleValue = 4.9;
+  writer.AppendDouble(kDoubleValue);
+  const std::string kStringValue = "fifty";
+  writer.AppendString(kStringValue);
+  const std::string kEmptyStringValue;
+  writer.AppendString(kEmptyStringValue);
+  const dbus::ObjectPath kObjectPathValue("ObjectPath");
+  writer.AppendObjectPath(kObjectPathValue);
+
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value;
+  // Pop a byte.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateIntegerValue(kByteValue)));
+  // Pop a bool.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateBooleanValue(kBoolValue)));
+  // Pop an int16.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateIntegerValue(kInt16Value)));
+  // Pop a uint16.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateIntegerValue(kUint16Value)));
+  // Pop an int32.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateIntegerValue(kInt32Value)));
+  // Pop a uint32.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kUint32Value)));
+  // Pop an int64.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kInt64Value)));
+  // Pop a uint64.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kUint64Value)));
+  // Pop a double.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kDoubleValue)));
+  // Pop a string.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateStringValue(kStringValue)));
+  // Pop an empty string.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateStringValue(kEmptyStringValue)));
+  // Pop an object path.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(
+      value->Equals(Value::CreateStringValue(kObjectPathValue.value())));
+}
+
+TEST(ValuesUtilTest, PopVariant) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append variant values.
+  dbus::MessageWriter writer(response.get());
+  const bool kBoolValue = true;
+  writer.AppendVariantOfBool(kBoolValue);
+  const int32 kInt32Value = -45;
+  writer.AppendVariantOfInt32(kInt32Value);
+  const double kDoubleValue = 4.9;
+  writer.AppendVariantOfDouble(kDoubleValue);
+  const std::string kStringValue = "fifty";
+  writer.AppendVariantOfString(kStringValue);
+
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value;
+  // Pop a bool.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateBooleanValue(kBoolValue)));
+  // Pop an int32.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateIntegerValue(kInt32Value)));
+  // Pop a double.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kDoubleValue)));
+  // Pop a string.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateStringValue(kStringValue)));
+}
+
+// Pop extremely large integers which cannot be precisely represented in
+// double.
+TEST(ValuesUtilTest, PopExtremelyLargeIntegers) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append large integers.
+  dbus::MessageWriter writer(response.get());
+  const int64 kInt64Value = -123456789012345689LL;
+  writer.AppendInt64(kInt64Value);
+  const uint64 kUint64Value = 9876543210987654321ULL;
+  writer.AppendUint64(kUint64Value);
+
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value;
+  double double_value = 0;
+  // Pop an int64.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kInt64Value)));
+  ASSERT_TRUE(value->GetAsDouble(&double_value));
+  EXPECT_NE(kInt64Value, static_cast<int64>(double_value));
+  // Pop a uint64.
+  value.reset(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(Value::CreateDoubleValue(kUint64Value)));
+  ASSERT_TRUE(value->GetAsDouble(&double_value));
+  EXPECT_NE(kUint64Value, static_cast<uint64>(double_value));
+}
+
+TEST(ValuesUtilTest, PopIntArray) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append an int32 array.
+  dbus::MessageWriter writer(response.get());
+  dbus::MessageWriter sub_writer(NULL);
+  std::vector<int32> data;
+  data.push_back(0);
+  data.push_back(1);
+  data.push_back(2);
+  writer.OpenArray("i", &sub_writer);
+  for (size_t i = 0; i != data.size(); ++i)
+    sub_writer.AppendInt32(data[i]);
+  writer.CloseContainer(&sub_writer);
+
+  // Create the expected value.
+  ListValue* list_value = new ListValue;
+  for (size_t i = 0; i != data.size(); ++i)
+    list_value->Append(Value::CreateIntegerValue(data[i]));
+
+  // Pop an int32 array.
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(list_value));
+}
+
+TEST(ValuesUtilTest, PopStringArray) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append a string array.
+  dbus::MessageWriter writer(response.get());
+  dbus::MessageWriter sub_writer(NULL);
+  std::vector<std::string> data;
+  data.push_back("Dreamlifter");
+  data.push_back("Beluga");
+  data.push_back("Mriya");
+  writer.AppendArrayOfStrings(data);
+
+  // Create the expected value.
+  ListValue* list_value = new ListValue;
+  for (size_t i = 0; i != data.size(); ++i)
+    list_value->Append(Value::CreateStringValue(data[i]));
+
+  // Pop a string array.
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(list_value));
+}
+
+TEST(ValuesUtilTest, PopStruct) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append a struct.
+  dbus::MessageWriter writer(response.get());
+  dbus::MessageWriter sub_writer(NULL);
+  writer.OpenStruct(&sub_writer);
+  const bool kBoolValue = true;
+  sub_writer.AppendBool(kBoolValue);
+  const int32 kInt32Value = -123;
+  sub_writer.AppendInt32(kInt32Value);
+  const double kDoubleValue = 1.23;
+  sub_writer.AppendDouble(kDoubleValue);
+  const std::string kStringValue = "one two three";
+  sub_writer.AppendString(kStringValue);
+  writer.CloseContainer(&sub_writer);
+
+  // Create the expected value.
+  ListValue list_value;
+  list_value.Append(Value::CreateBooleanValue(kBoolValue));
+  list_value.Append(Value::CreateIntegerValue(kInt32Value));
+  list_value.Append(Value::CreateDoubleValue(kDoubleValue));
+  list_value.Append(Value::CreateStringValue(kStringValue));
+
+  // Pop a struct.
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(&list_value));
+}
+
+TEST(ValuesUtilTest, PopStringToVariantDictionary) {
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  // Append a dictionary.
+  dbus::MessageWriter writer(response.get());
+  dbus::MessageWriter sub_writer(NULL);
+  dbus::MessageWriter entry_writer(NULL);
+  writer.OpenArray("{sv}", &sub_writer);
+  sub_writer.OpenDictEntry(&entry_writer);
+  const std::string kKey1 = "one";
+  entry_writer.AppendString(kKey1);
+  const bool kBoolValue = true;
+  entry_writer.AppendVariantOfBool(kBoolValue);
+  sub_writer.CloseContainer(&entry_writer);
+  sub_writer.OpenDictEntry(&entry_writer);
+  const std::string kKey2 = "two";
+  entry_writer.AppendString(kKey2);
+  const int32 kInt32Value = -45;
+  entry_writer.AppendVariantOfInt32(kInt32Value);
+  sub_writer.CloseContainer(&entry_writer);
+  sub_writer.OpenDictEntry(&entry_writer);
+  const std::string kKey3 = "three";
+  entry_writer.AppendString(kKey3);
+  const double kDoubleValue = 4.9;
+  entry_writer.AppendVariantOfDouble(kDoubleValue);
+  sub_writer.CloseContainer(&entry_writer);
+  sub_writer.OpenDictEntry(&entry_writer);
+  const std::string kKey4 = "four";
+  entry_writer.AppendString(kKey4);
+  const std::string kStringValue = "fifty";
+  entry_writer.AppendVariantOfString(kStringValue);
+  sub_writer.CloseContainer(&entry_writer);
+  writer.CloseContainer(&sub_writer);
+
+  // Create the expected value.
+  DictionaryValue dictionary_value;
+  dictionary_value.SetBoolean(kKey1, kBoolValue);
+  dictionary_value.SetInteger(kKey2, kInt32Value);
+  dictionary_value.SetDouble(kKey3, kDoubleValue);
+  dictionary_value.SetString(kKey4, kStringValue);
+
+  // Pop a dictinoary.
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(&dictionary_value));
+}
+
+TEST(ValuesUtilTest, PopDoubleToIntDictionary) {
+  // Create test data.
+  const int32 kValues[] = {0, 1, 1, 2, 3, 5, 8, 13, 21};
+  const std::vector<int32> values(kValues, kValues + arraysize(kValues));
+  std::vector<double> keys(values.size());
+  for (size_t i = 0; i != values.size(); ++i)
+    keys[i] = sqrt(values[i]);
+
+  // Append a dictionary.
+  scoped_ptr<dbus::Response> response(dbus::Response::CreateEmpty());
+  dbus::MessageWriter writer(response.get());
+  dbus::MessageWriter sub_writer(NULL);
+  writer.OpenArray("{di}", &sub_writer);
+  for (size_t i = 0; i != values.size(); ++i) {
+    dbus::MessageWriter entry_writer(NULL);
+    sub_writer.OpenDictEntry(&entry_writer);
+    entry_writer.AppendDouble(keys[i]);
+    entry_writer.AppendInt32(values[i]);
+    sub_writer.CloseContainer(&entry_writer);
+  }
+  writer.CloseContainer(&sub_writer);
+
+  // Create the expected value.
+  DictionaryValue dictionary_value;
+  for (size_t i = 0; i != values.size(); ++i) {
+    scoped_ptr<Value> key_value(Value::CreateDoubleValue(keys[i]));
+    std::string key_string;
+    base::JSONWriter::Write(key_value.get(), &key_string);
+    dictionary_value.SetInteger(key_string, values[i]);
+  }
+
+  // Pop a dictionary.
+  dbus::MessageReader reader(response.get());
+  scoped_ptr<Value> value(dbus::PopDataAsValue(&reader));
+  EXPECT_TRUE(value.get() != NULL);
+  EXPECT_TRUE(value->Equals(&dictionary_value));
+}