/******************************************************************************* * Copyright (c) 2017 Rogue Wave Software 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: * Rogue Wave Software Inc. - initial implementation *******************************************************************************/ package org.eclipse.php.phpunit.model; import java.lang.reflect.InvocationTargetException; import java.util.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.dltk.core.*; import org.eclipse.dltk.core.index2.search.ISearchEngine.MatchRule; import org.eclipse.dltk.core.search.IDLTKSearchScope; import org.eclipse.dltk.core.search.SearchEngine; import org.eclipse.dltk.internal.core.Model; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.php.core.compiler.PHPFlags; import org.eclipse.php.internal.core.model.PHPModelAccess; import org.eclipse.php.internal.core.project.PHPNature; import org.eclipse.php.phpunit.PHPUnitPlugin; import org.eclipse.php.phpunit.model.providers.PHP5ElementContentProvider; public class PHPUnitSearchEngine { public static int FIND_ELEMENT_CLASS = 1 << 2; // public static int FIND_ELEMENT_FUNCTION = 1 << 3; public static int FIND_ELEMENT_PHPUNIT_CASE = 1; public static int FIND_ELEMENT_PHPUNIT_SUITE = 1 << 1; // public static int FIND_ELEMENT_PHPUNIT_TEST = 1 << 6; // public static int FIND_OPTION_ALL_MODELS = 1 << 5; public static int FIND_OPTION_FIRST_ONLY = 1 << 4; public static final String CLASS_CASE = "PHPUnit_Framework_TestCase"; //$NON-NLS-1$ public static final String CLASS_SUITE = "PHPUnit_Framework_TestSuite"; //$NON-NLS-1$ public static final String PHPUNIT_BASE = "PHPUnit_Framework_Test"; //$NON-NLS-1$ public static final String FUNCTION_SUITE = "suite"; //$NON-NLS-1$ // public static final String INTERFACE_TEST = "PHPUnit_Framework_Test"; // //$NON-NLS-1$ // public static final String RETURN_UNKNOWN = "unknown"; //$NON-NLS-1$ private Map<IType, Set<IType>> typeHierarchyCache = new HashMap<IType, Set<IType>>(); private IScriptProject project; public PHPUnitSearchEngine(IScriptProject project) { this.project = project; } public List<IType> findTestCaseBaseClasses(IModelElement elementContainer, boolean listAbstract, IProgressMonitor monitor) { return findPHPUnitClassesBySupertype(elementContainer, getTestCase(), listAbstract, false, monitor); } public List<IType> findTestSuiteBaseClasses(IModelElement elementContainer, boolean listAbstract, IProgressMonitor monitor) { return findPHPUnitClassesBySupertype(elementContainer, getTestSuite(), listAbstract, false, monitor); } public List<IType> findTestCaseBaseClasses(IModelElement elementContainer, IType baseClass, boolean listAbstract, IProgressMonitor monitor) { return findPHPUnitClassesBySupertype(elementContainer, baseClass, listAbstract, false, monitor); } public List<IType> findAllTestCasesAndSuites(IModelElement element, boolean listAbstract, IProgressMonitor monitor) { List<IType> result = new LinkedList<>(); if (typeHierarchyCache.isEmpty()) { findPHPUnitClassesBySupertype(element, getTestCase(), listAbstract, false, monitor); findPHPUnitClassesBySupertype(element, getTestSuite(), listAbstract, false, monitor); } Collection<Set<IType>> values = typeHierarchyCache.values(); if (values != null && !values.isEmpty()) { for (Set<IType> set : values) { result.addAll(set); } } return result; } public List<IType> findPHPUnitClassesByTestCase(IModelElement elementContainer, boolean listAbstract, boolean isFirst, IProgressMonitor monitor) { return findPHPUnitClassesBySupertype(elementContainer, getTestCase(), listAbstract, isFirst, monitor); } public List<IType> findPHPUnitClassesByTestSuite(IModelElement elementContainer, boolean listAbstract, boolean isFirst, IProgressMonitor monitor) { return findPHPUnitClassesBySupertype(elementContainer, getTestSuite(), listAbstract, isFirst, monitor); } public List<IType> findPHPUnitClassesBySupertype(IModelElement elementContainer, IType superClass, boolean listAbstract, boolean isFirst, IProgressMonitor monitor) { if (superClass == null || elementContainer == null) { return Collections.emptyList(); } List<IType> subtypes = Collections.emptyList(); IPath parentPath = elementContainer.getPath(); List<IType> result = new LinkedList<>(); try { IScriptProject scriptProject = elementContainer.getScriptProject(); if (elementContainer != null && scriptProject.getProject().hasNature(PHPNature.ID)) { Set<IType> subClasses = null; try { subClasses = typeHierarchyCache.get(superClass); if (subClasses == null) { IType baseClass = getByName(PHPUNIT_BASE); ITypeHierarchy hierarchy = baseClass.newTypeHierarchy(scriptProject, monitor); if (monitor != null && monitor.isCanceled()) { return Collections.emptyList(); } IType testCase = getTestCase(); IType[] caseSubtypes = hierarchy.getAllSubtypes(testCase); IType testSuite = getTestSuite(); IType[] suiteSubtypes = hierarchy.getAllSubtypes(testSuite); Set<IType> caseSubtypesSet = new HashSet<>(); for (IType type : caseSubtypes) { caseSubtypesSet.add(type); } typeHierarchyCache.put(testCase, caseSubtypesSet); Set<IType> suiteSubtypesSet = new HashSet<>(); for (IType type : suiteSubtypes) { suiteSubtypesSet.add(type); } typeHierarchyCache.put(testSuite, suiteSubtypesSet); } IDLTKSearchScope scope = SearchEngine.createSearchScope(elementContainer); subClasses = typeHierarchyCache.get(superClass); first: for (IType type : subClasses) { int flags = type.getFlags(); if (listAbstract || !PHPFlags.isAbstract(flags)) { if (parentPath.isPrefixOf(type.getPath())) { result.add(type); if (isFirst) { subtypes = result; return subtypes; } continue first; } IModelElement element = type; second: do { if (scope.encloses(element)) { result.add(type); break second; } element = element.getParent(); } while (element != null && element instanceof IModelElement && !(element instanceof Model)); } } if (listAbstract) { result.add(superClass); } subtypes = result; } catch (ModelException e) { PHPUnitPlugin.log(e); } } } catch (CoreException e) { PHPUnitPlugin.log(e); } return subtypes; } public Set<IType> findTestCaseBaseClassesInWorkspace(final IProgressMonitor monitor) { final Set<IType> cache = new HashSet<>(); IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); for (IProject project : projects) { if (project.isOpen()) { IScriptProject scriptProject = DLTKCore.create(project); boolean hasNature = false; try { hasNature = project.hasNature(PHPNature.ID); } catch (CoreException e) { PHPUnitPlugin.log(e); } if (scriptProject != null && scriptProject.isOpen() && hasNature) { List<IType> foundTestCases = findTestCaseBaseClasses(scriptProject, getTestCase(), false, monitor); cache.addAll(foundTestCases); } } } return cache; } public List<IType> findTestCases(IModelElement elementContainer, IProgressMonitor monitor) { return findTestCaseBaseClasses(elementContainer, getTestCase(), true, monitor); } public List<IType> findTestSuites(IModelElement elementContainer, IProgressMonitor monitor) { return findTestCaseBaseClasses(elementContainer, getTestSuite(), true, monitor); } public boolean hasCasesOrSuites(IModelElement element, IProgressMonitor monitor) { return !findPHPUnitClassesBySupertype(element, getTestCase(), false, true, monitor).isEmpty() || !findPHPUnitClassesBySupertype(element, getTestSuite(), false, true, monitor).isEmpty(); } private void collectElements(final Object parent, final IProgressMonitor pm, final Set result, final int flags) { collectElementsRecursive(parent, pm, result, flags); } private boolean collectElementsRecursive(final Object parent, final IProgressMonitor pm, final Set result, final int flags) { if (parent instanceof Collection) return collectFromObject(((Collection) parent).toArray(), pm, result, flags); if (parent instanceof Object[]) return collectFromObject((Object[]) parent, pm, result, flags); if (parent instanceof IModelElement) return collectFromModelElement((IModelElement) parent, pm, result, flags); if (parent instanceof IProject) return collectFromProject((IProject) parent, pm, result, flags); if (parent instanceof IContainer) return collectFromContainer((IContainer) parent, pm, result, flags); if (parent instanceof IFile) return collectFromFile((IFile) parent, pm, result, flags); if (parent instanceof IAdaptable) { IResource resource = ((IAdaptable) parent).getAdapter(IResource.class); if (resource != null && resource instanceof IFile) { return collectFromFile((IFile) resource, pm, result, flags); } } return false; } private boolean collectFromModelElement(IModelElement parent, IProgressMonitor pm, Set result, int flags) { boolean isFirts = false; if ((flags & PHPUnitSearchEngine.FIND_OPTION_FIRST_ONLY) > 0) { isFirts = true; } if ((flags & FIND_ELEMENT_PHPUNIT_CASE) > 0) { List<IType> findPHPUnitClassesBySupertype1 = findPHPUnitClassesBySupertype(parent, getTestCase(), false, isFirts, new SubProgressMonitor(pm, IProgressMonitor.UNKNOWN)); if (findPHPUnitClassesBySupertype1 != null) { result.addAll(findPHPUnitClassesBySupertype1); } } if ((flags & FIND_ELEMENT_PHPUNIT_SUITE) > 0) { List<IType> findPHPUnitClassesBySupertype2 = findPHPUnitClassesBySupertype(parent, getTestSuite(), false, isFirts, new SubProgressMonitor(pm, IProgressMonitor.UNKNOWN)); if (findPHPUnitClassesBySupertype2 != null) { result.addAll(findPHPUnitClassesBySupertype2); } } return false; } private boolean collectFromContainer(final IContainer container, final IProgressMonitor pm, final Set result, final int flags) { final ITreeContentProvider provider = new PHP5ElementContentProvider(); return collectElementsRecursive(provider.getChildren(container), pm, result, flags); } private boolean collectFromFile(final IFile file, final IProgressMonitor pm, final Set result, final int flags) { return collectElementsRecursive(DLTKCore.create(file), pm, result, flags); } private boolean collectFromObject(final Object[] items, final IProgressMonitor pm, final Set result, final int flags) { if (items == null || items.length == 0) { return false; } final int nItems = items.length; final IProgressMonitor ipm = new SubProgressMonitor(pm, 1); boolean r = false; for (int i = 0; i < nItems; ++i) { r |= collectElementsRecursive(items[i], ipm, result, flags); if ((flags & FIND_OPTION_FIRST_ONLY) > 0 && r) { return true; } } return r; } private boolean collectFromProject(final IProject project, final IProgressMonitor pm, final Set result, final int flags) { return collectElementsRecursive(DLTKCore.create(project), pm, result, flags); } private void doFindElements(final Object[] parents, final Set<IType> result, final IProgressMonitor pm, final int flags) { if (parents != null && parents.length > 0) { for (Object parent : parents) { collectElements(parent, pm, result, flags); } } } public IType[] findElements(final Object[] parents, final int flags, IProgressMonitor pm) throws InvocationTargetException, InterruptedException { final Set<IType> result = new HashSet<>(); if (parents != null && parents.length > 0) { doFindElements(parents, result, pm, flags); } return result.toArray(new IType[result.size()]); } public boolean hasSuiteMethod(final IType classData) { if (classData == null) { return false; } IMethod[] functions; try { functions = classData.getMethods(); IMethod function; for (int i = 0; i < functions.length; ++i) { function = functions[i]; if (function.getElementName().compareToIgnoreCase(FUNCTION_SUITE) == 0) { return true; } } } catch (ModelException e) { PHPUnitPlugin.log(e); } return false; } public boolean hasTestCaseClass() { return getTestCase() != null; } public boolean isCase(IType classData) { return isSubOf(classData, getTestCase()); } public boolean isSuite(IType classData) { return isSubOf(classData, getTestSuite()); } public boolean isTest(final IType classData) { return isSuite(classData) || isCase(classData) || hasSuiteMethod(classData); } private IType getTestCase() { return getByName(CLASS_CASE); } private IType getTestSuite() { return getByName(CLASS_SUITE); } private IType getByName(String elementName) { if (elementName == null || elementName.length() == 0) { return null; } IType[] classes = PHPModelAccess.getDefault().findTypes(elementName, MatchRule.EXACT, 0, 0, SearchEngine.createSearchScope(project), null); if (classes != null && classes.length > 0) { return classes[0]; } return null; } private boolean isSubOf(final IType classData, IType superClass) { if (classData == null || superClass == null) { return false; } try { if (Flags.isAbstract(classData.getFlags())) { return false; } Set<IType> subclasses = typeHierarchyCache.get(superClass); if (subclasses == null) { findPHPUnitClassesBySupertype(project, superClass, true, false, null); subclasses = typeHierarchyCache.get(superClass); } return subclasses.contains(classData); } catch (ModelException e) { PHPUnitPlugin.log(e); } return false; } }