1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.store; |
7 | |
8 | import org.h2.message.DbException; |
9 | import org.h2.message.Trace; |
10 | import org.h2.util.BitField; |
11 | import org.h2.util.IntArray; |
12 | |
13 | /** |
14 | * An output stream that writes into a page store. |
15 | */ |
16 | public class PageOutputStream { |
17 | |
18 | private PageStore store; |
19 | private final Trace trace; |
20 | private final BitField exclude; |
21 | private final boolean atEnd; |
22 | private final int minPageId; |
23 | |
24 | private int trunkPageId; |
25 | private int trunkNext; |
26 | private IntArray reservedPages = new IntArray(); |
27 | private PageStreamTrunk trunk; |
28 | private int trunkIndex; |
29 | private PageStreamData data; |
30 | private int reserved; |
31 | private boolean needFlush; |
32 | private boolean writing; |
33 | private int pageCount; |
34 | private int logKey; |
35 | |
36 | /** |
37 | * Create a new page output stream. |
38 | * |
39 | * @param store the page store |
40 | * @param trunkPage the first trunk page (already allocated) |
41 | * @param exclude the pages not to use |
42 | * @param logKey the log key of the first trunk page |
43 | * @param atEnd whether only pages at the end of the file should be used |
44 | */ |
45 | public PageOutputStream(PageStore store, int trunkPage, BitField exclude, |
46 | int logKey, boolean atEnd) { |
47 | this.trace = store.getTrace(); |
48 | this.store = store; |
49 | this.trunkPageId = trunkPage; |
50 | this.exclude = exclude; |
51 | // minus one, because we increment before creating a trunk page |
52 | this.logKey = logKey - 1; |
53 | this.atEnd = atEnd; |
54 | minPageId = atEnd ? trunkPage : 0; |
55 | } |
56 | |
57 | /** |
58 | * Allocate the required pages so that no pages need to be allocated while |
59 | * writing. |
60 | * |
61 | * @param minBuffer the number of bytes to allocate |
62 | */ |
63 | void reserve(int minBuffer) { |
64 | if (reserved < minBuffer) { |
65 | int pageSize = store.getPageSize(); |
66 | int capacityPerPage = PageStreamData.getCapacity(pageSize); |
67 | int pages = PageStreamTrunk.getPagesAddressed(pageSize); |
68 | int pagesToAllocate = 0, totalCapacity = 0; |
69 | do { |
70 | // allocate x data pages plus one trunk page |
71 | pagesToAllocate += pages + 1; |
72 | totalCapacity += pages * capacityPerPage; |
73 | } while (totalCapacity < minBuffer); |
74 | int firstPageToUse = atEnd ? trunkPageId : 0; |
75 | store.allocatePages(reservedPages, pagesToAllocate, exclude, firstPageToUse); |
76 | reserved += totalCapacity; |
77 | if (data == null) { |
78 | initNextData(); |
79 | } |
80 | } |
81 | } |
82 | |
83 | private void initNextData() { |
84 | int nextData = trunk == null ? -1 : trunk.getPageData(trunkIndex++); |
85 | if (nextData == -1) { |
86 | int parent = trunkPageId; |
87 | if (trunkNext != 0) { |
88 | trunkPageId = trunkNext; |
89 | } |
90 | int len = PageStreamTrunk.getPagesAddressed(store.getPageSize()); |
91 | int[] pageIds = new int[len]; |
92 | for (int i = 0; i < len; i++) { |
93 | pageIds[i] = reservedPages.get(i); |
94 | } |
95 | trunkNext = reservedPages.get(len); |
96 | logKey++; |
97 | trunk = PageStreamTrunk.create(store, parent, trunkPageId, |
98 | trunkNext, logKey, pageIds); |
99 | trunkIndex = 0; |
100 | pageCount++; |
101 | trunk.write(); |
102 | reservedPages.removeRange(0, len + 1); |
103 | nextData = trunk.getPageData(trunkIndex++); |
104 | } |
105 | data = PageStreamData.create(store, nextData, trunk.getPos(), logKey); |
106 | pageCount++; |
107 | data.initWrite(); |
108 | } |
109 | |
110 | /** |
111 | * Write the data. |
112 | * |
113 | * @param b the buffer |
114 | * @param off the offset |
115 | * @param len the length |
116 | */ |
117 | public void write(byte[] b, int off, int len) { |
118 | if (len <= 0) { |
119 | return; |
120 | } |
121 | if (writing) { |
122 | DbException.throwInternalError("writing while still writing"); |
123 | } |
124 | try { |
125 | reserve(len); |
126 | writing = true; |
127 | while (len > 0) { |
128 | int l = data.write(b, off, len); |
129 | if (l < len) { |
130 | storePage(); |
131 | initNextData(); |
132 | } |
133 | reserved -= l; |
134 | off += l; |
135 | len -= l; |
136 | } |
137 | needFlush = true; |
138 | } finally { |
139 | writing = false; |
140 | } |
141 | } |
142 | |
143 | private void storePage() { |
144 | if (trace.isDebugEnabled()) { |
145 | trace.debug("pageOut.storePage " + data); |
146 | } |
147 | data.write(); |
148 | } |
149 | |
150 | /** |
151 | * Write all data. |
152 | */ |
153 | public void flush() { |
154 | if (needFlush) { |
155 | storePage(); |
156 | needFlush = false; |
157 | } |
158 | } |
159 | |
160 | /** |
161 | * Close the stream. |
162 | */ |
163 | public void close() { |
164 | store = null; |
165 | } |
166 | |
167 | int getCurrentDataPageId() { |
168 | return data.getPos(); |
169 | } |
170 | |
171 | /** |
172 | * Fill the data page with zeros and write it. |
173 | * This is required for a checkpoint. |
174 | */ |
175 | void fillPage() { |
176 | if (trace.isDebugEnabled()) { |
177 | trace.debug("pageOut.storePage fill " + data.getPos()); |
178 | } |
179 | reserve(data.getRemaining() + 1); |
180 | reserved -= data.getRemaining(); |
181 | data.write(); |
182 | initNextData(); |
183 | } |
184 | |
185 | long getSize() { |
186 | return pageCount * store.getPageSize(); |
187 | } |
188 | |
189 | /** |
190 | * Remove a trunk page from the stream. |
191 | * |
192 | * @param t the trunk page |
193 | */ |
194 | void free(PageStreamTrunk t) { |
195 | pageCount -= t.free(0); |
196 | } |
197 | |
198 | /** |
199 | * Free up all reserved pages. |
200 | */ |
201 | void freeReserved() { |
202 | if (reservedPages.size() > 0) { |
203 | int[] array = new int[reservedPages.size()]; |
204 | reservedPages.toArray(array); |
205 | reservedPages = new IntArray(); |
206 | reserved = 0; |
207 | for (int p : array) { |
208 | store.free(p, false); |
209 | } |
210 | } |
211 | } |
212 | |
213 | /** |
214 | * Get the smallest possible page id used. This is the trunk page if only |
215 | * appending at the end of the file, or 0. |
216 | * |
217 | * @return the smallest possible page. |
218 | */ |
219 | int getMinPageId() { |
220 | return minPageId; |
221 | } |
222 | |
223 | } |