/** * Copyright (c) 2010, 2011 Darmstadt University of Technology. * 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: * Marcel Bruch - initial API and implementation. * Stefan Henss - re-implementation in response to https://bugs.eclipse.org/bugs/show_bug.cgi?id=376796. */ package org.eclipse.recommenders.internal.chain.rcp; import static org.eclipse.recommenders.internal.chain.rcp.TypeBindingAnalyzer.findVisibleInstanceFieldsAndRelevantInstanceMethods; import static org.eclipse.recommenders.utils.Checks.cast; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding; import org.eclipse.jdt.internal.compiler.lookup.Binding; import org.eclipse.jdt.internal.compiler.lookup.InvocationSite; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; import com.google.common.base.Optional; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Table; @SuppressWarnings("restriction") public class ChainFinder { private final List<Optional<TypeBinding>> expectedTypes; private final Set<String> excludedTypes; private final InvocationSite invocationSite; private final Scope scope; private final List<Chain> chains = Lists.newLinkedList(); private final Map<Binding, ChainElement> edgeCache = Maps.newHashMap(); private final Map<TypeBinding, List<Binding>> fieldsAndMethodsCache = Maps.newHashMap(); private final Table<ChainElement, TypeBinding, Boolean> assignableCache = HashBasedTable.create(); ChainFinder(final List<Optional<TypeBinding>> expectedTypes, final Set<String> excludedTypes, final InvocationSite invocationSite, final Scope scope) { this.expectedTypes = expectedTypes; this.excludedTypes = excludedTypes; this.invocationSite = invocationSite; this.scope = scope; } void startChainSearch(final List<ChainElement> entrypoints, final int maxChains, final int minDepth, final int maxDepth) { for (final Optional<TypeBinding> expected : expectedTypes) { if (expected.isPresent() && !isFromExcludedType(expected.get())) { TypeBinding expectedType = expected.get(); int expectedDimension = 0; if (expectedType instanceof ArrayBinding) { expectedDimension = ((ArrayBinding) expectedType).dimensions(); expectedType = TypeBindingAnalyzer.removeArrayWrapper(expectedType); } searchChainsForExpectedType(expectedType, expectedDimension, entrypoints, maxChains, minDepth, maxDepth); } } } private void searchChainsForExpectedType(final TypeBinding expectedType, final int expectedDimensions, final List<ChainElement> entrypoints, final int maxChains, final int minDepth, final int maxDepth) { final LinkedList<LinkedList<ChainElement>> incompleteChains = prepareQueue(entrypoints); while (!incompleteChains.isEmpty()) { final LinkedList<ChainElement> chain = incompleteChains.poll(); final ChainElement edge = chain.getLast(); if (isValidEndOfChain(edge, expectedType, expectedDimensions)) { if (isValidChain(chain, minDepth)) { chains.add(new Chain(chain, expectedDimensions)); if (chains.size() == maxChains) { break; } } continue; } if (chain.size() < maxDepth && incompleteChains.size() <= 50000) { searchDeeper(chain, incompleteChains, edge.getReturnType()); } } } /** * Returns the potentially incomplete list of call chains that could be found before a time out happened. The * contents of this list are mutable and may change as the search makes progress. */ public List<Chain> getChains() { return chains; } private static LinkedList<LinkedList<ChainElement>> prepareQueue(final List<ChainElement> entrypoints) { final LinkedList<LinkedList<ChainElement>> incompleteChains = Lists.newLinkedList(); for (final ChainElement entrypoint : entrypoints) { final LinkedList<ChainElement> chain = Lists.newLinkedList(); chain.add(entrypoint); incompleteChains.add(chain); } return incompleteChains; } private boolean isFromExcludedType(final Binding binding) { final String key = StringUtils.substringBefore(String.valueOf(binding.computeUniqueKey()), ";"); //$NON-NLS-1$ return excludedTypes.contains(key); } private boolean isValidEndOfChain(final ChainElement edge, final TypeBinding expectedType, final int expectedDimension) { Boolean isAssignable = assignableCache.get(edge, expectedType); if (isAssignable == null) { isAssignable = TypeBindingAnalyzer.isAssignable(edge, expectedType, expectedDimension); assignableCache.put(edge, expectedType, isAssignable); } return isAssignable.booleanValue(); } private static boolean isValidChain(final LinkedList<ChainElement> chain, final int minDepth) { if (chain.size() < minDepth) { return false; } return true; } private void searchDeeper(final LinkedList<ChainElement> chain, final List<LinkedList<ChainElement>> incompleteChains, final TypeBinding currentlyVisitedType) { for (final Binding element : findAllFieldsAndMethods(currentlyVisitedType)) { final ChainElement newEdge = createEdge(element); if (!chain.contains(newEdge)) { incompleteChains.add(cloneChainAndAppendEdge(chain, newEdge)); } } } private List<Binding> findAllFieldsAndMethods(final TypeBinding chainElementType) { List<Binding> cached = fieldsAndMethodsCache.get(chainElementType); if (cached == null) { cached = Lists.newLinkedList(); for (final Binding binding : findVisibleInstanceFieldsAndRelevantInstanceMethods(chainElementType, invocationSite, scope)) { if (!isFromExcludedType(binding)) { cached.add(binding); } } fieldsAndMethodsCache.put(chainElementType, cached); } return cached; } private ChainElement createEdge(final Binding member) { ChainElement cached = edgeCache.get(member); if (cached == null) { cached = new ChainElement(member, false); edgeCache.put(member, cached); } return cached; } private static LinkedList<ChainElement> cloneChainAndAppendEdge(final LinkedList<ChainElement> chain, final ChainElement newEdge) { final LinkedList<ChainElement> chainCopy = cast(chain.clone()); chainCopy.add(newEdge); return chainCopy; } }