package org.eclipse.jdt.internal.core.nd.field;
import org.eclipse.jdt.internal.core.nd.INdStruct;
import org.eclipse.jdt.internal.core.nd.ITypeFactory;
import org.eclipse.jdt.internal.core.nd.Nd;
import org.eclipse.jdt.internal.core.nd.NdNode;
import org.eclipse.jdt.internal.core.nd.db.ModificationLog;
import org.eclipse.jdt.internal.core.nd.db.Database;
import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag;
public class FieldManyToOne<T extends INdStruct> extends BaseField implements IDestructableField, IRefCountedField {
public final static FieldPointer TARGET;
public final static FieldInt BACKPOINTER_INDEX;
StructDef<T> targetType;
final StructDef<? extends INdStruct> localType;
FieldOneToMany<?> backPointer;
@SuppressWarnings("rawtypes")
private final static StructDef<FieldManyToOne> type;
public final boolean pointsToOwner;
private final Tag putTag;
private final Tag destructTag;
private boolean permitsNull = true;
static {
type = StructDef.createAbstract(FieldManyToOne.class);
TARGET = type.addPointer();
BACKPOINTER_INDEX = type.addInt();
type.done();
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private FieldManyToOne(StructDef<? extends INdStruct> localType, FieldOneToMany<?> backPointer, boolean pointsToOwner) {
this.localType = localType;
this.pointsToOwner = pointsToOwner;
if (backPointer != null) {
if (backPointer.forwardPointer != null && backPointer.forwardPointer != this) {
throw new IllegalArgumentException(
"Attempted to construct a FieldNodePointer referring to a backpointer list that is already in use"
+ " by another field");
}
backPointer.targetType = (StructDef) localType;
this.targetType = (StructDef) backPointer.localType;
backPointer.forwardPointer = this;
}
this.backPointer = backPointer;
setFieldName("field " + localType.getNumFields() + ", a " + getClass().getSimpleName()
+ " in struct " + localType.getStructName());
this.putTag = ModificationLog.createTag("Writing " + getFieldName());
this.destructTag = ModificationLog.createTag("Destructing " + getFieldName());
}
public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> createNonNull(StructDef<B> builder,
FieldOneToMany<B> forwardPointer) {
FieldManyToOne<T> result = create(builder, forwardPointer);
result.permitsNull = false;
return result;
}
public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> create(StructDef<B> builder,
FieldOneToMany<B> forwardPointer) {
FieldManyToOne<T> result = new FieldManyToOne<T>(builder, forwardPointer, false);
builder.add(result);
builder.addDestructableField(result);
return result;
}
public static <T extends INdStruct, B extends INdStruct> FieldManyToOne<T> createOwner(StructDef<B> builder,
FieldOneToMany<B> forwardPointer) {
if (!NdNode.class.isAssignableFrom(builder.getStructClass())) {
throw new IllegalArgumentException(FieldManyToOne.class.getSimpleName() + " can't be the owner of "
+ builder.getStructClass().getSimpleName() + " because the latter isn't a subclass of "
+ NdNode.class.getSimpleName());
}
FieldManyToOne<T> result = new FieldManyToOne<T>(builder, forwardPointer, true);
builder.add(result);
builder.addDestructableField(result);
builder.addOwnerField(result);
return result;
}
public FieldManyToOne<T> permitNull(boolean permitted) {
this.permitsNull = permitted;
return this;
}
public T get(Nd nd, long address) {
return NdNode.load(nd, getAddress(nd, address), this.targetType);
}
public long getAddress(Nd nd, long address) {
long result = nd.getDB().getRecPtr(address + this.offset);
if (!this.permitsNull && result == 0) {
throw nd.describeProblem()
.addProblemAddress(this, address)
.build("Database contained a null in a non-null field");
}
return result;
}
public void put(Nd nd, long address, T value) {
if (value != null) {
put(nd, address, value.getAddress());
} else if (this.permitsNull) {
put(nd, address, 0);
} else {
throw new IllegalArgumentException("Attempted to write a null into a non-null field");
}
}
public void put(Nd nd, long address, long newTargetAddress) {
Database db = nd.getDB();
db.getLog().start(this.putTag);
try {
long fieldStart = address + this.offset;
if (this.backPointer == null) {
throw new IllegalStateException(
getClass().getSimpleName() + " must be associated with a " + FieldOneToMany.class.getSimpleName());
}
long oldTargetAddress = TARGET.get(nd, fieldStart);
if (oldTargetAddress == newTargetAddress) {
return;
}
detachFromOldTarget(nd, address, oldTargetAddress);
TARGET.put(nd, fieldStart, newTargetAddress);
if (newTargetAddress != 0) {
BACKPOINTER_INDEX.put(nd, fieldStart, this.backPointer.add(nd, newTargetAddress, address));
} else {
if (this.pointsToOwner) {
nd.scheduleDeletion(address);
}
}
} finally {
db.getLog().end(this.putTag);
}
}
protected void detachFromOldTarget(Nd nd, long address, long oldTargetAddress) {
long fieldStart = address + this.offset;
if (oldTargetAddress != 0) {
int oldIndex = BACKPOINTER_INDEX.get(nd, fieldStart);
this.backPointer.remove(nd, oldTargetAddress, oldIndex);
if (this.targetType.isNdNode()) {
short targetTypeId = NdNode.NODE_TYPE.get(nd, oldTargetAddress);
ITypeFactory<? extends NdNode> typeFactory = nd.getTypeFactory(targetTypeId);
if (typeFactory.getDeletionSemantics() == StructDef.DeletionSemantics.REFCOUNTED
&& typeFactory.isReadyForDeletion(nd, oldTargetAddress)) {
nd.scheduleDeletion(oldTargetAddress);
}
}
}
}
void adjustIndex(Nd nd, long address, int index) {
BACKPOINTER_INDEX.put(nd, address + this.offset, index);
}
@Override
public void destruct(Nd nd, long address) {
Database db = nd.getDB();
db.getLog().start(this.destructTag);
try {
long fieldStart = address + this.offset;
long oldTargetAddress = TARGET.get(nd, fieldStart);
detachFromOldTarget(nd, address, oldTargetAddress);
TARGET.put(nd, fieldStart, 0);
} finally {
db.getLog().end(this.destructTag);
}
}
void clearedByBackPointer(Nd nd, long address) {
long fieldStart = this.offset + address;
FieldManyToOne.TARGET.put(nd, fieldStart, 0);
FieldManyToOne.BACKPOINTER_INDEX.put(nd, fieldStart, 0);
}
@Override
public int getRecordSize() {
return type.size();
}
@Override
public boolean hasReferences(Nd nd, long address) {
long fieldStart = this.offset + address;
long target = TARGET.get(nd, fieldStart);
return target != 0;
}
}