/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "hw-BufferedTextOutput"
#include <hwbinder/BufferedTextOutput.h>
#include <hwbinder/Debug.h>
#include <cutils/atomic.h>
#include <cutils/threads.h>
#include <utils/Log.h>
#include <utils/RefBase.h>
#include <utils/Vector.h>
#include <hwbinder/Static.h>
#include <stdio.h>
#include <stdlib.h>
// ---------------------------------------------------------------------------
namespace android {
namespace hardware {
struct BufferedTextOutput::BufferState : public RefBase
{
explicit BufferState(int32_t _seq)
: seq(_seq)
, buffer(NULL)
, bufferPos(0)
, bufferSize(0)
, atFront(true)
, indent(0)
, bundle(0) {
}
~BufferState() {
free(buffer);
}
status_t append(const char* txt, size_t len) {
if ((len+bufferPos) > bufferSize) {
size_t newSize = ((len+bufferPos)*3)/2;
if (newSize < (len+bufferPos)) return NO_MEMORY; // overflow
void* b = realloc(buffer, newSize);
if (!b) return NO_MEMORY;
buffer = (char*)b;
bufferSize = newSize;
}
if ((len+bufferPos) < bufferPos) return NO_MEMORY; // integer overflow
memcpy(buffer+bufferPos, txt, len);
bufferPos += len;
return NO_ERROR;
}
void restart() {
bufferPos = 0;
atFront = true;
if (bufferSize > 256) {
void* b = realloc(buffer, 256);
if (b) {
buffer = (char*)b;
bufferSize = 256;
}
}
}
const int32_t seq;
char* buffer;
size_t bufferPos;
size_t bufferSize;
bool atFront;
int32_t indent;
int32_t bundle;
};
struct BufferedTextOutput::ThreadState
{
Vector<sp<BufferedTextOutput::BufferState> > states;
};
static mutex_t gMutex;
static thread_store_t tls;
BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
{
ThreadState* ts = (ThreadState*) thread_store_get( &tls );
if (ts) return ts;
ts = new ThreadState;
thread_store_set( &tls, ts, threadDestructor );
return ts;
}
void BufferedTextOutput::threadDestructor(void *st)
{
delete ((ThreadState*)st);
}
static volatile int32_t gSequence = 0;
static volatile int32_t gFreeBufferIndex = -1;
static int32_t allocBufferIndex()
{
int32_t res = -1;
mutex_lock(&gMutex);
if (gFreeBufferIndex >= 0) {
res = gFreeBufferIndex;
gFreeBufferIndex = gTextBuffers[res];
gTextBuffers.editItemAt(res) = -1;
} else {
res = gTextBuffers.size();
gTextBuffers.add(-1);
}
mutex_unlock(&gMutex);
return res;
}
static void freeBufferIndex(int32_t idx)
{
mutex_lock(&gMutex);
gTextBuffers.editItemAt(idx) = gFreeBufferIndex;
gFreeBufferIndex = idx;
mutex_unlock(&gMutex);
}
// ---------------------------------------------------------------------------
BufferedTextOutput::BufferedTextOutput(uint32_t flags)
: mFlags(flags)
, mSeq(android_atomic_inc(&gSequence))
, mIndex(allocBufferIndex())
{
mGlobalState = new BufferState(mSeq);
if (mGlobalState) mGlobalState->incStrong(this);
}
BufferedTextOutput::~BufferedTextOutput()
{
if (mGlobalState) mGlobalState->decStrong(this);
freeBufferIndex(mIndex);
}
status_t BufferedTextOutput::print(const char* txt, size_t len)
{
AutoMutex _l(mLock);
BufferState* b = getBuffer();
const char* const end = txt+len;
status_t err;
while (txt < end) {
// Find the next line.
const char* first = txt;
while (txt < end && *txt != '\n') txt++;
// Include this and all following empty lines.
while (txt < end && *txt == '\n') txt++;
// Special cases for first data on a line.
if (b->atFront) {
if (b->indent > 0) {
// If this is the start of a line, add the indent.
const char* prefix = stringForIndent(b->indent);
err = b->append(prefix, strlen(prefix));
if (err != NO_ERROR) return err;
} else if (*(txt-1) == '\n' && !b->bundle) {
// Fast path: if we are not indenting or bundling, and
// have been given one or more complete lines, just write
// them out without going through the buffer.
// Slurp up all of the lines.
const char* lastLine = txt+1;
while (txt < end) {
if (*txt++ == '\n') lastLine = txt;
}
struct iovec vec;
vec.iov_base = (void*)first;
vec.iov_len = lastLine-first;
//printf("Writing %d bytes of data!\n", vec.iov_len);
writeLines(vec, 1);
txt = lastLine;
continue;
}
}
// Append the new text to the buffer.
err = b->append(first, txt-first);
if (err != NO_ERROR) return err;
b->atFront = *(txt-1) == '\n';
// If we have finished a line and are not bundling, write
// it out.
//printf("Buffer is now %d bytes\n", b->bufferPos);
if (b->atFront && !b->bundle) {
struct iovec vec;
vec.iov_base = b->buffer;
vec.iov_len = b->bufferPos;
//printf("Writing %d bytes of data!\n", vec.iov_len);
writeLines(vec, 1);
b->restart();
}
}
return NO_ERROR;
}
void BufferedTextOutput::moveIndent(int delta)
{
AutoMutex _l(mLock);
BufferState* b = getBuffer();
b->indent += delta;
if (b->indent < 0) b->indent = 0;
}
void BufferedTextOutput::pushBundle()
{
AutoMutex _l(mLock);
BufferState* b = getBuffer();
b->bundle++;
}
void BufferedTextOutput::popBundle()
{
AutoMutex _l(mLock);
BufferState* b = getBuffer();
b->bundle--;
LOG_FATAL_IF(b->bundle < 0,
"TextOutput::popBundle() called more times than pushBundle()");
if (b->bundle < 0) b->bundle = 0;
if (b->bundle == 0) {
// Last bundle, write out data if it is complete. If it is not
// complete, don't write until the last line is done... this may
// or may not be the write thing to do, but it's the easiest.
if (b->bufferPos > 0 && b->atFront) {
struct iovec vec;
vec.iov_base = b->buffer;
vec.iov_len = b->bufferPos;
writeLines(vec, 1);
b->restart();
}
}
}
BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
{
if ((mFlags&MULTITHREADED) != 0) {
ThreadState* ts = getThreadState();
if (ts) {
while (ts->states.size() <= (size_t)mIndex) ts->states.add(NULL);
BufferState* bs = ts->states[mIndex].get();
if (bs != NULL && bs->seq == mSeq) return bs;
ts->states.editItemAt(mIndex) = new BufferState(mIndex);
bs = ts->states[mIndex].get();
if (bs != NULL) return bs;
}
}
return mGlobalState;
}
}; // namespace hardware
}; // namespace android