blob: 8f95c681fe7442026d3f366dfbd415f51e79ee7b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2016 Google, Inc and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Xenos (Google) - Initial implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.nd.field;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.internal.core.nd.Nd;
import org.eclipse.jdt.internal.core.nd.NdNode;
import org.eclipse.jdt.internal.core.nd.RawGrowableArray;
/**
* Holds the 1 side of a 1..n relationship between two objects. FieldNodePointer and FieldBackPointer fields always go
* together in pairs.
*/
public class FieldOneToMany<T extends NdNode> implements IDestructableField, IRefCountedField, IField {
private int offset;
public Class<T> targetType;
public final Class<? extends NdNode> localType;
private final RawGrowableArray backPointerArray;
FieldManyToOne<?> forwardPointer;
public interface Visitor<T> {
public void visit(int index, T toVisit);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private FieldOneToMany(Class<? extends NdNode> localType, FieldManyToOne<? extends NdNode> forwardPointer,
int inlineElements) {
this.localType = localType;
if (forwardPointer != null) {
if (forwardPointer.backPointer != null && forwardPointer.backPointer != this) {
throw new IllegalArgumentException(
"Attempted to construct a FieldBackPointer referring to a forward pointer that is already in use" //$NON-NLS-1$
+ " by another field"); //$NON-NLS-1$
}
forwardPointer.targetType = (Class)localType;
this.targetType = (Class)forwardPointer.localType;
forwardPointer.backPointer = this;
}
this.forwardPointer = forwardPointer;
this.backPointerArray = new RawGrowableArray(inlineElements);
}
/**
* Creates a {@link FieldOneToMany} using the given builder. It will hold the many side of a one-to-many
* relationship with nodeType.
*
* @param builder builder that is being used to construct the struct containing this field
* @param forwardPointer field of the model object which holds the one side of this one-to-many relationship
* @param inlineElementCount number of inline elements. If this is nonzero, space for this number elements is
* preallocated and reserved in the header. The first few elements inserted will be stored here. For relationships
* which will usually have more than a certain number of participants, using a small number of inline elements will
* offer a performance improvement. For relationships that will normally be empty, this should be 0.
* @return the newly constructed backpointer field
*/
public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder,
FieldManyToOne<B> forwardPointer, int inlineElementCount) {
FieldOneToMany<T> result = new FieldOneToMany<T>(builder.getStructClass(), forwardPointer,
inlineElementCount);
builder.add(result);
builder.addDestructableField(result);
builder.addRefCountedField(result);
return result;
}
public static <T extends NdNode, B extends NdNode> FieldOneToMany<T> create(StructDef<B> builder,
FieldManyToOne<B> forwardPointer) {
return create(builder, forwardPointer, 0);
}
public void accept(Nd nd, long address, Visitor<T> visitor) {
int size = size(nd, address);
for (int idx = 0; idx < size; idx++) {
visitor.visit(idx, get(nd, address, idx));
}
}
public List<T> asList(Nd nd, long address) {
final List<T> result = new ArrayList<>(size(nd, address));
accept(nd, address, new Visitor<T>() {
@Override
public void visit(int index, T toVisit) {
result.add(toVisit);
}
});
return result;
}
public boolean isEmpty(Nd nd, long address) {
return this.backPointerArray.isEmpty(nd, address + this.offset);
}
public int size(Nd nd, long address) {
return this.backPointerArray.size(nd, address + this.offset);
}
public T get(Nd nd, long address, int index) {
long nextPointer = this.backPointerArray.get(nd, address + this.offset, index);
return NdNode.load(nd, nextPointer, this.targetType);
}
/**
* Removes the given index from the list. If another element is swapped into the removed element's
* location, that element's index will be updated. The removed element itself will not be modified. The
* caller is responsible for nulling out the pointer and updating its index if necessary.
* <p>
* Not intended to be called by clients. The normal way to remove something from a backpointer list is
* by calling {@link FieldManyToOne#put}, which performs the appropriate removals automatically.
*/
void remove(Nd nd, long address, int index) {
long swappedElement = this.backPointerArray.remove(nd, address + this.offset, index);
if (swappedElement != 0) {
this.forwardPointer.adjustIndex(nd, swappedElement, index);
}
}
/**
* Addss the given forward pointer to the list and returns the insertion index. This should not be invoked
* directly by clients. The normal way to insert into a backpointer list is to assign a forward pointer.
*/
int add(Nd nd, long address, long value) {
return this.backPointerArray.add(nd, address + this.offset, value);
}
/**
* Returns the record size of the back pointer list
*/
public int getRecordSize() {
return this.backPointerArray.getRecordSize();
}
public void ensureCapacity(Nd nd, long address, int capacity) {
long arrayAddress = address + this.offset;
this.backPointerArray.ensureCapacity(nd, arrayAddress, capacity);
}
@Override
public void destruct(Nd nd, long address) {
long arrayAddress = address + this.offset;
int size = size(nd, address);
boolean isOwner = this.forwardPointer.pointsToOwner;
for (int idx = 0; idx < size; idx++) {
long target = this.backPointerArray.get(nd, arrayAddress, idx);
this.forwardPointer.clearedByBackPointer(nd, target);
if (isOwner) {
nd.scheduleDeletion(target);
}
}
this.backPointerArray.destruct(nd, arrayAddress);
}
public int getCapacity(Nd nd, long address) {
return this.backPointerArray.getCapacity(nd, address + this.offset);
}
@Override
public boolean hasReferences(Nd nd, long address) {
// If this field owns the objects it points to, don't treat the incoming pointers as ref counts
if (this.forwardPointer.pointsToOwner) {
return false;
}
return !isEmpty(nd, address);
}
@Override
public void setOffset(int offset) {
this.offset = offset;
}
}