package org.eclipse.jdt.internal.core.nd.field;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.internal.core.nd.ITypeFactory;
import org.eclipse.jdt.internal.core.nd.Nd;
import org.eclipse.jdt.internal.core.nd.db.Database;
import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag;
import org.eclipse.jdt.internal.core.nd.util.MathUtils;
public class FieldList<T> extends BaseField implements IDestructableField {
public final static FieldPointer FIRST_BLOCK;
public final static FieldPointer LAST_BLOCK_WITH_ELEMENTS;
@SuppressWarnings("rawtypes")
private static final StructDef<FieldList> type;
private static final int ;
private static final long MAX_BYTES_IN_A_CHUNK = Database.getBytesThatFitInChunks(1);
private final StructDef<T> elementType;
private final int elementsPerBlock;
private final StructDef<?> ownerType;
private final Tag allocateTag;
private final Tag appendTag;
private final Tag destructTag;
static {
type = StructDef.createAbstract(FieldList.class);
FIRST_BLOCK = type.addPointer();
LAST_BLOCK_WITH_ELEMENTS = type.addPointer();
type.done();
LIST_HEADER_BYTES = MathUtils.roundUpToNearestMultipleOfPowerOfTwo(type.size(), Database.BLOCK_SIZE_DELTA);
}
private static class {
public final static FieldPointer ;
public final static FieldShort ;
public final static FieldShort ;
public static final int ;
@SuppressWarnings("hiding")
private static final StructDef<BlockHeader> ;
static {
type = StructDef.createAbstract(BlockHeader.class);
NEXT_BLOCK = type.addPointer();
BLOCK_SIZE = type.addShort();
ELEMENTS_IN_USE = type.addShort();
type.done();
BLOCK_HEADER_BYTES = MathUtils.roundUpToNearestMultipleOfPowerOfTwo(type.size(), Database.BLOCK_SIZE_DELTA);
}
}
private FieldList(StructDef<?> ownerType, StructDef<T> elementType, int elementsPerBlock) {
this.elementType = elementType;
this.elementsPerBlock = elementsPerBlock;
this.ownerType = ownerType;
int fieldNumber = ownerType.getNumFields();
setFieldName("field " + fieldNumber + ", a " + getClass().getSimpleName()
+ " in struct " + ownerType.getStructName());
this.allocateTag = ModificationLog.createTag("Allocating elements for " + getFieldName());
this.appendTag = ModificationLog.createTag("Appending to " + getFieldName());
this.destructTag = ModificationLog.createTag("Deallocating " + getFieldName());
}
public static <T> FieldList<T> create(StructDef<?> ownerStruct, StructDef<T> elementType) {
return create(ownerStruct, elementType, 1);
}
public static <T> FieldList<T> create(StructDef<?> ownerStruct, StructDef<T> elementType, int elementsPerBlock) {
FieldList<T> result = new FieldList<>(ownerStruct, elementType, elementsPerBlock);
ownerStruct.add(result);
ownerStruct.addDestructableField(result);
return result;
}
private int getElementSize() {
int recordSize = this.elementType.getFactory().getRecordSize();
return MathUtils.roundUpToNearestMultipleOfPowerOfTwo(recordSize, Database.BLOCK_SIZE_DELTA);
}
@Override
public int getRecordSize() {
return LIST_HEADER_BYTES;
}
public List<T> asList(Nd nd, long address) {
long headerStartAddress = address + this.offset;
long firstBlockAddress = FIRST_BLOCK.get(nd, headerStartAddress);
List<T> result = new ArrayList<>();
long nextBlockAddress = firstBlockAddress;
while (nextBlockAddress != 0) {
long currentBlockAddress = nextBlockAddress;
nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
long firstElementInBlockAddress = currentBlockAddress + BlockHeader.BLOCK_HEADER_BYTES;
readElements(result, nd, firstElementInBlockAddress, elementsInBlock);
}
return result;
}
private void readElements(List<T> result, Nd nd, long nextElementAddress, int count) {
ITypeFactory<T> factory = this.elementType.getFactory();
int size = getElementSize();
for (; count > 0; count--) {
result.add(factory.create(nd, nextElementAddress));
nextElementAddress += size;
}
}
public T append(Nd nd, long address) {
Database db = nd.getDB();
db.getLog().start(this.appendTag);
try {
long headerStartAddress = address + this.offset;
long nextBlockAddress = LAST_BLOCK_WITH_ELEMENTS.get(nd, headerStartAddress);
long insertionBlockAddress = nextBlockAddress;
if (nextBlockAddress == 0) {
long newBlockAddress = allocateNewBlock(nd, this.elementsPerBlock);
LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, newBlockAddress);
FIRST_BLOCK.put(nd, headerStartAddress, newBlockAddress);
insertionBlockAddress = newBlockAddress;
}
int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, insertionBlockAddress);
int blockSize = BlockHeader.BLOCK_SIZE.get(nd, insertionBlockAddress);
if (elementsInBlock >= blockSize) {
long nextBlock = BlockHeader.NEXT_BLOCK.get(nd, insertionBlockAddress);
if (nextBlock == 0) {
nextBlock = allocateNewBlock(nd, this.elementsPerBlock);
BlockHeader.NEXT_BLOCK.put(nd, insertionBlockAddress, nextBlock);
}
LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, nextBlock);
insertionBlockAddress = nextBlock;
elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, insertionBlockAddress);
}
BlockHeader.ELEMENTS_IN_USE.put(nd, insertionBlockAddress, (short) (elementsInBlock + 1));
int elementSize = getElementSize();
long resultAddress = insertionBlockAddress + BlockHeader.BLOCK_HEADER_BYTES + elementsInBlock * elementSize;
assert ((resultAddress - Database.BLOCK_HEADER_SIZE) & (Database.BLOCK_SIZE_DELTA - 1)) == 0;
return this.elementType.getFactory().create(nd, resultAddress);
} finally {
db.getLog().end(this.appendTag);
}
}
public void allocate(Nd nd, long address, int numElements) {
Database db = nd.getDB();
db.getLog().start(this.allocateTag);
try {
if (numElements == 0) {
return;
}
long headerStartAddress = address + this.offset;
long nextBlockAddress = LAST_BLOCK_WITH_ELEMENTS.get(nd, headerStartAddress);
int maxBlockSizeThatFitsInAChunk = (int) ((MAX_BYTES_IN_A_CHUNK - BlockHeader.BLOCK_HEADER_BYTES)
/ getElementSize());
if (nextBlockAddress == 0) {
int firstAllocation = Math.min(numElements, maxBlockSizeThatFitsInAChunk);
nextBlockAddress = allocateNewBlock(nd, firstAllocation);
LAST_BLOCK_WITH_ELEMENTS.put(nd, headerStartAddress, nextBlockAddress);
FIRST_BLOCK.put(nd, headerStartAddress, nextBlockAddress);
}
int remainingToAllocate = numElements;
while (true) {
long currentBlockAddress = nextBlockAddress;
nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
int elementsInUse = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
int blockSize = BlockHeader.BLOCK_SIZE.get(nd, currentBlockAddress);
remainingToAllocate -= (blockSize - elementsInUse);
if (remainingToAllocate <= 0) {
break;
}
if (nextBlockAddress == 0) {
nextBlockAddress = allocateNewBlock(nd, Math.min(maxBlockSizeThatFitsInAChunk, numElements));
BlockHeader.NEXT_BLOCK.put(nd, currentBlockAddress, nextBlockAddress);
}
}
} finally {
db.getLog().end(this.allocateTag);
}
}
private long allocateNewBlock(Nd nd, int blockSize) {
short poolId = getMemoryPoolId(nd);
int elementSize = getElementSize();
long bytesNeeded = BlockHeader.BLOCK_HEADER_BYTES + blockSize * elementSize;
if (MAX_BYTES_IN_A_CHUNK - bytesNeeded < elementSize) {
bytesNeeded = MAX_BYTES_IN_A_CHUNK;
}
long result = nd.getDB().malloc(bytesNeeded, poolId);
BlockHeader.BLOCK_SIZE.put(nd, result, (short) blockSize);
return result;
}
private short getMemoryPoolId(Nd nd) {
short poolId = Database.POOL_LINKED_LIST;
if (this.ownerType != null) {
Class<?> structClass = this.ownerType.getStructClass();
if (nd.getTypeRegistry().isRegisteredClass(structClass)) {
poolId = (short) (Database.POOL_FIRST_NODE_TYPE + nd.getNodeType(structClass));
}
}
return poolId;
}
@Override
public void destruct(Nd nd, long address) {
Database db = nd.getDB();
db.getLog().start(this.destructTag);
try {
short poolId = getMemoryPoolId(nd);
long headerStartAddress = address + this.offset;
long firstBlockAddress = FIRST_BLOCK.get(nd, headerStartAddress);
long nextBlockAddress = firstBlockAddress;
while (nextBlockAddress != 0) {
long currentBlockAddress = nextBlockAddress;
nextBlockAddress = BlockHeader.NEXT_BLOCK.get(nd, currentBlockAddress);
int elementsInBlock = BlockHeader.ELEMENTS_IN_USE.get(nd, currentBlockAddress);
destructElements(nd, currentBlockAddress + BlockHeader.BLOCK_HEADER_BYTES, elementsInBlock);
db.free(currentBlockAddress, poolId);
}
db.clearRange(headerStartAddress, getRecordSize());
} finally {
db.getLog().end(this.destructTag);
}
}
private void destructElements(Nd nd, long nextElementAddress, int count) {
ITypeFactory<T> factory = this.elementType.getFactory();
int size = getElementSize();
while (--count >= 0) {
factory.destruct(nd, nextElementAddress);
nextElementAddress += size;
}
}
}