blob: 261e85371b3cd0f407448557d983e1dc09313173 [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.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.internal.core.nd.IDestructable;
import org.eclipse.jdt.internal.core.nd.ITypeFactory;
import org.eclipse.jdt.internal.core.nd.Nd;
/**
* Defines a data structure that will appear in the database.
* <p>
* There are three mechanisms for deleting a struct from the database:
* <ul>
* <li>Explicit deletion. This happens synchronously via manual calls to Nd.delete. Structs intended for manual
* deletion have refCounted=false and an empty ownerFields.
* <li>Owner pointers. Such structs have one or more outbound pointers to an "owner" object. They are deleted
* asynchronously when the last owner pointer is deleted. The structs have refCounted=false and a nonempty
* ownerFields.
* <li>Refcounting. Such structs are deleted asynchronously when all elements are removed from all of their ManyToOne
* relationships which are not marked as incoming owner pointers. Owner relationships need to be excluded from
* refcounting since they would always create cycles. These structs have refCounted=true.
* </ul>
* <p>
* Structs deleted by refcounting and owner pointers are not intended to inherit from one another, but anything may
* inherit from a struct that uses manual deletion and anything may inherit from a struct that uses the same deletion
* mechanism.
*/
public final class StructDef<T> {
Class<T> clazz;
private StructDef<? super T> superClass;
private List<IField> fields = new ArrayList<>();
private boolean doneCalled;
private boolean offsetsComputed;
private List<StructDef<? extends T>> subClasses = new ArrayList<>();
private int size;
List<IDestructableField> destructableFields = new ArrayList<>();
boolean refCounted;
private List<IRefCountedField> refCountedFields = new ArrayList<>();
private List<IRefCountedField> ownerFields = new ArrayList<>();
boolean isAbstract;
private ITypeFactory<T> factory;
protected boolean hasUserDestructor;
private DeletionSemantics deletionSemantics;
public static enum DeletionSemantics {
EXPLICIT, OWNED, REFCOUNTED
}
private StructDef(Class<T> clazz) {
this(clazz, null);
}
private StructDef(Class<T> clazz, StructDef<? super T> superClass) {
this(clazz, superClass, Modifier.isAbstract(clazz.getModifiers()));
}
private StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract) {
this.clazz = clazz;
this.superClass = superClass;
if (this.superClass != null) {
this.superClass.subClasses.add(this);
}
this.isAbstract = isAbstract;
final String fullyQualifiedClassName = clazz.getName();
final Constructor<T> constructor;
if (!this.isAbstract) {
try {
constructor = clazz.getConstructor(new Class<?>[] { Nd.class, long.class });
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException("The node class " + fullyQualifiedClassName //$NON-NLS-1$
+ " does not have an appropriate constructor for it to be used with Nd"); //$NON-NLS-1$
}
} else {
constructor = null;
}
this.hasUserDestructor = IDestructable.class.isAssignableFrom(clazz);
this.factory = new ITypeFactory<T>() {
public T create(Nd dom, long address) {
if (StructDef.this.isAbstract) {
throw new UnsupportedOperationException(
"Attempting to instantiate abstract class" + fullyQualifiedClassName); //$NON-NLS-1$
}
try {
return constructor.newInstance(dom, address);
} catch (InvocationTargetException e) {
Throwable target = e.getCause();
if (target instanceof RuntimeException) {
throw (RuntimeException) target;
}
throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$
}
}
public int getRecordSize() {
return StructDef.this.size();
}
public boolean hasDestructor() {
return StructDef.this.hasUserDestructor || hasDestructableFields();
}
public Class<?> getElementClass() {
return StructDef.this.clazz;
}
public void destruct(Nd nd, long address) {
checkNotMutable();
if (StructDef.this.hasUserDestructor) {
IDestructable destructable = (IDestructable)create(nd, address);
destructable.destruct();
}
destructFields(nd, address);
}
public void destructFields(Nd dom, long address) {
StructDef.this.destructFields(dom, address);
}
@Override
public boolean isReadyForDeletion(Nd dom, long address) {
return StructDef.this.isReadyForDeletion(dom, address);
}
@Override
public DeletionSemantics getDeletionSemantics() {
return StructDef.this.getDeletionSemantics();
}
};
}
public Class<T> getStructClass() {
return this.clazz;
}
@Override
public String toString() {
return this.clazz.getName();
}
public static <T> StructDef<T> createAbstract(Class<T> clazz) {
return new StructDef<T>(clazz, null, true);
}
public static <T> StructDef<T> createAbstract(Class<T> clazz, StructDef<? super T> superClass) {
return new StructDef<T>(clazz, superClass, true);
}
public static <T> StructDef<T> create(Class<T> clazz) {
return new StructDef<T>(clazz);
}
public static <T> StructDef<T> create(Class<T> clazz, StructDef<? super T> superClass) {
return new StructDef<T>(clazz, superClass);
}
protected boolean isReadyForDeletion(Nd dom, long address) {
List<IRefCountedField> toIterate = Collections.EMPTY_LIST;
switch (this.deletionSemantics) {
case EXPLICIT: return false;
case OWNED: toIterate = this.ownerFields; break;
case REFCOUNTED: toIterate = this.refCountedFields; break;
}
for (IRefCountedField next : toIterate) {
if (next.hasReferences(dom, address)) {
return false;
}
}
final StructDef<? super T> localSuperClass = StructDef.this.superClass;
if (localSuperClass != null && localSuperClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
return localSuperClass.isReadyForDeletion(dom, address);
}
return true;
}
protected boolean hasDestructableFields() {
return (!StructDef.this.destructableFields.isEmpty() ||
(StructDef.this.superClass != null && StructDef.this.superClass.hasDestructableFields()));
}
public DeletionSemantics getDeletionSemantics() {
return this.deletionSemantics;
}
/**
* Call this once all the fields have been added to the struct definition and it is
* ready to use.
*/
public void done() {
if (this.doneCalled) {
throw new IllegalStateException("May not call done() more than once"); //$NON-NLS-1$
}
this.doneCalled = true;
if (this.superClass == null || this.superClass.areOffsetsComputed()) {
computeOffsets();
}
}
public void add(IField toAdd) {
checkMutable();
this.fields.add(toAdd);
}
public void addDestructableField(IDestructableField field) {
checkMutable();
this.destructableFields.add(field);
}
public StructDef<T> useStandardRefCounting() {
checkMutable();
this.refCounted = true;
return this;
}
public void addRefCountedField(IRefCountedField result) {
checkMutable();
this.refCountedFields.add(result);
}
public void addOwnerField(IRefCountedField result) {
checkMutable();
this.ownerFields.add(result);
}
public boolean areOffsetsComputed() {
return this.offsetsComputed;
}
public int size() {
checkNotMutable();
return this.size;
}
void checkNotMutable() {
if (!this.offsetsComputed) {
throw new IllegalStateException("Must call done() before using the struct"); //$NON-NLS-1$
}
}
private void checkMutable() {
if (this.doneCalled) {
throw new IllegalStateException("May not modify a StructDef after done() has been called"); //$NON-NLS-1$
}
}
/**
* Invoked on all StructDef after both {@link #done()} has been called on the struct and
* {@link #computeOffsets()} has been called on their base class.
*/
private void computeOffsets() {
int offset = this.superClass == null ? 0 : this.superClass.size();
for (IField next : this.fields) {
next.setOffset(offset);
offset += next.getRecordSize();
}
this.size = offset;
if (this.refCounted) {
this.deletionSemantics = DeletionSemantics.REFCOUNTED;
} else {
if (!this.ownerFields.isEmpty()) {
this.deletionSemantics = DeletionSemantics.OWNED;
} else if (this.superClass != null) {
this.deletionSemantics = this.superClass.deletionSemantics;
} else {
this.deletionSemantics = DeletionSemantics.EXPLICIT;
}
}
// Now verify that the deletion semantics of this struct are compatible with the deletion
// semantics of its superclass
if (this.superClass != null && this.deletionSemantics != this.superClass.deletionSemantics) {
if (this.superClass.deletionSemantics != DeletionSemantics.EXPLICIT) {
throw new IllegalStateException("A class (" + this.clazz.getName() + ") that uses " //$NON-NLS-1$//$NON-NLS-2$
+ this.deletionSemantics.toString() + " deletion semantics may not inherit from a class " //$NON-NLS-1$
+ "that uses " + this.superClass.deletionSemantics.toString() + " semantics"); //$NON-NLS-1$//$NON-NLS-2$
}
}
this.offsetsComputed = true;
for (StructDef<? extends T> next : this.subClasses) {
if (next.doneCalled) {
next.computeOffsets();
}
}
}
public FieldPointer addPointer() {
FieldPointer result = new FieldPointer();
add(result);
return result;
}
public FieldShort addShort() {
FieldShort result = new FieldShort();
add(result);
return result;
}
public FieldInt addInt() {
FieldInt result = new FieldInt();
add(result);
return result;
}
public FieldLong addLong() {
FieldLong result = new FieldLong();
add(result);
return result;
}
public FieldString addString() {
FieldString result = new FieldString();
add(result);
addDestructableField(result);
return result;
}
public FieldDouble addDouble() {
FieldDouble result = new FieldDouble();
add(result);
return result;
}
public FieldFloat addFloat() {
FieldFloat result = new FieldFloat();
add(result);
return result;
}
public FieldByte addByte() {
FieldByte result = new FieldByte();
add(result);
return result;
}
public FieldChar addChar() {
FieldChar result = new FieldChar();
add(result);
return result;
}
public <F> Field<F> add(ITypeFactory<F> factory1) {
Field<F> result = new Field<>(factory1);
add(result);
if (result.factory.hasDestructor()) {
this.destructableFields.add(result);
}
return result;
}
public ITypeFactory<T> getFactory() {
return this.factory;
}
void destructFields(Nd dom, long address) {
for (IDestructableField next : StructDef.this.destructableFields) {
next.destruct(dom, address);
}
if (this.superClass != null) {
this.superClass.destructFields(dom, address);
}
}
}