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
17package android.webkit;
18
19import java.lang.ref.ReferenceQueue;
20import java.lang.ref.SoftReference;
21import java.util.LinkedList;
22import java.util.ListIterator;
23
24/** Utility class optimized for accumulating bytes, and then spitting
25    them back out.  It does not optimize for returning the result in a
26    single array, though this is supported in the API. It is fastest
27    if the retrieval can be done via iterating through chunks.
28*/
29class ByteArrayBuilder {
30
31    private static final int DEFAULT_CAPACITY = 8192;
32
33    // Global pool of chunks to be used by other ByteArrayBuilders.
34    private static final LinkedList<SoftReference<Chunk>> sPool =
35            new LinkedList<SoftReference<Chunk>>();
36    // Reference queue for processing gc'd entries.
37    private static final ReferenceQueue<Chunk> sQueue =
38            new ReferenceQueue<Chunk>();
39
40    private LinkedList<Chunk> mChunks;
41
42    public ByteArrayBuilder() {
43        mChunks = new LinkedList<Chunk>();
44    }
45
46    public synchronized void append(byte[] array, int offset, int length) {
47        while (length > 0) {
48            Chunk c = null;
49            if (mChunks.isEmpty()) {
50                c = obtainChunk(length);
51                mChunks.addLast(c);
52            } else {
53                c = mChunks.getLast();
54                if (c.mLength == c.mArray.length) {
55                    c = obtainChunk(length);
56                    mChunks.addLast(c);
57                }
58            }
59            int amount = Math.min(length, c.mArray.length - c.mLength);
60            System.arraycopy(array, offset, c.mArray, c.mLength, amount);
61            c.mLength += amount;
62            length -= amount;
63            offset += amount;
64        }
65    }
66
67    /**
68     * The fastest way to retrieve the data is to iterate through the
69     * chunks.  This returns the first chunk.  Note: this pulls the
70     * chunk out of the queue.  The caller must call Chunk.release() to
71     * dispose of it.
72     */
73    public synchronized Chunk getFirstChunk() {
74        if (mChunks.isEmpty()) return null;
75        return mChunks.removeFirst();
76    }
77
78    public synchronized boolean isEmpty() {
79        return mChunks.isEmpty();
80    }
81
82    public synchronized int getByteSize() {
83        int total = 0;
84        ListIterator<Chunk> it = mChunks.listIterator(0);
85        while (it.hasNext()) {
86            Chunk c = it.next();
87            total += c.mLength;
88        }
89        return total;
90    }
91
92    public synchronized void clear() {
93        Chunk c = getFirstChunk();
94        while (c != null) {
95            c.release();
96            c = getFirstChunk();
97        }
98    }
99
100    // Must be called with lock held on sPool.
101    private void processPoolLocked() {
102        while (true) {
103            SoftReference<Chunk> entry = (SoftReference<Chunk>) sQueue.poll();
104            if (entry == null) {
105                break;
106            }
107            sPool.remove(entry);
108        }
109    }
110
111    private Chunk obtainChunk(int length) {
112        // Correct a small length.
113        if (length < DEFAULT_CAPACITY) {
114            length = DEFAULT_CAPACITY;
115        }
116        synchronized (sPool) {
117            // Process any queued references and remove them from the pool.
118            processPoolLocked();
119            if (!sPool.isEmpty()) {
120                Chunk c = sPool.removeFirst().get();
121                // The first item may have been queued after processPoolLocked
122                // so check for null.
123                if (c != null) {
124                    return c;
125                }
126            }
127            return new Chunk(length);
128        }
129    }
130
131    public static class Chunk {
132        public byte[]  mArray;
133        public int     mLength;
134
135        public Chunk(int length) {
136            mArray = new byte[length];
137            mLength = 0;
138        }
139
140        /**
141         * Release the chunk and make it available for reuse.
142         */
143        public void release() {
144            mLength = 0;
145            synchronized (sPool) {
146                // Add the chunk back to the pool as a SoftReference so it can
147                // be gc'd if needed.
148                sPool.offer(new SoftReference<Chunk>(this, sQueue));
149                sPool.notifyAll();
150            }
151        }
152
153    }
154}
155