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