1 /*******************************************************************************
2  * Copyright (c) 2005, 2016 QNX Software Systems and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     QNX - Initial API and implementation
13  *     Markus Schorn (Wind River Systems)
14  *     IBM Corporation
15  *     Sergey Prigogin (Google)
16  *******************************************************************************/
17 package org.eclipse.jdt.internal.core.nd.db;
18 
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 
22 /**
23  * Caches the content of a piece of the database.
24  */
25 final class Chunk {
26 	final private byte[] fBuffer= new byte[Database.CHUNK_SIZE];
27 
28 	final Database fDatabase;
29 	/**
30 	 * Holds the database-specific chunk number. This is the index into the database's chunk array and indicates the
31 	 * start of the range of addresses held by this chunk. Non-negative.
32 	 */
33 	final int fSequenceNumber;
34 	/**
35 	 * True iff this chunk contains data that hasn't yet been written to disk. This is protected by the write lock
36 	 * on the corresponding {@link Database}.
37 	 */
38 	boolean fDirty;
39 	/**
40 	 * True iff this {@link Chunk} was accessed since the last time it was tested for eviction in the
41 	 * {@link ChunkCache}. Protected by synchronizing on the {@link ChunkCache} itself.
42 	 */
43 	boolean fCacheHitFlag;
44 	/**
45 	 * Holds the index into the {@link ChunkCache}'s page table, or -1 if this {@link Chunk} isn't present in the page
46 	 * table. Protected by synchronizing on the {@link ChunkCache} itself.
47 	 */
48 	int fCacheIndex= -1;
49 
Chunk(Database db, int sequenceNumber)50 	Chunk(Database db, int sequenceNumber) {
51 		this.fDatabase= db;
52 		this.fSequenceNumber= sequenceNumber;
53 	}
54 
makeDirty()55 	public void makeDirty() {
56 		if (this.fSequenceNumber >= Database.NUM_HEADER_CHUNKS) {
57 			Chunk chunk = this.fDatabase.fChunks[this.fSequenceNumber];
58 			if (chunk != this) {
59 				throw new IllegalStateException("CHUNK " + this.fSequenceNumber + ": found two copies. Copy 1: " //$NON-NLS-1$ //$NON-NLS-2$
60 						+ System.identityHashCode(this) + ", Copy 2: " + System.identityHashCode(chunk)); //$NON-NLS-1$
61 			}
62 		}
63 		if (!this.fDirty) {
64 			if (Database.DEBUG_PAGE_CACHE) {
65 				System.out.println(
66 						"CHUNK " + this.fSequenceNumber + ": dirtied - instance " + System.identityHashCode(this)); //$NON-NLS-1$ //$NON-NLS-2$
67 			}
68 			if (this.fSequenceNumber >= Database.NUM_HEADER_CHUNKS
69 					&& this.fDatabase.fMostRecentlyFetchedChunk != this) {
70 				throw new IllegalStateException("CHUNK " + this.fSequenceNumber //$NON-NLS-1$
71 						+ " dirtied out of order: Only the most-recently-fetched chunk is allowed to be dirtied"); //$NON-NLS-1$
72 			}
73 			this.fDirty = true;
74 			this.fDatabase.chunkDirtied(this);
75 		}
76 	}
77 
read()78 	void read() throws IndexException {
79 		try {
80 			final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
81 			this.fDatabase.read(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
82 		} catch (IOException e) {
83 			throw new IndexException(new DBStatus(e));
84 		}
85 	}
86 
87 	/**
88 	 * Uninterruptable. Returns true iff an attempt was made to interrupt the flush with
89 	 * {@link Thread#interrupt()}.
90 	 */
flush()91 	boolean flush() throws IndexException {
92 		if (Database.DEBUG_PAGE_CACHE) {
93 			System.out.println(
94 					"CHUNK " + this.fSequenceNumber + ": flushing - instance " + System.identityHashCode(this)); //$NON-NLS-1$//$NON-NLS-2$
95 		}
96 		boolean wasCanceled = false;
97 		try {
98 			final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
99 			wasCanceled = this.fDatabase.write(buf, (long) this.fSequenceNumber * Database.CHUNK_SIZE);
100 		} catch (IOException e) {
101 			throw new IndexException(new DBStatus(e));
102 		}
103 		this.fDirty = false;
104 		this.fDatabase.chunkCleaned(this);
105 		return wasCanceled;
106 	}
107 
recPtrToIndex(final long offset)108 	static int recPtrToIndex(final long offset) {
109 		return (int) (offset & Database.OFFSET_IN_CHUNK_MASK);
110 	}
111 
putByte(final long offset, final byte value)112 	public void putByte(final long offset, final byte value) {
113 		makeDirty();
114 		this.fBuffer[recPtrToIndex(offset)]= value;
115 		recordWrite(offset, 1);
116 	}
117 
getByte(final long offset)118 	public byte getByte(final long offset) {
119 		return this.fBuffer[recPtrToIndex(offset)];
120 	}
121 
122 	/**
123 	 * Returns a copy of the entire chunk.
124 	 */
getBytes()125 	public byte[] getBytes() {
126 		final byte[] bytes = new byte[this.fBuffer.length];
127 		System.arraycopy(this.fBuffer, 0, bytes, 0, this.fBuffer.length);
128 		return bytes;
129 	}
130 
getBytes(final long offset, final int length)131 	public byte[] getBytes(final long offset, final int length) {
132 		final byte[] bytes = new byte[length];
133 		System.arraycopy(this.fBuffer, recPtrToIndex(offset), bytes, 0, length);
134 		return bytes;
135 	}
136 
putBytes(final long offset, final byte[] bytes)137 	public void putBytes(final long offset, final byte[] bytes) {
138 		makeDirty();
139 		System.arraycopy(bytes, 0, this.fBuffer, recPtrToIndex(offset), bytes.length);
140 		recordWrite(offset, bytes.length);
141 	}
142 
putInt(final long offset, final int value)143 	public void putInt(final long offset, final int value) {
144 		makeDirty();
145 		int idx= recPtrToIndex(offset);
146 		putInt(value, this.fBuffer, idx);
147 		recordWrite(offset, 4);
148 	}
149 
putInt(final int value, final byte[] buffer, int idx)150 	static final void putInt(final int value, final byte[] buffer, int idx) {
151 		buffer[idx]=   (byte) (value >> 24);
152 		buffer[++idx]= (byte) (value >> 16);
153 		buffer[++idx]= (byte) (value >> 8);
154 		buffer[++idx]= (byte) (value);
155 	}
156 
getInt(final long offset)157 	public int getInt(final long offset) {
158 		return getInt(this.fBuffer, recPtrToIndex(offset));
159 	}
160 
getInt(final byte[] buffer, int idx)161 	static final int getInt(final byte[] buffer, int idx) {
162 		return ((buffer[idx] & 0xff) << 24) |
163 				((buffer[++idx] & 0xff) << 16) |
164 				((buffer[++idx] & 0xff) <<  8) |
165 				((buffer[++idx] & 0xff) <<  0);
166 	}
167 
168 	/**
169 	 * A free Record Pointer is a pointer to a raw block, i.e. the
170 	 * pointer is not moved past the BLOCK_HEADER_SIZE.
171 	 */
compressFreeRecPtr(final long value)172 	static int compressFreeRecPtr(final long value) {
173 		// This assert verifies the alignment. We expect the low bits to be clear.
174 		assert (value & (Database.BLOCK_SIZE_DELTA - 1)) == 0;
175 		final int dense = (int) (value >> Database.BLOCK_SIZE_DELTA_BITS);
176 		return dense;
177 	}
178 
179 	/**
180 	 * A free Record Pointer is a pointer to a raw block,
181 	 * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
182 	 */
expandToFreeRecPtr(int value)183 	static long expandToFreeRecPtr(int value) {
184 		/*
185 		 * We need to properly manage the integer that was read. The value will be sign-extended
186 		 * so if the most significant bit is set, the resulting long will look negative. By
187 		 * masking it with ((long)1 << 32) - 1 we remove all the sign-extended bits and just
188 		 * have an unsigned 32-bit value as a long. This gives us one more useful bit in the
189 		 * stored record pointers.
190 		 */
191 		long address = value & 0xFFFFFFFFL;
192 		return address << Database.BLOCK_SIZE_DELTA_BITS;
193 	}
194 
195 	/**
196 	 * A Record Pointer is a pointer as returned by Database.malloc().
197 	 * This is a pointer to a block + BLOCK_HEADER_SIZE.
198 	 */
putRecPtr(final long offset, final long value)199 	public void putRecPtr(final long offset, final long value) {
200 		makeDirty();
201 		int idx = recPtrToIndex(offset);
202 		Database.putRecPtr(value, this.fBuffer, idx);
203 		recordWrite(offset, 4);
204 	}
205 
206 	/**
207 	 * A free Record Pointer is a pointer to a raw block,
208 	 * i.e. the pointer is not moved past the BLOCK_HEADER_SIZE.
209 	 */
putFreeRecPtr(final long offset, final long value)210 	public void putFreeRecPtr(final long offset, final long value) {
211 		makeDirty();
212 		int idx = recPtrToIndex(offset);
213 		putInt(compressFreeRecPtr(value), this.fBuffer, idx);
214 		recordWrite(offset, 4);
215 	}
216 
getRecPtr(final long offset)217 	public long getRecPtr(final long offset) {
218 		final int idx = recPtrToIndex(offset);
219 		return Database.getRecPtr(this.fBuffer, idx);
220 	}
221 
getFreeRecPtr(final long offset)222 	public long getFreeRecPtr(final long offset) {
223 		final int idx = recPtrToIndex(offset);
224 		int value = getInt(this.fBuffer, idx);
225 		return expandToFreeRecPtr(value);
226 	}
227 
put3ByteUnsignedInt(final long offset, final int value)228 	public void put3ByteUnsignedInt(final long offset, final int value) {
229 		makeDirty();
230 		int idx= recPtrToIndex(offset);
231 		this.fBuffer[idx]= (byte) (value >> 16);
232 		this.fBuffer[++idx]= (byte) (value >> 8);
233 		this.fBuffer[++idx]= (byte) (value);
234 		recordWrite(offset, 3);
235 	}
236 
get3ByteUnsignedInt(final long offset)237 	public int get3ByteUnsignedInt(final long offset) {
238 		int idx= recPtrToIndex(offset);
239 		return ((this.fBuffer[idx] & 0xff) << 16) |
240 				((this.fBuffer[++idx] & 0xff) <<  8) |
241 				((this.fBuffer[++idx] & 0xff) <<  0);
242 	}
243 
putShort(final long offset, final short value)244 	public void putShort(final long offset, final short value) {
245 		makeDirty();
246 		int idx= recPtrToIndex(offset);
247 		this.fBuffer[idx]= (byte) (value >> 8);
248 		this.fBuffer[++idx]= (byte) (value);
249 		recordWrite(offset, 2);
250 	}
251 
recordWrite(long offset, int size)252 	private void recordWrite(long offset, int size) {
253 		this.fDatabase.getLog().recordWrite(offset, size);
254 	}
255 
getShort(final long offset)256 	public short getShort(final long offset) {
257 		int idx= recPtrToIndex(offset);
258 		return (short) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
259 	}
260 
getLong(final long offset)261 	public long getLong(final long offset) {
262 		int idx= recPtrToIndex(offset);
263 		return ((((long) this.fBuffer[idx] & 0xff) << 56) |
264 				(((long) this.fBuffer[++idx] & 0xff) << 48) |
265 				(((long) this.fBuffer[++idx] & 0xff) << 40) |
266 				(((long) this.fBuffer[++idx] & 0xff) << 32) |
267 				(((long) this.fBuffer[++idx] & 0xff) << 24) |
268 				(((long) this.fBuffer[++idx] & 0xff) << 16) |
269 				(((long) this.fBuffer[++idx] & 0xff) <<  8) |
270 				(((long) this.fBuffer[++idx] & 0xff) <<  0));
271 	}
272 
getDouble(long offset)273 	public double getDouble(long offset) {
274 		return Double.longBitsToDouble(getLong(offset));
275 	}
276 
getFloat(long offset)277 	public float getFloat(long offset) {
278 		return Float.intBitsToFloat(getInt(offset));
279 	}
280 
putLong(final long offset, final long value)281 	public void putLong(final long offset, final long value) {
282 		makeDirty();
283 		int idx= recPtrToIndex(offset);
284 
285 		this.fBuffer[idx]=   (byte) (value >> 56);
286 		this.fBuffer[++idx]= (byte) (value >> 48);
287 		this.fBuffer[++idx]= (byte) (value >> 40);
288 		this.fBuffer[++idx]= (byte) (value >> 32);
289 		this.fBuffer[++idx]= (byte) (value >> 24);
290 		this.fBuffer[++idx]= (byte) (value >> 16);
291 		this.fBuffer[++idx]= (byte) (value >> 8);
292 		this.fBuffer[++idx]= (byte) (value);
293 		recordWrite(offset, 8);
294 	}
295 
putChar(final long offset, final char value)296 	public void putChar(final long offset, final char value) {
297 		makeDirty();
298 		int idx= recPtrToIndex(offset);
299 		this.fBuffer[idx]= (byte) (value >> 8);
300 		this.fBuffer[++idx]= (byte) (value);
301 		recordWrite(offset, 2);
302 	}
303 
putChars(final long offset, char[] chars, int start, int len)304 	public void putChars(final long offset, char[] chars, int start, int len) {
305 		makeDirty();
306 		int idx= recPtrToIndex(offset)-1;
307 		final int end= start + len;
308 		for (int i = start; i < end; i++) {
309 			char value= chars[i];
310 			this.fBuffer[++idx]= (byte) (value >> 8);
311 			this.fBuffer[++idx]= (byte) (value);
312 		}
313 		recordWrite(offset, len * 2);
314 	}
315 
putCharsAsBytes(final long offset, char[] chars, int start, int len)316 	public void putCharsAsBytes(final long offset, char[] chars, int start, int len) {
317 		makeDirty();
318 		int idx= recPtrToIndex(offset)-1;
319 		final int end= start + len;
320 		for (int i = start; i < end; i++) {
321 			char value= chars[i];
322 			this.fBuffer[++idx]= (byte) (value);
323 		}
324 		recordWrite(offset, len);
325 	}
326 
putDouble(final long offset, double value)327 	public void putDouble(final long offset, double value) {
328 		putLong(offset, Double.doubleToLongBits(value));
329 	}
330 
putFloat(final long offset, float value)331 	public void putFloat(final long offset, float value) {
332 		putInt(offset, Float.floatToIntBits(value));
333 	}
334 
getChar(final long offset)335 	public char getChar(final long offset) {
336 		int idx= recPtrToIndex(offset);
337 		return (char) (((this.fBuffer[idx] << 8) | (this.fBuffer[++idx] & 0xff)));
338 	}
339 
getChars(final long offset, final char[] result, int start, int len)340 	public void getChars(final long offset, final char[] result, int start, int len) {
341 		final ByteBuffer buf= ByteBuffer.wrap(this.fBuffer);
342 		buf.position(recPtrToIndex(offset));
343 		buf.asCharBuffer().get(result, start, len);
344 	}
345 
getCharsFromBytes(final long offset, final char[] result, int start, int len)346 	public void getCharsFromBytes(final long offset, final char[] result, int start, int len) {
347 		final int pos = recPtrToIndex(offset);
348 		for (int i = 0; i < len; i++) {
349 			result[start + i] =  (char) (this.fBuffer[pos + i] & 0xff);
350 		}
351 	}
352 
clear(final long offset, final int length)353 	void clear(final long offset, final int length) {
354 		makeDirty();
355 		int idx = recPtrToIndex(offset);
356 		final int end = idx + length;
357 		if (end > this.fBuffer.length) {
358 			throw new IndexException("Attempting to clear beyond end of chunk. Chunk = " + this.fSequenceNumber //$NON-NLS-1$
359 					+ ", offset = " + offset + ", length = " + length); //$NON-NLS-1$//$NON-NLS-2$
360 		}
361 		for (; idx < end; idx++) {
362 			this.fBuffer[idx] = 0;
363 		}
364 		recordWrite(offset, length);
365 	}
366 
put(final long offset, final byte[] data, final int len)367 	void put(final long offset, final byte[] data, final int len) {
368 		put(offset, data, 0, len);
369 	}
370 
put(final long offset, final byte[] data, int dataPos, final int len)371 	void put(final long offset, final byte[] data, int dataPos, final int len) {
372 		makeDirty();
373 		int idx = recPtrToIndex(offset);
374 		System.arraycopy(data, dataPos, this.fBuffer, idx, len);
375 		recordWrite(offset, len);
376 	}
377 
get(final long offset, byte[] data)378 	public void get(final long offset, byte[] data) {
379 		get(offset, data, 0, data.length);
380 	}
381 
get(final long offset, byte[] data, int dataPos, int len)382 	public void get(final long offset, byte[] data, int dataPos, int len) {
383 		int idx = recPtrToIndex(offset);
384 		System.arraycopy(this.fBuffer, idx, data, dataPos, len);
385 	}
386 
387 	/**
388 	 * Returns a dirtied, writable version of this chunk whose identity won't change until the write lock is released.
389 	 */
getWritableChunk()390 	public Chunk getWritableChunk() {
391 		Chunk result = this.fDatabase.getChunk((long) this.fSequenceNumber * Database.CHUNK_SIZE);
392 		result.makeDirty();
393 		return result;
394 	}
395 }
396