/******************************************************************************* * 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); } public long getAddressOf(Nd nd, long address, int index) { return this.backPointerArray.get(nd, address + this.offset, index); } /** * 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; } }