lttng: Re-enable API tracking
[deliverable/tracecompass.git] / org.eclipse.linuxtools.ctf.core / src / org / eclipse / linuxtools / ctf / core / event / io / BitBuffer.java
CommitLineData
486efb2e
AM
1/*******************************************************************************.
2 * Copyright (c) 2011-2012 Ericsson, Ecole Polytechnique de Montreal and others
3 *
4 * All rights reserved. This program and the accompanying materials are made
5 * available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
2b50c5ac
MK
9 * Contributors:
10 * Matthew Khouzam - Initial Design and implementation + overhaul
11 * Francis Giraldeau - Initial API and implementation
12 * Philippe Proulx - Some refinement and optimization
13 * Etienne Bergeron <Etienne.Bergeron@gmail.com> - fix zero size read + cleanup
486efb2e
AM
14 *******************************************************************************/
15
16package org.eclipse.linuxtools.ctf.core.event.io;
17
486efb2e
AM
18import java.nio.ByteBuffer;
19import java.nio.ByteOrder;
20
db8e8f7d
AM
21import org.eclipse.linuxtools.ctf.core.trace.CTFReaderException;
22
486efb2e
AM
23/**
24 * <b><u>BitBuffer</u></b>
25 * <p>
26 * A bitwise buffer capable of accessing fields with bit offsets.
2b50c5ac 27 *
486efb2e
AM
28 * @since 2.0
29 */
0594c61c 30public final class BitBuffer {
486efb2e
AM
31
32 // ------------------------------------------------------------------------
33 // Constants
34 // ------------------------------------------------------------------------
35
36 /* default bit width */
37 /** 8 bits to a char */
38 public static final int BIT_CHAR = 8;
39 /** 16 bits to a short */
40 public static final int BIT_SHORT = 16;
41 /** 32 bits to an int */
42 public static final int BIT_INT = 32;
43 /** 32 bits to a float */
44 public static final int BIT_FLOAT = 32;
45 /** 64 bits to a long */
46 public static final int BIT_LONG = 64;
47
48 // ------------------------------------------------------------------------
49 // Attributes
50 // ------------------------------------------------------------------------
51
4c67e724
MK
52 private ByteBuffer fBuffer;
53 private long fPosition;
54 private ByteOrder fByteOrder;
486efb2e
AM
55
56 // ------------------------------------------------------------------------
57 // Constructors
58 // ------------------------------------------------------------------------
59 /**
74e4b6b9 60 * Default constructor, makes a big-endian buffer
486efb2e
AM
61 */
62 public BitBuffer() {
63 this(null, ByteOrder.BIG_ENDIAN);
64 }
65
66 /**
74e4b6b9 67 * Constructor, makes a big-endian buffer
486efb2e
AM
68 *
69 * @param buf
70 * the bytebuffer to read
71 */
72 public BitBuffer(ByteBuffer buf) {
73 this(buf, ByteOrder.BIG_ENDIAN);
74 }
75
76 /**
74e4b6b9 77 * Constructor that is fully parameterizable
486efb2e
AM
78 *
79 * @param buf
80 * the buffer to read
81 * @param order
74e4b6b9 82 * the byte order (big-endian, little-endian, network?)
486efb2e
AM
83 */
84 public BitBuffer(ByteBuffer buf, ByteOrder order) {
85 setByteBuffer(buf);
86 setByteOrder(order);
4c67e724
MK
87 resetPosition();
88 }
89
90 private void resetPosition() {
91 fPosition = 0;
486efb2e
AM
92 }
93
94 // ------------------------------------------------------------------------
95 // 'Get' operations on buffer
96 // ------------------------------------------------------------------------
97
98 /**
99 * Relative <i>get</i> method for reading 32-bit integer.
100 *
101 * Reads next four bytes from the current bit position according to current
102 * byte order.
103 *
2b50c5ac 104 * @return The int value (signed) read from the buffer
db8e8f7d 105 * @throws CTFReaderException
2b50c5ac
MK
106 * An error occurred reading the long. This exception can be
107 * raised if the buffer tries to read out of bounds
486efb2e 108 */
db8e8f7d 109 public int getInt() throws CTFReaderException {
0594c61c 110 return getInt(BIT_INT, true);
486efb2e
AM
111 }
112
2b50c5ac
MK
113 /**
114 * Relative <i>get</i> method for reading 64-bit integer.
115 *
116 * Reads next eight bytes from the current bit position according to current
117 * byte order.
118 *
119 * @return The long value (signed) read from the buffer
120 * @throws CTFReaderException
121 * An error occurred reading the long. This exception can be
122 * raised if the buffer tries to read out of bounds
123 */
124 public long getLong() throws CTFReaderException {
125 return get(BIT_LONG, true);
126 }
127
128 /**
129 * Relative <i>get</i> method for reading long of <i>length</i> bits.
130 *
131 * Reads <i>length</i> bits starting at the current position. The result is
132 * signed extended if <i>signed</i> is true. The current position is
133 * increased of <i>length</i> bits.
134 *
135 * @param length
136 * The length in bits of this integer
137 * @param signed
138 * The sign extended flag
139 * @return The long value read from the buffer
140 * @throws CTFReaderException
141 * An error occurred reading the data. If more than 64 bits at a
142 * time are read, or the buffer is read beyond its end, this
143 * exception will be raised.
144 */
145 public long get(int length, boolean signed) throws CTFReaderException {
146 if (length > BIT_LONG) {
147 throw new CTFReaderException("Cannot read a long longer than 64 bits. Rquested: " + length); //$NON-NLS-1$
148 }
149 if (length > BIT_INT) {
150 final int highShift = length - BIT_INT;
151 long a = getInt();
152 long b = getInt(highShift, false);
153 long retVal;
154 /* Cast the signed-extended int into a unsigned int. */
155 a &= 0xFFFFFFFFL;
156 b &= (1L << highShift) - 1L;
157
4c67e724 158 retVal = (fByteOrder == ByteOrder.BIG_ENDIAN) ? ((a << highShift) | b) : ((b << BIT_INT) | a);
2b50c5ac
MK
159 /* sign extend */
160 if (signed) {
161 int signExtendBits = BIT_LONG - length;
162 retVal = (retVal << signExtendBits) >> signExtendBits;
163 }
164 return retVal;
165 }
166 long retVal = getInt(length, signed);
167 return (signed ? retVal : (retVal & 0xFFFFFFFFL));
168 }
169
486efb2e
AM
170 /**
171 * Relative <i>get</i> method for reading integer of <i>length</i> bits.
172 *
173 * Reads <i>length</i> bits starting at the current position. The result is
174 * signed extended if <i>signed</i> is true. The current position is
175 * increased of <i>length</i> bits.
176 *
177 * @param length
178 * The length in bits of this integer
179 * @param signed
180 * The sign extended flag
181 * @return The int value read from the buffer
db8e8f7d
AM
182 * @throws CTFReaderException
183 * An error occurred reading the data. When the buffer is read
184 * beyond its end, this exception will be raised.
486efb2e 185 */
2b50c5ac 186 private int getInt(int length, boolean signed) throws CTFReaderException {
74e4b6b9
EB
187
188 /* Nothing to read. */
486efb2e
AM
189 if (length == 0) {
190 return 0;
191 }
74e4b6b9
EB
192
193 /* Validate that the buffer has enough bits. */
194 if (!canRead(length)) {
db8e8f7d
AM
195 throw new CTFReaderException("Cannot read the integer, " + //$NON-NLS-1$
196 "the buffer does not have enough remaining space. " + //$NON-NLS-1$
197 "Requested:" + length); //$NON-NLS-1$
74e4b6b9
EB
198 }
199
200 /* Get the value from the byte buffer. */
201 int val = 0;
486efb2e
AM
202 boolean gotIt = false;
203
2b50c5ac
MK
204 /*
205 * Try a fast read when the position is byte-aligned by using
206 * java.nio.ByteBuffer's native methods
207 */
208 /*
209 * A faster alignment detection as the compiler cannot guaranty that pos
210 * is always positive.
211 */
4c67e724 212 if ((fPosition & (BitBuffer.BIT_CHAR - 1)) == 0) {
486efb2e
AM
213 switch (length) {
214 case BitBuffer.BIT_CHAR:
215 // Byte
4c67e724 216 val = fBuffer.get((int) (fPosition / 8));
74e4b6b9
EB
217 if (!signed) {
218 val = val & 0xff;
486efb2e
AM
219 }
220 gotIt = true;
221 break;
222
223 case BitBuffer.BIT_SHORT:
224 // Word
4c67e724 225 val = fBuffer.getShort((int) (fPosition / 8));
74e4b6b9
EB
226 if (!signed) {
227 val = val & 0xffff;
486efb2e
AM
228 }
229 gotIt = true;
230 break;
231
232 case BitBuffer.BIT_INT:
233 // Double word
4c67e724 234 val = fBuffer.getInt((int) (fPosition / 8));
486efb2e
AM
235 gotIt = true;
236 break;
237
238 default:
239 break;
240 }
241 }
74e4b6b9
EB
242
243 /* When not byte-aligned, fall-back to a general decoder. */
486efb2e
AM
244 if (!gotIt) {
245 // Nothing read yet: use longer methods
4c67e724
MK
246 if (fByteOrder == ByteOrder.LITTLE_ENDIAN) {
247 val = getIntLE(fPosition, length, signed);
486efb2e 248 } else {
4c67e724 249 val = getIntBE(fPosition, length, signed);
486efb2e
AM
250 }
251 }
4c67e724 252 fPosition += length;
486efb2e
AM
253
254 return val;
255 }
256
47ca6c05 257 private int getIntBE(long index, int length, boolean signed) {
486efb2e 258 assert ((length > 0) && (length <= BIT_INT));
47ca6c05
SM
259 long end = index + length;
260 int startByte = (int) (index / BIT_CHAR);
261 int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR);
486efb2e
AM
262 int currByte, lshift, cshift, mask, cmask, cache;
263 int value = 0;
264
265 currByte = startByte;
4c67e724 266 cache = fBuffer.get(currByte) & 0xFF;
486efb2e
AM
267 boolean isNeg = (cache & (1 << (BIT_CHAR - (index % BIT_CHAR) - 1))) != 0;
268 if (signed && isNeg) {
269 value = ~0;
270 }
271 if (startByte == (endByte - 1)) {
272 cmask = cache >>> ((BIT_CHAR - (end % BIT_CHAR)) % BIT_CHAR);
273 if (((length) % BIT_CHAR) > 0) {
274 mask = ~((~0) << length);
275 cmask &= mask;
276 }
277 value <<= length;
278 value |= cmask;
279 return value;
280 }
47ca6c05 281 cshift = (int) (index % BIT_CHAR);
486efb2e
AM
282 if (cshift > 0) {
283 mask = ~((~0) << (BIT_CHAR - cshift));
284 cmask = cache & mask;
285 lshift = BIT_CHAR - cshift;
286 value <<= lshift;
287 value |= cmask;
486efb2e
AM
288 currByte++;
289 }
290 for (; currByte < (endByte - 1); currByte++) {
291 value <<= BIT_CHAR;
4c67e724 292 value |= fBuffer.get(currByte) & 0xFF;
486efb2e 293 }
47ca6c05 294 lshift = (int) (end % BIT_CHAR);
486efb2e
AM
295 if (lshift > 0) {
296 mask = ~((~0) << lshift);
4c67e724 297 cmask = fBuffer.get(currByte) & 0xFF;
486efb2e
AM
298 cmask >>>= BIT_CHAR - lshift;
299 cmask &= mask;
300 value <<= lshift;
301 value |= cmask;
302 } else {
303 value <<= BIT_CHAR;
4c67e724 304 value |= fBuffer.get(currByte) & 0xFF;
486efb2e
AM
305 }
306 return value;
307 }
308
47ca6c05 309 private int getIntLE(long index, int length, boolean signed) {
486efb2e 310 assert ((length > 0) && (length <= BIT_INT));
47ca6c05
SM
311 long end = index + length;
312 int startByte = (int) (index / BIT_CHAR);
313 int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR);
486efb2e
AM
314 int currByte, lshift, cshift, mask, cmask, cache, mod;
315 int value = 0;
316
317 currByte = endByte - 1;
4c67e724 318 cache = fBuffer.get(currByte) & 0xFF;
47ca6c05 319 mod = (int) (end % BIT_CHAR);
486efb2e
AM
320 lshift = (mod > 0) ? mod : BIT_CHAR;
321 boolean isNeg = (cache & (1 << (lshift - 1))) != 0;
322 if (signed && isNeg) {
323 value = ~0;
324 }
325 if (startByte == (endByte - 1)) {
326 cmask = cache >>> (index % BIT_CHAR);
327 if (((length) % BIT_CHAR) > 0) {
328 mask = ~((~0) << length);
329 cmask &= mask;
330 }
331 value <<= length;
332 value |= cmask;
333 return value;
334 }
47ca6c05 335 cshift = (int) (end % BIT_CHAR);
486efb2e
AM
336 if (cshift > 0) {
337 mask = ~((~0) << cshift);
338 cmask = cache & mask;
339 value <<= cshift;
340 value |= cmask;
486efb2e
AM
341 currByte--;
342 }
343 for (; currByte >= (startByte + 1); currByte--) {
344 value <<= BIT_CHAR;
4c67e724 345 value |= fBuffer.get(currByte) & 0xFF;
486efb2e 346 }
47ca6c05 347 lshift = (int) (index % BIT_CHAR);
486efb2e
AM
348 if (lshift > 0) {
349 mask = ~((~0) << (BIT_CHAR - lshift));
4c67e724 350 cmask = fBuffer.get(currByte) & 0xFF;
486efb2e
AM
351 cmask >>>= lshift;
352 cmask &= mask;
353 value <<= (BIT_CHAR - lshift);
354 value |= cmask;
355 } else {
356 value <<= BIT_CHAR;
4c67e724 357 value |= fBuffer.get(currByte) & 0xFF;
486efb2e
AM
358 }
359 return value;
360 }
361
362 // ------------------------------------------------------------------------
363 // 'Put' operations on buffer
364 // ------------------------------------------------------------------------
365
366 /**
367 * Relative <i>put</i> method to write signed 32-bit integer.
368 *
369 * Write four bytes starting from current bit position in the buffer
370 * according to the current byte order. The current position is increased of
371 * <i>length</i> bits.
372 *
373 * @param value
374 * The int value to write
db8e8f7d
AM
375 * @throws CTFReaderException
376 * An error occurred writing the data. If the buffer is written
377 * beyond its end, this exception will be raised.
486efb2e 378 */
db8e8f7d 379 public void putInt(int value) throws CTFReaderException {
486efb2e
AM
380 putInt(BIT_INT, value);
381 }
382
383 /**
384 * Relative <i>put</i> method to write <i>length</i> bits integer.
385 *
386 * Writes <i>length</i> lower-order bits from the provided <i>value</i>,
387 * starting from current bit position in the buffer. Sequential bytes are
388 * written according to the current byte order. The sign bit is carried to
389 * the MSB if signed is true. The sign bit is included in <i>length</i>. The
390 * current position is increased of <i>length</i>.
391 *
392 * @param length
393 * The number of bits to write
394 * @param value
395 * The value to write
db8e8f7d
AM
396 * @throws CTFReaderException
397 * An error occurred writing the data. If the buffer is written
398 * beyond its end, this exception will be raised.
486efb2e 399 */
db8e8f7d 400 public void putInt(int length, int value) throws CTFReaderException {
4c67e724 401 final long curPos = fPosition;
486efb2e
AM
402
403 if (!canRead(length)) {
db8e8f7d
AM
404 throw new CTFReaderException("Cannot write to bitbuffer, " //$NON-NLS-1$
405 + "insufficient space. Requested: " + length); //$NON-NLS-1$
486efb2e
AM
406 }
407 if (length == 0) {
408 return;
409 }
4c67e724 410 if (fByteOrder == ByteOrder.LITTLE_ENDIAN) {
486efb2e
AM
411 putIntLE(curPos, length, value);
412 } else {
413 putIntBE(curPos, length, value);
414 }
4c67e724 415 fPosition += length;
486efb2e
AM
416 }
417
47ca6c05 418 private void putIntBE(long index, int length, int value) {
486efb2e 419 assert ((length > 0) && (length <= BIT_INT));
47ca6c05
SM
420 long end = index + length;
421 int startByte = (int) (index / BIT_CHAR);
422 int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR);
486efb2e
AM
423 int currByte, lshift, cshift, mask, cmask;
424 int correctedValue = value;
425
426 /*
427 * mask v high bits. Works for unsigned and two complement signed
428 * numbers which value do not overflow on length bits.
429 */
430
431 if (length < BIT_INT) {
432 correctedValue &= ~(~0 << length);
433 }
434
435 /* sub byte */
436 if (startByte == (endByte - 1)) {
47ca6c05 437 lshift = (int) ((BIT_CHAR - (end % BIT_CHAR)) % BIT_CHAR);
486efb2e
AM
438 mask = ~((~0) << lshift);
439 if ((index % BIT_CHAR) > 0) {
440 mask |= (~(0)) << (BIT_CHAR - (index % BIT_CHAR));
441 }
442 cmask = correctedValue << lshift;
443 /*
74e4b6b9
EB
444 * low bits are cleared because of left-shift and high bits are
445 * already cleared
486efb2e
AM
446 */
447 cmask &= ~mask;
4c67e724
MK
448 int b = fBuffer.get(startByte) & 0xFF;
449 fBuffer.put(startByte, (byte) ((b & mask) | cmask));
486efb2e
AM
450 return;
451 }
452
453 /* head byte contains MSB */
454 currByte = endByte - 1;
47ca6c05 455 cshift = (int) (end % BIT_CHAR);
486efb2e
AM
456 if (cshift > 0) {
457 lshift = BIT_CHAR - cshift;
458 mask = ~((~0) << lshift);
459 cmask = correctedValue << lshift;
460 cmask &= ~mask;
4c67e724
MK
461 int b = fBuffer.get(currByte) & 0xFF;
462 fBuffer.put(currByte, (byte) ((b & mask) | cmask));
486efb2e 463 correctedValue >>>= cshift;
486efb2e
AM
464 currByte--;
465 }
466
467 /* middle byte(s) */
468 for (; currByte >= (startByte + 1); currByte--) {
4c67e724 469 fBuffer.put(currByte, (byte) correctedValue);
486efb2e
AM
470 correctedValue >>>= BIT_CHAR;
471 }
472 /* end byte contains LSB */
473 if ((index % BIT_CHAR) > 0) {
474 mask = (~0) << (BIT_CHAR - (index % BIT_CHAR));
475 cmask = correctedValue & ~mask;
4c67e724
MK
476 int b = fBuffer.get(currByte) & 0xFF;
477 fBuffer.put(currByte, (byte) ((b & mask) | cmask));
486efb2e 478 } else {
4c67e724 479 fBuffer.put(currByte, (byte) correctedValue);
486efb2e
AM
480 }
481 }
482
47ca6c05 483 private void putIntLE(long index, int length, int value) {
486efb2e 484 assert ((length > 0) && (length <= BIT_INT));
47ca6c05
SM
485 long end = index + length;
486 int startByte = (int) (index / BIT_CHAR);
487 int endByte = (int) ((end + (BIT_CHAR - 1)) / BIT_CHAR);
486efb2e
AM
488 int currByte, lshift, cshift, mask, cmask;
489 int correctedValue = value;
490
491 /*
492 * mask v high bits. Works for unsigned and two complement signed
493 * numbers which value do not overflow on length bits.
494 */
495
496 if (length < BIT_INT) {
497 correctedValue &= ~(~0 << length);
498 }
499
500 /* sub byte */
501 if (startByte == (endByte - 1)) {
47ca6c05 502 lshift = (int) (index % BIT_CHAR);
486efb2e
AM
503 mask = ~((~0) << lshift);
504 if ((end % BIT_CHAR) > 0) {
505 mask |= (~(0)) << (end % BIT_CHAR);
506 }
507 cmask = correctedValue << lshift;
508 /*
74e4b6b9
EB
509 * low bits are cleared because of left-shift and high bits are
510 * already cleared
486efb2e
AM
511 */
512 cmask &= ~mask;
4c67e724
MK
513 int b = fBuffer.get(startByte) & 0xFF;
514 fBuffer.put(startByte, (byte) ((b & mask) | cmask));
486efb2e
AM
515 return;
516 }
517
518 /* head byte */
519 currByte = startByte;
47ca6c05 520 cshift = (int) (index % BIT_CHAR);
486efb2e
AM
521 if (cshift > 0) {
522 mask = ~((~0) << cshift);
523 cmask = correctedValue << cshift;
524 cmask &= ~mask;
4c67e724
MK
525 int b = fBuffer.get(currByte) & 0xFF;
526 fBuffer.put(currByte, (byte) ((b & mask) | cmask));
486efb2e 527 correctedValue >>>= BIT_CHAR - cshift;
486efb2e
AM
528 currByte++;
529 }
530
531 /* middle byte(s) */
532 for (; currByte < (endByte - 1); currByte++) {
4c67e724 533 fBuffer.put(currByte, (byte) correctedValue);
486efb2e
AM
534 correctedValue >>>= BIT_CHAR;
535 }
536 /* end byte */
537 if ((end % BIT_CHAR) > 0) {
538 mask = (~0) << (end % BIT_CHAR);
539 cmask = correctedValue & ~mask;
4c67e724
MK
540 int b = fBuffer.get(currByte) & 0xFF;
541 fBuffer.put(currByte, (byte) ((b & mask) | cmask));
486efb2e 542 } else {
4c67e724 543 fBuffer.put(currByte, (byte) correctedValue);
486efb2e
AM
544 }
545 }
546
547 // ------------------------------------------------------------------------
548 // Buffer attributes handling
549 // ------------------------------------------------------------------------
550
551 /**
552 * Can this buffer be read for thus amount of bits?
553 *
554 * @param length
555 * the length in bits to read
556 * @return does the buffer have enough room to read the next "length"
557 */
558 public boolean canRead(int length) {
4c67e724 559 if (fBuffer == null) {
486efb2e
AM
560 return false;
561 }
562
4c67e724 563 if ((fPosition + length) > (((long) fBuffer.capacity()) * BIT_CHAR)) {
486efb2e
AM
564 return false;
565 }
566 return true;
567 }
568
569 /**
570 * Sets the order of the buffer.
571 *
572 * @param order
573 * The order of the buffer.
574 */
575 public void setByteOrder(ByteOrder order) {
4c67e724
MK
576 fByteOrder = order;
577 if (fBuffer != null) {
578 fBuffer.order(order);
486efb2e
AM
579 }
580 }
581
582 /**
583 * Sets the order of the buffer.
584 *
585 * @return The order of the buffer.
586 */
587 public ByteOrder getByteOrder() {
4c67e724 588 return fByteOrder;
486efb2e
AM
589 }
590
591 /**
592 * Sets the position in the buffer.
593 *
594 * @param newPosition
595 * The new position of the buffer.
4c67e724
MK
596 * @throws CTFReaderException
597 * Thrown on out of bounds exceptions
486efb2e 598 */
4c67e724
MK
599 public void position(long newPosition) throws CTFReaderException {
600
601 if ((fBuffer != null) && (newPosition / 8) > fBuffer.capacity()) {
602 throw new CTFReaderException("Out of bounds exception on a position move, attempting to access position: " + newPosition); //$NON-NLS-1$
603 }
604 fPosition = newPosition;
486efb2e
AM
605 }
606
607 /**
608 *
609 * Sets the position in the buffer.
610 *
611 * @return order The position of the buffer.
612 */
47ca6c05 613 public long position() {
4c67e724 614 return fPosition;
486efb2e
AM
615 }
616
617 /**
618 * Sets the byte buffer
619 *
620 * @param buf
621 * the byte buffer
622 */
623 public void setByteBuffer(ByteBuffer buf) {
4c67e724 624 fBuffer = buf;
486efb2e 625 if (buf != null) {
4c67e724 626 fBuffer.order(fByteOrder);
486efb2e
AM
627 }
628 clear();
629 }
630
631 /**
632 * Gets the byte buffer
633 *
634 * @return The byte buffer
635 */
636 public ByteBuffer getByteBuffer() {
4c67e724 637 return fBuffer;
486efb2e
AM
638 }
639
640 /**
74e4b6b9 641 * Resets the bitbuffer.
486efb2e
AM
642 */
643 public void clear() {
4c67e724
MK
644 resetPosition();
645 if (fBuffer == null) {
486efb2e
AM
646 return;
647 }
4c67e724 648 fBuffer.clear();
486efb2e
AM
649 }
650
651}
This page took 0.061207 seconds and 5 git commands to generate.