1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <utils/BufferedTextOutput.h>
18
19#include <utils/Atomic.h>
20#include <utils/Debug.h>
21#include <utils/Log.h>
22#include <utils/RefBase.h>
23#include <utils/Vector.h>
24#include <cutils/threads.h>
25
26#include <private/utils/Static.h>
27
28#include <stdio.h>
29#include <stdlib.h>
30
31// ---------------------------------------------------------------------------
32
33namespace android {
34
35struct BufferedTextOutput::BufferState : public RefBase
36{
37    BufferState(int32_t _seq)
38        : seq(_seq)
39        , buffer(NULL)
40        , bufferPos(0)
41        , bufferSize(0)
42        , atFront(true)
43        , indent(0)
44        , bundle(0) {
45    }
46    ~BufferState() {
47        free(buffer);
48    }
49
50    status_t append(const char* txt, size_t len) {
51        if ((len+bufferPos) > bufferSize) {
52            void* b = realloc(buffer, ((len+bufferPos)*3)/2);
53            if (!b) return NO_MEMORY;
54            buffer = (char*)b;
55        }
56        memcpy(buffer+bufferPos, txt, len);
57        bufferPos += len;
58        return NO_ERROR;
59    }
60
61    void restart() {
62        bufferPos = 0;
63        atFront = true;
64        if (bufferSize > 256) {
65            void* b = realloc(buffer, 256);
66            if (b) {
67                buffer = (char*)b;
68                bufferSize = 256;
69            }
70        }
71    }
72
73    const int32_t seq;
74    char* buffer;
75    size_t bufferPos;
76    size_t bufferSize;
77    bool atFront;
78    int32_t indent;
79    int32_t bundle;
80};
81
82struct BufferedTextOutput::ThreadState
83{
84    Vector<sp<BufferedTextOutput::BufferState> > states;
85};
86
87static mutex_t          gMutex;
88
89static thread_store_t   tls;
90
91BufferedTextOutput::ThreadState* BufferedTextOutput::getThreadState()
92{
93    ThreadState*  ts = (ThreadState*) thread_store_get( &tls );
94    if (ts) return ts;
95    ts = new ThreadState;
96    thread_store_set( &tls, ts, threadDestructor );
97    return ts;
98}
99
100void BufferedTextOutput::threadDestructor(void *st)
101{
102    delete ((ThreadState*)st);
103}
104
105static volatile int32_t gSequence = 0;
106
107static volatile int32_t gFreeBufferIndex = -1;
108
109static int32_t allocBufferIndex()
110{
111    int32_t res = -1;
112
113    mutex_lock(&gMutex);
114
115    if (gFreeBufferIndex >= 0) {
116        res = gFreeBufferIndex;
117        gFreeBufferIndex = gTextBuffers[res];
118        gTextBuffers.editItemAt(res) = -1;
119
120    } else {
121        res = gTextBuffers.size();
122        gTextBuffers.add(-1);
123    }
124
125    mutex_unlock(&gMutex);
126
127    return res;
128}
129
130static void freeBufferIndex(int32_t idx)
131{
132    mutex_lock(&gMutex);
133    gTextBuffers.editItemAt(idx) = gFreeBufferIndex;
134    gFreeBufferIndex = idx;
135    mutex_unlock(&gMutex);
136}
137
138// ---------------------------------------------------------------------------
139
140BufferedTextOutput::BufferedTextOutput(uint32_t flags)
141    : mFlags(flags)
142    , mSeq(android_atomic_inc(&gSequence))
143    , mIndex(allocBufferIndex())
144{
145    mGlobalState = new BufferState(mSeq);
146    if (mGlobalState) mGlobalState->incStrong(this);
147}
148
149BufferedTextOutput::~BufferedTextOutput()
150{
151    if (mGlobalState) mGlobalState->decStrong(this);
152    freeBufferIndex(mIndex);
153}
154
155status_t BufferedTextOutput::print(const char* txt, size_t len)
156{
157    //printf("BufferedTextOutput: printing %d\n", len);
158
159    AutoMutex _l(mLock);
160    BufferState* b = getBuffer();
161
162    const char* const end = txt+len;
163
164    status_t err;
165
166    while (txt < end) {
167        // Find the next line.
168        const char* first = txt;
169        while (txt < end && *txt != '\n') txt++;
170
171        // Include this and all following empty lines.
172        while (txt < end && *txt == '\n') txt++;
173
174        // Special cases for first data on a line.
175        if (b->atFront) {
176            if (b->indent > 0) {
177                // If this is the start of a line, add the indent.
178                const char* prefix = stringForIndent(b->indent);
179                err = b->append(prefix, strlen(prefix));
180                if (err != NO_ERROR) return err;
181
182            } else if (*(txt-1) == '\n' && !b->bundle) {
183                // Fast path: if we are not indenting or bundling, and
184                // have been given one or more complete lines, just write
185                // them out without going through the buffer.
186
187                // Slurp up all of the lines.
188                const char* lastLine = txt+1;
189                while (txt < end) {
190                    if (*txt++ == '\n') lastLine = txt;
191                }
192                struct iovec vec;
193                vec.iov_base = (void*)first;
194                vec.iov_len = lastLine-first;
195                //printf("Writing %d bytes of data!\n", vec.iov_len);
196                writeLines(vec, 1);
197                txt = lastLine;
198                continue;
199            }
200        }
201
202        // Append the new text to the buffer.
203        err = b->append(first, txt-first);
204        if (err != NO_ERROR) return err;
205        b->atFront = *(txt-1) == '\n';
206
207        // If we have finished a line and are not bundling, write
208        // it out.
209        //printf("Buffer is now %d bytes\n", b->bufferPos);
210        if (b->atFront && !b->bundle) {
211            struct iovec vec;
212            vec.iov_base = b->buffer;
213            vec.iov_len = b->bufferPos;
214            //printf("Writing %d bytes of data!\n", vec.iov_len);
215            writeLines(vec, 1);
216            b->restart();
217        }
218    }
219
220    return NO_ERROR;
221}
222
223void BufferedTextOutput::moveIndent(int delta)
224{
225    AutoMutex _l(mLock);
226    BufferState* b = getBuffer();
227    b->indent += delta;
228    if (b->indent < 0) b->indent = 0;
229}
230
231void BufferedTextOutput::pushBundle()
232{
233    AutoMutex _l(mLock);
234    BufferState* b = getBuffer();
235    b->bundle++;
236}
237
238void BufferedTextOutput::popBundle()
239{
240    AutoMutex _l(mLock);
241    BufferState* b = getBuffer();
242    b->bundle--;
243    LOG_FATAL_IF(b->bundle < 0,
244        "TextOutput::popBundle() called more times than pushBundle()");
245    if (b->bundle < 0) b->bundle = 0;
246
247    if (b->bundle == 0) {
248        // Last bundle, write out data if it is complete.  If it is not
249        // complete, don't write until the last line is done... this may
250        // or may not be the write thing to do, but it's the easiest.
251        if (b->bufferPos > 0 && b->atFront) {
252            struct iovec vec;
253            vec.iov_base = b->buffer;
254            vec.iov_len = b->bufferPos;
255            writeLines(vec, 1);
256            b->restart();
257        }
258    }
259}
260
261BufferedTextOutput::BufferState* BufferedTextOutput::getBuffer() const
262{
263    if ((mFlags&MULTITHREADED) != 0) {
264        ThreadState* ts = getThreadState();
265        if (ts) {
266            while (ts->states.size() <= (size_t)mIndex) ts->states.add(NULL);
267            BufferState* bs = ts->states[mIndex].get();
268            if (bs != NULL && bs->seq == mSeq) return bs;
269
270            ts->states.editItemAt(mIndex) = new BufferState(mIndex);
271            bs = ts->states[mIndex].get();
272            if (bs != NULL) return bs;
273        }
274    }
275
276    return mGlobalState;
277}
278
279}; // namespace android
280