Add misc android/base/ helper classes.
This patch adds a few new helper classes under android/base/ that
will be used by future patches.
- Add a 'clear' method to String class.
- Add StringFormat() function to generated String instances
from formatted string input, and StringAppendFormat() to
append some to an existing instance.
- Add ScopedFd to implement a scoped file descriptor wrapper.
- Add ScopedHandle to implement a scoped Win32 HANDLE wrapper.
Change-Id: I0ae2a1de1123586b23e4faca8f394c6b4dff622e
diff --git a/Makefile.common b/Makefile.common
index 3d6ea6f..8215a52 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -123,6 +123,7 @@
android/base/files/PathUtils.cpp \
android/base/Log.cpp \
android/base/String.cpp \
+ android/base/StringFormat.cpp \
android/base/StringView.cpp \
android/filesystems/ext4_utils.cpp \
android/utils/assert.c \
diff --git a/Makefile.tests b/Makefile.tests
index 2c4b28c..68933b6 100644
--- a/Makefile.tests
+++ b/Makefile.tests
@@ -11,14 +11,22 @@
android/base/containers/StringVector_unittest.cpp \
android/base/EintrWrapper_unittest.cpp \
android/base/files/PathUtils_unittest.cpp \
+ android/base/files/ScopedFd_unittest.cpp \
android/base/files/ScopedStdioFile_unittest.cpp \
android/base/Log_unittest.cpp \
android/base/memory/MallocUsableSize_unittest.cpp \
android/base/memory/ScopedPtr_unittest.cpp \
android/base/String_unittest.cpp \
+ android/base/StringFormat_unittest.cpp \
android/base/StringView_unittest.cpp \
android/filesystems/ext4_utils_unittest.cpp \
+ifeq (windows,$(HOST_OS))
+EMULATOR_UNITTESTS_SOURCES += \
+ android/base/files/ScopedHandle_unittest.cpp \
+
+endif
+
$(call start-emulator-program, emulator_unittests)
LOCAL_C_INCLUDES += $(EMULATOR_GTEST_INCLUDES)
LOCAL_LDLIBS += $(EMULATOR_GTEST_LDLIBS)
diff --git a/android/base/String.h b/android/base/String.h
index fc7e41f..4e574cb 100644
--- a/android/base/String.h
+++ b/android/base/String.h
@@ -62,9 +62,13 @@
// Return current capacity.
size_t capacity() const {
- return (mStr == mStorage) ? kMinCapacity : mCapacity;
+ return (mStr == mStorage) ?
+ static_cast<size_t>(kMinCapacity) : mCapacity;
}
+ // Clear the content of a given instance.
+ void clear() { resize(0); }
+
// Array indexing operators.
char& operator[](size_t index) { return mStr[index]; }
const char& operator[](size_t index) const { return mStr[index]; }
diff --git a/android/base/StringFormat.cpp b/android/base/StringFormat.cpp
new file mode 100644
index 0000000..a3ba105
--- /dev/null
+++ b/android/base/StringFormat.cpp
@@ -0,0 +1,82 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/base/StringFormat.h"
+
+#include <stdio.h>
+
+namespace android {
+namespace base {
+
+String StringFormat(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ String result = StringFormatWithArgs(format, args);
+ va_end(args);
+ return result;
+}
+
+String StringFormatWithArgs(const char* format, va_list args) {
+ String result;
+ StringAppendFormatWithArgs(&result, format, args);
+ return result;
+}
+
+void StringAppendFormat(String* string, const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ StringAppendFormatWithArgs(string, format, args);
+ va_end(args);
+}
+
+void StringAppendFormatWithArgs(String* string,
+ const char* format,
+ va_list args) {
+ size_t cur_size = string->size();
+ size_t extra = 0;
+ for (;;) {
+ va_list args2;
+ va_copy(args2, args);
+ int ret = vsnprintf(&(*string)[cur_size], extra, format, args2);
+ va_end(args2);
+
+ if (ret == 0) {
+ // Nothing to do here.
+ break;
+ }
+
+ if (ret > 0) {
+ size_t ret_sz = static_cast<size_t>(ret);
+ if (extra == 0) {
+ // First pass, resize the string and try again.
+ extra = ret_sz + 1;
+ string->resize(cur_size + extra);
+ continue;
+ }
+ if (ret_sz < extra) {
+ // Second pass or later, success!
+ string->resize(cur_size + ret_sz);
+ return;
+ }
+ }
+
+ // NOTE: The MSVCRT.DLL implementation of snprintf() is broken and
+ // will return -1 in case of truncation. This code path is taken
+ // when this happens, or when |ret_sz| is equal or larger than
+ // |extra|. Grow the buffer to allow for more room, then try again.
+ extra += (extra >> 1) + 32;
+ string->resize(cur_size + extra);
+ }
+}
+
+
+} // namespace base
+} // namespace android
diff --git a/android/base/StringFormat.h b/android/base/StringFormat.h
new file mode 100644
index 0000000..c8e155d
--- /dev/null
+++ b/android/base/StringFormat.h
@@ -0,0 +1,45 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#ifndef ANDROID_BASE_STRING_FORMAT_H
+#define ANDROID_BASE_STRING_FORMAT_H
+
+#include "android/base/String.h"
+
+#include <stdarg.h>
+
+namespace android {
+namespace base {
+
+// Create a new String instance that contains the printf-style formatted
+// output from |format| and potentially any following arguments.
+String StringFormat(const char* format, ...);
+
+// A variant of StringFormat() which uses a va_list to list formatting
+// parameters instead.
+String StringFormatWithArgs(const char* format, va_list args);
+
+// Appends a formatted string at the end of an existing string.
+// |string| is the target String instance, |format| the format string,
+// followed by any formatting parameters. This is more efficient than
+// appending the result of StringFormat(format,...) to |*string| directly.
+void StringAppendFormat(String* string, const char* format, ...);
+
+// A variant of StringAppendFormat() that takes a va_list to list
+// formatting parameters.
+void StringAppendFormatWithArgs(String* string,
+ const char* format,
+ va_list args);
+
+} // namespace base
+} // namespace android
+
+#endif // ANDROID_BASE_STRING_FORMAT_H
diff --git a/android/base/StringFormat_unittest.cpp b/android/base/StringFormat_unittest.cpp
new file mode 100644
index 0000000..7db8529
--- /dev/null
+++ b/android/base/StringFormat_unittest.cpp
@@ -0,0 +1,87 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/base/StringFormat.h"
+
+#include <gtest/gtest.h>
+
+#include "android/base/String.h"
+
+namespace android {
+namespace base {
+
+TEST(StringFormat, EmptyString) {
+ String s = StringFormat("");
+ EXPECT_TRUE(s.empty());
+ EXPECT_STREQ("", s.c_str());
+}
+
+TEST(StringFormat, SimpleString) {
+ String s = StringFormat("foobar");
+ EXPECT_STREQ("foobar", s.c_str());
+}
+
+TEST(StringFormat, SimpleDecimal) {
+ String s = StringFormat("Pi is about %d.%d\n", 3, 1415);
+ EXPECT_STREQ("Pi is about 3.1415\n", s.c_str());
+}
+
+TEST(StringFormat, VeryLongString) {
+ static const char kPiece[] = "A hospital bed is a parked taxi with the meter running - Groucho Marx. ";
+ const size_t kPieceLen = sizeof(kPiece) - 1U;
+ String s = StringFormat("%s%s%s%s%s%s%s",
+ kPiece,
+ kPiece,
+ kPiece,
+ kPiece,
+ kPiece,
+ kPiece,
+ kPiece
+ );
+ EXPECT_EQ(7U * kPieceLen, s.size());
+ for (size_t n = 0; n < 7U; ++n) {
+ String s2 = String(s.c_str() + n * kPieceLen, kPieceLen);
+ EXPECT_STREQ(kPiece, s2.c_str()) << "Index #" << n;
+ }
+}
+
+TEST(StringAppendFormat, EmptyString) {
+ String s = "foo";
+ StringAppendFormat(&s, "");
+ EXPECT_EQ(3U, s.size());
+ EXPECT_STREQ("foo", s.c_str());
+}
+
+TEST(StringAppendFormat, SimpleString) {
+ String s = "foo";
+ StringAppendFormat(&s, "bar");
+ EXPECT_EQ(6U, s.size());
+ EXPECT_STREQ("foobar", s.c_str());
+}
+
+TEST(StringAppendFormat, VeryLongString) {
+ static const char kPiece[] = "A hospital bed is a parked taxi with the meter running - Groucho Marx. ";
+ const size_t kPieceLen = sizeof(kPiece) - 1U;
+ const size_t kCount = 12;
+ String s;
+ for (size_t n = 0; n < kCount; ++n) {
+ StringAppendFormat(&s, "%s", kPiece);
+ }
+
+ EXPECT_EQ(kCount * kPieceLen, s.size());
+ for (size_t n = 0; n < kCount; ++n) {
+ String s2 = String(s.c_str() + n * kPieceLen, kPieceLen);
+ EXPECT_STREQ(kPiece, s2.c_str()) << "Index #" << n;
+ }
+}
+
+} // namespace base
+} // namespace android
diff --git a/android/base/String_unittest.cpp b/android/base/String_unittest.cpp
index e582124..69719fb 100644
--- a/android/base/String_unittest.cpp
+++ b/android/base/String_unittest.cpp
@@ -91,6 +91,14 @@
EXPECT_EQ(kCount, s.size());
}
+TEST(String, clear) {
+ String s("foo bar");
+ EXPECT_FALSE(s.empty());
+ s.clear();
+ EXPECT_TRUE(s.empty());
+ EXPECT_EQ(0U, s.size());
+}
+
TEST(String, IndexedAccess) {
String s("foobar");
EXPECT_EQ('f', s[0]);
diff --git a/android/base/files/ScopedFd.h b/android/base/files/ScopedFd.h
new file mode 100644
index 0000000..6aa5157
--- /dev/null
+++ b/android/base/files/ScopedFd.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#ifndef ANDROID_BASE_SCOPED_FD_H
+#define ANDROID_BASE_SCOPED_FD_H
+
+#include "android/base/Compiler.h"
+
+#include <errno.h>
+#include <unistd.h>
+
+namespace android {
+namespace base {
+
+// Helper class to hold an integer file descriptor, and have the 'close'
+// function called automatically on scope exit, unless the 'release'
+// method was called previously.
+class ScopedFd {
+public:
+ // Default constructor will hold an invalid descriptor.
+ ScopedFd() : fd_(-1) {}
+
+ // Constructor takes ownership of |fd|.
+ explicit ScopedFd(int fd) : fd_(fd) {}
+
+ // Destructor calls close().
+ ~ScopedFd() { close(); }
+
+ // Return the file descriptor value, does _not_ transfer ownership.
+ int get() const { return fd_; }
+
+ // Return the file descriptor value, transfers ownership to the caller.
+ int release() {
+ int fd = fd_;
+ fd_ = -1;
+ return fd;
+ }
+
+ // Return true iff the file descriptor is valid.
+ bool valid() const { return fd_ >= 0; }
+
+ // Close the file descriptor (and make the wrapped value invalid).
+ void close() {
+ if (fd_ != -1) {
+ int save_errno = errno;
+ ::close(fd_);
+ fd_ = -1;
+ errno = save_errno;
+ }
+ }
+
+ // Swap two ScopedFd instances.
+ void swap(ScopedFd* other) {
+ int fd = fd_;
+ fd_ = other->fd_;
+ other->fd_ = fd;
+ }
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedFd);
+
+ int fd_;
+};
+
+} // namespace base
+} // namespace android
+
+#endif // ANDROID_BASE_SCOPED_FD_H
diff --git a/android/base/files/ScopedFd_unittest.cpp b/android/base/files/ScopedFd_unittest.cpp
new file mode 100644
index 0000000..7ac1d69
--- /dev/null
+++ b/android/base/files/ScopedFd_unittest.cpp
@@ -0,0 +1,76 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/base/files/ScopedFd.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace base {
+
+namespace {
+
+// The path of a file that can always be opened for reading on any platform.
+#ifdef _WIN32
+static const char kNullFile[] = "NUL";
+#else
+static const char kNullFile[] = "/dev/null";
+#endif
+
+int OpenNull() {
+ return ::open(kNullFile, O_RDONLY);
+}
+
+} // namespace
+
+TEST(ScopedFd, DefaultConstructor) {
+ ScopedFd f;
+ EXPECT_FALSE(f.valid());
+ EXPECT_EQ(-1, f.get());
+}
+
+TEST(ScopedFd, Constructor) {
+ ScopedFd f(OpenNull());
+ EXPECT_TRUE(f.valid());
+}
+
+TEST(ScopedFd, Release) {
+ ScopedFd f(OpenNull());
+ EXPECT_TRUE(f.valid());
+ int fd = f.release();
+ EXPECT_FALSE(f.valid());
+ EXPECT_NE(-1, fd);
+ ::close(fd);
+}
+
+TEST(ScopedFd, Close) {
+ ScopedFd f(OpenNull());
+ EXPECT_TRUE(f.valid());
+ f.close();
+ EXPECT_FALSE(f.valid());
+}
+
+TEST(ScopedFd, Swap) {
+ ScopedFd f1;
+ ScopedFd f2(OpenNull());
+ EXPECT_FALSE(f1.valid());
+ EXPECT_TRUE(f2.valid());
+ f1.swap(&f2);
+ EXPECT_FALSE(f2.valid());
+ EXPECT_TRUE(f1.valid());
+}
+
+
+} // namespace base
+} // namespace android
diff --git a/android/base/files/ScopedHandle.h b/android/base/files/ScopedHandle.h
new file mode 100644
index 0000000..45a97eb
--- /dev/null
+++ b/android/base/files/ScopedHandle.h
@@ -0,0 +1,78 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#ifndef ANDROID_BASE_FILES_SCOPED_HANDLE_H
+#define ANDROID_BASE_FILES_SCOPED_HANDLE_H
+
+#if !defined(_WIN32) && !defined(_WIN64)
+#error "Only compile this file when targetting Windows!"
+#endif
+
+#include "android/base/Compiler.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <windows.h>
+
+namespace android {
+namespace base {
+
+// Helper class used to wrap a Win32 HANDLE that will be closed when
+// the instance is destroyed, unless the release() method was called
+// before that.
+class ScopedHandle {
+public:
+ // Default destructor is used to wrap an invalid handle value.
+ ScopedHandle() : handle_(INVALID_HANDLE_VALUE) {}
+
+ // Constructor takes ownership of |handle|.
+ explicit ScopedHandle(HANDLE handle) : handle_(handle) {}
+
+ // Destructor calls close() method.
+ ~ScopedHandle() { close(); }
+
+ // Returns true iff the wrapped HANDLE value is valid.
+ bool valid() const { return handle_ != INVALID_HANDLE_VALUE; }
+
+ // Return current HANDLE value. Does _not_ transfer ownership.
+ HANDLE get() const { return handle_; }
+
+ // Return current HANDLE value, transferring ownership to the caller.
+ HANDLE release() {
+ HANDLE h = handle_;
+ handle_ = INVALID_HANDLE_VALUE;
+ return h;
+ }
+
+ // Close handle if it is not invalid.
+ void close() {
+ if (handle_ != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(handle_);
+ handle_ = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ // Swap the content of two ScopedHandle instances.
+ void swap(ScopedHandle* other) {
+ HANDLE handle = handle_;
+ handle_ = other->handle_;
+ other->handle_ = handle;
+ }
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedHandle);
+
+ HANDLE handle_;
+};
+
+} // namespace base
+} // namespace android
+
+#endif // ANDROID_BASE_FILES_SCOPED_HANDLE_H
diff --git a/android/base/files/ScopedHandle_unittest.cpp b/android/base/files/ScopedHandle_unittest.cpp
new file mode 100644
index 0000000..93baf9d
--- /dev/null
+++ b/android/base/files/ScopedHandle_unittest.cpp
@@ -0,0 +1,74 @@
+// Copyright 2014 The Android Open Source Project
+//
+// This software is licensed under the terms of the GNU General Public
+// License version 2, as published by the Free Software Foundation, and
+// may be copied, distributed, and modified under those terms.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+#include "android/base/files/ScopedHandle.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace base {
+
+namespace {
+
+// The path of a file that can always be opened for reading on any platform.
+static const char kNullFile[] = "NUL";
+
+HANDLE OpenNull() {
+ return ::CreateFile(kNullFile,
+ GENERIC_READ,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+}
+
+} // namespace
+
+TEST(ScopedHandle, DefaultConstructor) {
+ ScopedHandle h;
+ EXPECT_FALSE(h.valid());
+ EXPECT_EQ(INVALID_HANDLE_VALUE, h.get());
+}
+
+TEST(ScopedHandle, Constructor) {
+ ScopedHandle h(OpenNull());
+ EXPECT_TRUE(h.valid());
+}
+
+TEST(ScopedHandle, Release) {
+ ScopedHandle h(OpenNull());
+ EXPECT_TRUE(h.valid());
+ HANDLE handle = h.release();
+ EXPECT_FALSE(h.valid());
+ EXPECT_NE(INVALID_HANDLE_VALUE, handle);
+ ::CloseHandle(handle);
+}
+
+TEST(ScopedHandle, Close) {
+ ScopedHandle h(OpenNull());
+ EXPECT_TRUE(h.valid());
+ h.close();
+ EXPECT_FALSE(h.valid());
+}
+
+TEST(ScopedHandle, Swap) {
+ ScopedHandle h1;
+ ScopedHandle h2(OpenNull());
+ EXPECT_FALSE(h1.valid());
+ EXPECT_TRUE(h2.valid());
+ h1.swap(&h2);
+ EXPECT_FALSE(h2.valid());
+ EXPECT_TRUE(h1.valid());
+}
+
+} // namespace base
+} // namespace android