| /******************************************************************************* |
| * 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); |
| } |
| } |
| |
| |
| } |