View Javadoc
1   package de.fhdw.wtf.walker.tasks;
2   
3   import java.util.Collections;
4   import java.util.Iterator;
5   import java.util.LinkedList;
6   import java.util.List;
7   
8   import de.fhdw.wtf.common.ast.Attribute;
9   import de.fhdw.wtf.common.ast.ConstructorOrOperation;
10  import de.fhdw.wtf.common.ast.Group;
11  import de.fhdw.wtf.common.ast.Model;
12  import de.fhdw.wtf.common.ast.type.ClassType;
13  import de.fhdw.wtf.common.ast.type.Type;
14  import de.fhdw.wtf.common.exception.walker.TaskException;
15  import de.fhdw.wtf.common.task.TaskExecutor;
16  import de.fhdw.wtf.walker.walker.SimpleWalkerTask;
17  
18  /**
19   * Analyzes the inheritance trees of the given model. Enriches the model with useful information regarding necessary
20   * constructor calls.
21   */
22  public final class AnalyzeInheritanceTreesTask extends SimpleWalkerTask {
23  	
24  	/**
25  	 * Used to identify the type Anything.
26  	 */
27  	private static final String ANYTHING = "Anything";
28  	
29  	/**
30  	 * Returns a new instance of {@link AnalyzeInheritanceTreesTask}.
31  	 * 
32  	 * @param model
33  	 *            The model to perform this task on.
34  	 * @param taskmanager
35  	 *            The executor which performs this task.
36  	 * @return a new instance of {@link AnalyzeInheritanceTreesTask}
37  	 */
38  	public static AnalyzeInheritanceTreesTask create(final Model model, final TaskExecutor taskmanager) {
39  		return new AnalyzeInheritanceTreesTask(model, taskmanager);
40  	}
41  	
42  	/**
43  	 * Instantiates a new instance of {@link AnalyzeInheritanceTreesTask}.
44  	 * 
45  	 * @param model
46  	 *            The model to perform this task on.
47  	 * @param taskmanager
48  	 *            The executor which performs this task.
49  	 */
50  	private AnalyzeInheritanceTreesTask(final Model model, final TaskExecutor taskmanager) {
51  		super(model, taskmanager);
52  	}
53  	
54  	/**
55  	 * Helper class which represents one path along the inheritance-hierarchy of a root class.
56  	 */
57  	private class InheritancePath {
58  		/**
59  		 * Types ordered along this InheritancePath.
60  		 */
61  		private final List<Type> pathElements;
62  		
63  		/**
64  		 * Constructs an empty InheritancePath.
65  		 */
66  		private InheritancePath() {
67  			this.pathElements = new LinkedList<>();
68  		}
69  		
70  		/**
71  		 * Constructs an InheritancePath with first as its root.
72  		 * 
73  		 * @param first
74  		 *            the first element of this path.
75  		 */
76  		InheritancePath(final Type first) {
77  			this();
78  			this.addElement(first);
79  		}
80  		
81  		/**
82  		 * Constructs an InheritancePath with firsts as its path elements.
83  		 * 
84  		 * @param firsts
85  		 *            the path elements for this.
86  		 */
87  		InheritancePath(final List<Type> firsts) {
88  			this();
89  			this.addElements(firsts);
90  		}
91  		
92  		/**
93  		 * Constructs an InheritancePath which is identical to firsts.
94  		 * 
95  		 * @param firsts
96  		 *            the path to copy.
97  		 */
98  		InheritancePath(final InheritancePath firsts) {
99  			this(firsts.pathElements);
100 		}
101 		
102 		/**
103 		 * Adds element to the end of this path.
104 		 * 
105 		 * @param element
106 		 *            the new end of this path.
107 		 */
108 		void addElement(final Type element) {
109 			this.pathElements.add(element);
110 		}
111 		
112 		/**
113 		 * Adds elements to this path so that these elements remain in the order provided in elements.
114 		 * 
115 		 * @param elements
116 		 *            the elements added to this.
117 		 */
118 		void addElements(final List<Type> elements) {
119 			this.pathElements.addAll(elements);
120 		}
121 		
122 		/**
123 		 * Returns true if element is also an element of this. False otherwise.
124 		 * 
125 		 * @param element
126 		 *            to check.
127 		 * @return true if element is part of this. False otherwise.
128 		 */
129 		boolean contains(final Type element) {
130 			return this.pathElements.contains(element);
131 		}
132 		
133 		/**
134 		 * Returns all elements that this and other have in common. The common elements will be provided the order
135 		 * provided by this.
136 		 * 
137 		 * @param other
138 		 *            the path to intersect with.
139 		 * @return common elements in the order of their existence in this path.
140 		 */
141 		InheritancePath intersect(final InheritancePath other) {
142 			final InheritancePath result = new InheritancePath();
143 			final Iterator<Type> myElements = this.pathElements.iterator();
144 			while (myElements.hasNext()) {
145 				final Type myElement = myElements.next();
146 				if (other.contains(myElement)) {
147 					result.addElement(myElement);
148 				}
149 			}
150 			return result;
151 		}
152 		
153 		/**
154 		 * Makes one step in the inheritance-tree. Alternative paths will be returned.
155 		 * 
156 		 * @return alternative paths if any exist. An empty llist otherwise.
157 		 */
158 		List<InheritancePath> step() {
159 			final List<InheritancePath> alternativePaths = new LinkedList<>();
160 			final Type lastElement = this.pathElements.get(this.pathElements.size() - 1);
161 			final Iterator<Type> followers = lastElement.getSuperTypes().iterator();
162 			if (followers.hasNext()) {
163 				final Type firstFollower = followers.next();
164 				while (followers.hasNext()) {
165 					final Type follower = followers.next();
166 					if (!follower.toString().equals(ANYTHING)) {
167 						final InheritancePath alternativePath = new InheritancePath(this);
168 						alternativePath.addElement(follower);
169 						alternativePaths.add(alternativePath);
170 					}
171 				}
172 				if (!firstFollower.toString().equals(ANYTHING)) {
173 					this.addElement(firstFollower);
174 				}
175 			}
176 			return alternativePaths;
177 		}
178 		
179 		/**
180 		 * Calculates all inheritance paths reachable from the end of this path and returns them.
181 		 * 
182 		 * @return a list with all paths reachable from the last element of this.
183 		 */
184 		List<InheritancePath> run() {
185 			final List<InheritancePath> result = new LinkedList<>();
186 			final List<InheritancePath> preResult = new LinkedList<>();
187 			int oldSize = this.pathElements.size();
188 			List<InheritancePath> alternativePaths = this.step();
189 			while (this.pathElements.size() != oldSize || !alternativePaths.isEmpty()) {
190 				preResult.addAll(alternativePaths);
191 				oldSize = this.pathElements.size();
192 				alternativePaths = this.step();
193 			}
194 			final Iterator<InheritancePath> paths = preResult.iterator();
195 			result.add(this);
196 			while (paths.hasNext()) {
197 				result.addAll(paths.next().run());
198 			}
199 			return result;
200 		}
201 		
202 		@Override
203 		public String toString() {
204 			return this.pathElements.toString();
205 		}
206 		
207 		@Override
208 		public boolean equals(final Object o) {
209 			if (o instanceof InheritancePath) {
210 				final InheritancePath oAsInheritancePath = (InheritancePath) o;
211 				return this.pathElements.equals(oAsInheritancePath.pathElements);
212 			}
213 			return false;
214 		}
215 		
216 		@Override
217 		public int hashCode() {
218 			return this.pathElements.hashCode();
219 		}
220 		
221 		/**
222 		 * 
223 		 * @return true if this path is empty.
224 		 */
225 		boolean isEmpty() {
226 			return this.pathElements.isEmpty();
227 		}
228 		
229 		/**
230 		 * removes top from this path if top is the top of this path.
231 		 * 
232 		 * @param top
233 		 *            the top to remove.
234 		 * 
235 		 */
236 		void removeTop(final Type top) {
237 			final int highestIndex = this.pathElements.size() - 1;
238 			if (highestIndex >= 0 && this.pathElements.get(highestIndex).equals(top)) {
239 				this.pathElements.remove(highestIndex);
240 			}
241 		}
242 		
243 	}
244 	
245 	@Override
246 	public void handleClass(final ClassType c) throws TaskException {
247 		final List<InheritancePath> allPaths = new LinkedList<>();
248 		final Iterator<Type> superTypes = c.getSuperTypes().iterator();
249 		while (superTypes.hasNext()) {
250 			final Type currentSuperType = superTypes.next();
251 			if (!currentSuperType.toString().equals(ANYTHING)) {
252 				allPaths.addAll(new InheritancePath(currentSuperType).run());
253 			}
254 		}
255 		final List<Type> definitiveResponsibilities = new LinkedList<>();
256 		final List<InheritancePath> allIntersections =
257 				this.gatherAllRelevantIntersections(allPaths, definitiveResponsibilities);
258 		this.mapCalls(c, allIntersections, definitiveResponsibilities);
259 	}
260 	
261 	/**
262 	 * First step to get all call responsibilities. Fills definitiveResponsibilities with the value found in the first
263 	 * intersection of none open ended paths where the intersection is of length 1.
264 	 * 
265 	 * @param allPaths
266 	 *            all inheritance paths.
267 	 * @param definitiveResponsibilities
268 	 *            a collection with all definitive call responsibilities.
269 	 * @return a list with resulting intersection.
270 	 */
271 	private List<InheritancePath> gatherAllRelevantIntersections(final List<InheritancePath> allPaths,
272 			final List<Type> definitiveResponsibilities) {
273 		if (allPaths.size() <= 1) {
274 			return allPaths;
275 		}
276 		final List<InheritancePath> allIntersections = new LinkedList<>();
277 		final List<InheritancePath> copy = new LinkedList<>();
278 		copy.addAll(allPaths);
279 		Collections.reverse(copy);
280 		boolean firstSingleOne = true;
281 		boolean firstRound = true;
282 		boolean firstAllLooseEnds = true;
283 		final Iterator<InheritancePath> paths = allPaths.iterator();
284 		while (paths.hasNext()) {
285 			final InheritancePath currentPath = paths.next();
286 			final Type pathEnd = currentPath.pathElements.get(currentPath.pathElements.size() - 1);
287 			final List<InheritancePath> currentIntersections = new LinkedList<>();
288 			boolean commonEnd = false;
289 			final Iterator<InheritancePath> copyPaths = copy.iterator();
290 			while (copyPaths.hasNext()) {
291 				final InheritancePath currentCopy = copyPaths.next();
292 				final InheritancePath intersection = currentPath.intersect(currentCopy);
293 				if (!intersection.isEmpty()) {
294 					currentIntersections.add(intersection);
295 				}
296 				if (!currentPath.equals(currentCopy)) {
297 					commonEnd =
298 							commonEnd
299 									|| pathEnd
300 											.equals(currentCopy.pathElements.get(currentCopy.pathElements.size() - 1));
301 					firstAllLooseEnds = firstAllLooseEnds && firstRound && !commonEnd;
302 				}
303 			}
304 			if (commonEnd) {
305 				if (firstSingleOne) {
306 					firstSingleOne =
307 							this.gatherSingleOnes(currentPath, currentIntersections, definitiveResponsibilities);
308 				}
309 				allIntersections.addAll(currentIntersections);
310 			} else {
311 				if (firstAllLooseEnds && currentPath.pathElements.size() == 1) {
312 					final Type type = currentPath.pathElements.get(0);
313 					if (!definitiveResponsibilities.contains(type)) {
314 						definitiveResponsibilities.add(type);
315 					}
316 				} else {
317 					allIntersections.addAll(currentIntersections);
318 				}
319 			}
320 			firstRound = firstAllLooseEnds;
321 		}
322 		
323 		return allIntersections;
324 	}
325 	
326 	/**
327 	 * Fills definitiveResponsibilities with the value found in the first intersection of none open ended paths where
328 	 * the intersection is of length 1.
329 	 * 
330 	 * @param currentPath
331 	 *            the current path.
332 	 * @param currentIntersections
333 	 *            the current intersections.
334 	 * @param definitiveResponsibilities
335 	 *            a list with definitive responsibilities.
336 	 * @return true if definitiveResponsibilities has been changed.
337 	 */
338 	private boolean gatherSingleOnes(final InheritancePath currentPath,
339 			final List<InheritancePath> currentIntersections,
340 			final List<Type> definitiveResponsibilities) {
341 		final Iterator<InheritancePath> intersections = currentIntersections.iterator();
342 		final Type first = currentPath.pathElements.get(0);
343 		boolean firstOne = false;
344 		while (intersections.hasNext()) {
345 			final InheritancePath current = intersections.next();
346 			if (current.pathElements.size() == 1) {
347 				final Type type = current.pathElements.get(0);
348 				firstOne = !type.equals(first) && !definitiveResponsibilities.contains(type);
349 				if (firstOne) {
350 					definitiveResponsibilities.add(type);
351 				}
352 			}
353 		}
354 		return firstOne;
355 	}
356 	
357 	/**
358 	 * Maps c to all types it has to instantiate during runtime. Uses the map "constructorCalldependencies" in Model for
359 	 * this purpose. Maps to an empty list if c has no responsibilities in the regard of instantiating super types.
360 	 * 
361 	 * @param c
362 	 *            the root of the inheritance tree to map types from.
363 	 * @param allIntersections
364 	 *            The inheritance tree with c as root in form of all paths that have the direct super types of c as root
365 	 *            after running gatherAllRelevantIntersections.
366 	 * @param definitiveResponsibilities
367 	 *            a list with definitive responsibilities.
368 	 */
369 	private void mapCalls(final ClassType c,
370 			final List<InheritancePath> allIntersections,
371 			final List<Type> definitiveResponsibilities) {
372 		List<InheritancePath> copyNewIntersections = new LinkedList<>();
373 		copyNewIntersections.addAll(allIntersections);
374 		while (!copyNewIntersections.isEmpty()) {
375 			final List<InheritancePath> newIntersections = new LinkedList<>();
376 			final Iterator<InheritancePath> intersections = copyNewIntersections.iterator();
377 			while (intersections.hasNext()) {
378 				final InheritancePath path = intersections.next();
379 				if (path.isEmpty()) {
380 					intersections.remove();
381 				} else if (path.pathElements.size() == 1) {
382 					final Type type = path.pathElements.get(0);
383 					if (!definitiveResponsibilities.contains(type)) {
384 						definitiveResponsibilities.add(type);
385 					}
386 					final List<InheritancePath> copyList = new LinkedList<>();
387 					copyList.addAll(copyNewIntersections);
388 					final Iterator<InheritancePath> copy = copyList.iterator();
389 					while (copy.hasNext()) {
390 						copy.next().removeTop(type);
391 					}
392 					// intersections.remove();
393 				} else {
394 					final Type top = path.pathElements.get(path.pathElements.size() - 1);
395 					final List<InheritancePath> copyList = new LinkedList<>();
396 					copyList.addAll(copyNewIntersections);
397 					final Iterator<InheritancePath> copies = copyList.iterator();
398 					while (copies.hasNext()) {
399 						final InheritancePath copy = copies.next();
400 						final InheritancePath intersection = path.intersect(copy);
401 						copy.removeTop(top);
402 						if (!intersection.isEmpty()) {
403 							if (intersection.pathElements.size() == 1) {
404 								final Type type = path.pathElements.get(0);
405 								if (!definitiveResponsibilities.contains(type)) {
406 									definitiveResponsibilities.add(type);
407 								}
408 							} else {
409 								intersection.removeTop(top);
410 								newIntersections.add(intersection);
411 							}
412 						}
413 					}
414 				}
415 			}
416 			copyNewIntersections = new LinkedList<>();
417 			copyNewIntersections.addAll(newIntersections);
418 		}
419 		this.getModel().putConstructorCallDependency(c, definitiveResponsibilities);
420 	}
421 	
422 	@Override
423 	public void handleGroup(final Group g) throws TaskException {
424 		// Nothing to do.
425 	}
426 	
427 	@Override
428 	public void handleAttribute(final Attribute a, final ClassType owner) throws TaskException {
429 		// Nothing to do.
430 	}
431 	
432 	@Override
433 	public void handleConstructorOrOperation(final ConstructorOrOperation coo, final ClassType owner)
434 			throws TaskException {
435 		// Nothing to do.
436 	}
437 	
438 	@Override
439 	public void finalizeTask() throws TaskException {
440 		// Nothing to do.
441 	}
442 	
443 	@Override
444 	public void beginTask() throws TaskException {
445 		// Nothing to do.
446 	}
447 	
448 }