1 /******************************************************************************* 2 * Copyright (c) 2015, 2016 Google, Inc and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Stefan Xenos (Google) - Initial implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.core.nd.field; 15 16 import java.lang.reflect.Constructor; 17 import java.lang.reflect.InvocationTargetException; 18 import java.lang.reflect.Modifier; 19 import java.util.ArrayList; 20 import java.util.Collections; 21 import java.util.HashSet; 22 import java.util.List; 23 import java.util.Set; 24 25 import org.eclipse.jdt.internal.core.nd.IDestructable; 26 import org.eclipse.jdt.internal.core.nd.ITypeFactory; 27 import org.eclipse.jdt.internal.core.nd.Nd; 28 import org.eclipse.jdt.internal.core.nd.NdNode; 29 import org.eclipse.jdt.internal.core.nd.db.Database; 30 import org.eclipse.jdt.internal.core.nd.db.ModificationLog; 31 import org.eclipse.jdt.internal.core.nd.db.ModificationLog.Tag; 32 import org.eclipse.jdt.internal.core.nd.util.MathUtils; 33 34 /** 35 * Defines a data structure that will appear in the database. 36 * <p> 37 * There are three mechanisms for deleting a struct from the database: 38 * <ul> 39 * <li>Explicit deletion. This happens synchronously via manual calls to Nd.delete. Structs intended for manual 40 * deletion have refCounted=false and an empty ownerFields. 41 * <li>Owner pointers. Such structs have one or more outbound pointers to an "owner" object. They are deleted 42 * asynchronously when the last owner pointer is deleted. The structs have refCounted=false and a nonempty 43 * ownerFields. 44 * <li>Refcounting. Such structs are deleted asynchronously when all elements are removed from all of their ManyToOne 45 * relationships which are not marked as incoming owner pointers. Owner relationships need to be excluded from 46 * refcounting since they would always create cycles. These structs have refCounted=true. 47 * </ul> 48 * <p> 49 * Structs deleted by refcounting and owner pointers are not intended to inherit from one another, but anything may 50 * inherit from a struct that uses manual deletion and anything may inherit from a struct that uses the same deletion 51 * mechanism. 52 */ 53 public final class StructDef<T> { 54 Class<T> clazz; 55 private StructDef<? super T> superClass; 56 private Set<StructDef<?>> dependencies = new HashSet<>(); 57 private List<IField> fields = new ArrayList<>(); 58 private boolean doneCalled; 59 private boolean offsetsComputed; 60 private List<StructDef<? extends T>> dependents = new ArrayList<>(); 61 private int size; 62 List<IDestructableField> destructableFields = new ArrayList<>(); 63 boolean refCounted; 64 private List<IRefCountedField> refCountedFields = new ArrayList<>(); 65 private List<IRefCountedField> ownerFields = new ArrayList<>(); 66 boolean isAbstract; 67 private ITypeFactory<T> factory; 68 protected boolean hasUserDestructor; 69 private DeletionSemantics deletionSemantics; 70 final Tag destructTag; 71 private boolean isNdNode; 72 73 public static enum DeletionSemantics { 74 EXPLICIT, OWNED, REFCOUNTED 75 } 76 StructDef(Class<T> clazz)77 private StructDef(Class<T> clazz) { 78 this(clazz, null); 79 } 80 StructDef(Class<T> clazz, StructDef<? super T> superClass)81 private StructDef(Class<T> clazz, StructDef<? super T> superClass) { 82 this(clazz, superClass, Modifier.isAbstract(clazz.getModifiers())); 83 } 84 StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract)85 private StructDef(Class<T> clazz, StructDef<? super T> superClass, boolean isAbstract) { 86 this.destructTag = ModificationLog.createTag("Destructing struct " + clazz.getSimpleName()); //$NON-NLS-1$ 87 this.clazz = clazz; 88 this.isNdNode = NdNode.class.isAssignableFrom(clazz); 89 this.superClass = superClass; 90 if (this.superClass != null) { 91 addDependency(this.superClass); 92 } 93 this.isAbstract = isAbstract; 94 final String fullyQualifiedClassName = clazz.getName(); 95 96 final Constructor<T> constructor; 97 if (!this.isAbstract) { 98 try { 99 constructor = clazz.getConstructor(new Class<?>[] { Nd.class, long.class }); 100 } catch (NoSuchMethodException | SecurityException e) { 101 throw new IllegalArgumentException("The node class " + fullyQualifiedClassName //$NON-NLS-1$ 102 + " does not have an appropriate constructor for it to be used with Nd"); //$NON-NLS-1$ 103 } 104 } else { 105 constructor = null; 106 } 107 108 this.hasUserDestructor = IDestructable.class.isAssignableFrom(clazz); 109 110 this.factory = new ITypeFactory<T>() { 111 @Override 112 public T create(Nd dom, long address) { 113 if (StructDef.this.isAbstract) { 114 throw new UnsupportedOperationException( 115 "Attempting to instantiate abstract class" + fullyQualifiedClassName); //$NON-NLS-1$ 116 } 117 118 try { 119 return constructor.newInstance(dom, address); 120 } catch (InvocationTargetException e) { 121 Throwable target = e.getCause(); 122 123 if (target instanceof RuntimeException) { 124 throw (RuntimeException) target; 125 } 126 127 throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$ 128 } catch (InstantiationException | IllegalAccessException e) { 129 throw new RuntimeException("Error in AutoTypeFactory", e); //$NON-NLS-1$ 130 } 131 } 132 133 @Override 134 public int getRecordSize() { 135 return StructDef.this.size(); 136 } 137 138 @Override 139 public boolean hasDestructor() { 140 return StructDef.this.hasUserDestructor || hasDestructableFields(); 141 } 142 143 @Override 144 public Class<?> getElementClass() { 145 return StructDef.this.clazz; 146 } 147 148 @Override 149 public void destruct(Nd nd, long address) { 150 checkNotMutable(); 151 Database db = nd.getDB(); 152 db.getLog().start(StructDef.this.destructTag); 153 try { 154 if (StructDef.this.hasUserDestructor) { 155 IDestructable destructable = (IDestructable)create(nd, address); 156 destructable.destruct(); 157 } 158 destructFields(nd, address); 159 } finally { 160 db.getLog().end(StructDef.this.destructTag); 161 } 162 } 163 164 @Override 165 public void destructFields(Nd dom, long address) { 166 StructDef.this.destructFields(dom, address); 167 } 168 169 @Override 170 public boolean isReadyForDeletion(Nd dom, long address) { 171 return StructDef.this.isReadyForDeletion(dom, address); 172 } 173 174 @Override 175 public DeletionSemantics getDeletionSemantics() { 176 return StructDef.this.getDeletionSemantics(); 177 } 178 }; 179 } 180 addDependency(StructDef<?> newDependency)181 public void addDependency(StructDef<?> newDependency) { 182 if (newDependency.hasIndirectDependent(new HashSet<>(), this)) { 183 throw new IllegalArgumentException("Circular dependency detected. Struct " //$NON-NLS-1$ 184 + getStructName() + " and struct " + newDependency.getStructName() //$NON-NLS-1$ 185 + " both depend on one another"); //$NON-NLS-1$ 186 } 187 if (this.dependencies.add(newDependency)) { 188 this.superClass.dependents.add(this); 189 } 190 } 191 hasIndirectDependent(Set<StructDef<?>> visited, StructDef<?> structDef)192 private boolean hasIndirectDependent(Set<StructDef<?>> visited, StructDef<?> structDef) { 193 for (StructDef<?> next : this.dependents) { 194 if (!visited.add(next)) { 195 continue; 196 } 197 if (next.equals(structDef)) { 198 return true; 199 } 200 if (next.hasIndirectDependent(visited, structDef)) { 201 return true; 202 } 203 } 204 return false; 205 } 206 getStructClass()207 public Class<T> getStructClass() { 208 return this.clazz; 209 } 210 211 @Override toString()212 public String toString() { 213 return this.clazz.getName(); 214 } 215 createAbstract(Class<T> clazz)216 public static <T> StructDef<T> createAbstract(Class<T> clazz) { 217 return new StructDef<T>(clazz, null, true); 218 } 219 createAbstract(Class<T> clazz, StructDef<? super T> superClass)220 public static <T> StructDef<T> createAbstract(Class<T> clazz, StructDef<? super T> superClass) { 221 return new StructDef<T>(clazz, superClass, true); 222 } 223 create(Class<T> clazz)224 public static <T> StructDef<T> create(Class<T> clazz) { 225 return new StructDef<T>(clazz); 226 } 227 create(Class<T> clazz, StructDef<? super T> superClass)228 public static <T> StructDef<T> create(Class<T> clazz, StructDef<? super T> superClass) { 229 return new StructDef<T>(clazz, superClass); 230 } 231 isReadyForDeletion(Nd dom, long address)232 protected boolean isReadyForDeletion(Nd dom, long address) { 233 List<IRefCountedField> toIterate = Collections.EMPTY_LIST; 234 switch (this.deletionSemantics) { 235 case EXPLICIT: return false; 236 case OWNED: toIterate = this.ownerFields; break; 237 case REFCOUNTED: toIterate = this.refCountedFields; break; 238 } 239 240 for (IRefCountedField next : toIterate) { 241 if (next.hasReferences(dom, address)) { 242 return false; 243 } 244 } 245 246 final StructDef<? super T> localSuperClass = StructDef.this.superClass; 247 if (localSuperClass != null && localSuperClass.deletionSemantics != DeletionSemantics.EXPLICIT) { 248 return localSuperClass.isReadyForDeletion(dom, address); 249 } 250 return true; 251 } 252 hasDestructableFields()253 protected boolean hasDestructableFields() { 254 return (!StructDef.this.destructableFields.isEmpty() || 255 (StructDef.this.superClass != null && StructDef.this.superClass.hasDestructableFields())); 256 } 257 getDeletionSemantics()258 public DeletionSemantics getDeletionSemantics() { 259 return this.deletionSemantics; 260 } 261 areAllDependenciesResolved()262 private boolean areAllDependenciesResolved() { 263 for (StructDef<?> next : this.dependencies) { 264 if (!next.areOffsetsComputed()) { 265 return false; 266 } 267 } 268 return true; 269 } 270 271 /** 272 * Call this once all the fields have been added to the struct definition and it is 273 * ready to use. 274 */ done()275 public void done() { 276 if (this.doneCalled) { 277 throw new IllegalStateException("May not call done() more than once"); //$NON-NLS-1$ 278 } 279 this.doneCalled = true; 280 281 if (areAllDependenciesResolved()) { 282 computeOffsets(); 283 } 284 } 285 add(IField toAdd)286 public void add(IField toAdd) { 287 checkMutable(); 288 289 this.fields.add(toAdd); 290 } 291 addDestructableField(IDestructableField field)292 public void addDestructableField(IDestructableField field) { 293 checkMutable(); 294 295 this.destructableFields.add(field); 296 } 297 useStandardRefCounting()298 public StructDef<T> useStandardRefCounting() { 299 checkMutable(); 300 301 this.refCounted = true; 302 return this; 303 } 304 addRefCountedField(IRefCountedField result)305 public void addRefCountedField(IRefCountedField result) { 306 checkMutable(); 307 308 this.refCountedFields.add(result); 309 } 310 addOwnerField(IRefCountedField result)311 public void addOwnerField(IRefCountedField result) { 312 checkMutable(); 313 314 this.ownerFields.add(result); 315 } 316 areOffsetsComputed()317 public boolean areOffsetsComputed() { 318 return this.offsetsComputed; 319 } 320 size()321 public int size() { 322 checkNotMutable(); 323 return this.size; 324 } 325 checkNotMutable()326 void checkNotMutable() { 327 if (!this.offsetsComputed) { 328 throw new IllegalStateException("Must call done() before using the struct"); //$NON-NLS-1$ 329 } 330 } 331 checkMutable()332 private void checkMutable() { 333 if (this.doneCalled) { 334 throw new IllegalStateException("May not modify a StructDef after done() has been called"); //$NON-NLS-1$ 335 } 336 } 337 338 /** 339 * Invoked on all StructDef after both {@link #done()} has been called on the struct and 340 * {@link #computeOffsets()} has been called on every dependency of this struct. 341 */ computeOffsets()342 private void computeOffsets() { 343 int offset = this.superClass == null ? 0 : this.superClass.size(); 344 345 for (IField next : this.fields) { 346 offset = MathUtils.roundUpToNearestMultiple(offset, next.getAlignment()); 347 next.setOffset(offset); 348 offset += next.getRecordSize(); 349 } 350 351 this.size = offset; 352 if (this.refCounted) { 353 this.deletionSemantics = DeletionSemantics.REFCOUNTED; 354 } else { 355 if (!this.ownerFields.isEmpty()) { 356 this.deletionSemantics = DeletionSemantics.OWNED; 357 } else if (this.superClass != null) { 358 this.deletionSemantics = this.superClass.deletionSemantics; 359 } else { 360 this.deletionSemantics = DeletionSemantics.EXPLICIT; 361 } 362 } 363 // Now verify that the deletion semantics of this struct are compatible with the deletion 364 // semantics of its superclass 365 if (this.superClass != null && this.deletionSemantics != this.superClass.deletionSemantics) { 366 if (this.superClass.deletionSemantics != DeletionSemantics.EXPLICIT) { 367 throw new IllegalStateException("A class (" + this.clazz.getName() + ") that uses " //$NON-NLS-1$//$NON-NLS-2$ 368 + this.deletionSemantics.toString() + " deletion semantics may not inherit from a class " //$NON-NLS-1$ 369 + "that uses " + this.superClass.deletionSemantics.toString() + " semantics"); //$NON-NLS-1$//$NON-NLS-2$ 370 } 371 } 372 373 this.offsetsComputed = true; 374 375 for (StructDef<? extends T> next : this.dependents) { 376 if (next.doneCalled) { 377 next.computeOffsets(); 378 } 379 } 380 } 381 addPointer()382 public FieldPointer addPointer() { 383 FieldPointer result = new FieldPointer(getStructName(), this.fields.size()); 384 add(result); 385 return result; 386 } 387 addShort()388 public FieldShort addShort() { 389 FieldShort result = new FieldShort(getStructName(), this.fields.size()); 390 add(result); 391 return result; 392 } 393 addInt()394 public FieldInt addInt() { 395 FieldInt result = new FieldInt(getStructName(), this.fields.size()); 396 add(result); 397 return result; 398 } 399 addLong()400 public FieldLong addLong() { 401 FieldLong result = new FieldLong(getStructName(), this.fields.size()); 402 add(result); 403 return result; 404 } 405 addString()406 public FieldString addString() { 407 FieldString result = new FieldString(getStructName(), this.fields.size()); 408 add(result); 409 addDestructableField(result); 410 return result; 411 } 412 addDouble()413 public FieldDouble addDouble() { 414 FieldDouble result = new FieldDouble(getStructName(), this.fields.size()); 415 add(result); 416 return result; 417 } 418 addFloat()419 public FieldFloat addFloat() { 420 FieldFloat result = new FieldFloat(getStructName(), this.fields.size()); 421 add(result); 422 return result; 423 } 424 getStructName()425 public String getStructName() { 426 return this.clazz.getSimpleName(); 427 } 428 addByte()429 public FieldByte addByte() { 430 FieldByte result = new FieldByte(getStructName(), this.fields.size()); 431 add(result); 432 return result; 433 } 434 addChar()435 public FieldChar addChar() { 436 FieldChar result = new FieldChar(getStructName(), this.fields.size()); 437 add(result); 438 return result; 439 } 440 add(ITypeFactory<F> factory1)441 public <F> Field<F> add(ITypeFactory<F> factory1) { 442 Field<F> result = new Field<>(factory1, getStructName(), this.fields.size()); 443 add(result); 444 if (result.factory.hasDestructor()) { 445 this.destructableFields.add(result); 446 } 447 return result; 448 } 449 getFactory()450 public ITypeFactory<T> getFactory() { 451 return this.factory; 452 } 453 destructFields(Nd dom, long address)454 void destructFields(Nd dom, long address) { 455 for (IDestructableField next : StructDef.this.destructableFields) { 456 next.destruct(dom, address); 457 } 458 459 if (this.superClass != null) { 460 this.superClass.destructFields(dom, address); 461 } 462 } 463 isNdNode()464 public boolean isNdNode() { 465 return this.isNdNode; 466 } 467 getNumFields()468 public int getNumFields() { 469 return this.fields.size(); 470 } 471 } 472