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 package android.webkit; 18 19 import java.lang.ref.ReferenceQueue; 20 import java.lang.ref.SoftReference; 21 import java.util.LinkedList; 22 import 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 */ 29 class 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