/******************************************************************************* * Copyright (c) 2007, 2011 Wind River Systems, 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: * Anton Leherbauer (Wind River Systems) - initial API and implementation * Markus Schorn (Wind River Systems) * Marc-Andre Laperle - Extracted Util class from ToggleSourceHeaderAction *******************************************************************************/ package org.eclipse.cdt.internal.ui.editor; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.core.runtime.content.IContentTypeManager; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IBinding; import org.eclipse.cdt.core.dom.ast.IProblemBinding; import org.eclipse.cdt.core.index.IIndex; import org.eclipse.cdt.core.index.IIndexName; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable; import org.eclipse.cdt.internal.ui.refactoring.RefactoringASTCache; /** * A collection of static methods for finding the source file corresponding to a header * and vice versa. */ public final class SourceHeaderPartnerFinder { private static class Counter { public int fCount; } private SourceHeaderPartnerFinder() { } /** * Compute the partner file for a translation unit. * The partner file is the corresponding source or header file * based on heuristics. * * @since 4.0 */ private static class PartnerFileComputer implements ASTRunnable { PartnerFileVisitor fVisitor = null; public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) { if (ast != null && ast.getIndex() != null) { fVisitor = new PartnerFileVisitor(); ast.accept(fVisitor); } return Status.OK_STATUS; } public IPath getPartnerFileLocation() { if(fVisitor != null) { return fVisitor.getPartnerFileLocation(); } return null; } } private static class PartnerFileVisitor extends ASTVisitor { /** * When this many times the same partner file is hit, * we are confident enough to take it. */ private static final int CONFIDENCE_LIMIT = 15; /** * When this many times no match was found in the index, * we suspect that we won't get a good partner. */ private static final int SUSPECT_LIMIT = 15; private IIndex fIndex; private IPath fFilePath; private Map<IPath, Counter> fMap; /** The confidence level == number of hits */ private int fConfidence; /** Suspect level == number of no index matches */ private int fSuspect; /** The current favorite partner file */ private IPath fFavoriteLocation; { shouldVisitDeclarators= true; shouldVisitTranslationUnit = true; } public PartnerFileVisitor() { fMap= new HashMap<IPath, Counter>(); } @Override public int visit(IASTTranslationUnit tu) { fIndex= tu.getIndex(); if(fIndex == null) { return PROCESS_ABORT; } fFilePath= Path.fromOSString(tu.getFilePath()); return super.visit(tu); } public IPath getPartnerFileLocation() { return fFavoriteLocation; } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#visit(org.eclipse.cdt.core.dom.ast.IASTDeclarator) */ @Override public int visit(IASTDeclarator declarator) { if (declarator instanceof IASTFunctionDeclarator) { IASTName name= declarator.getName(); if (name != null && declarator.getNestedDeclarator() == null) { IBinding binding= name.resolveBinding(); if (binding != null && !(binding instanceof IProblemBinding)) { boolean isDefinition= name.isDefinition(); final IIndexName[] partnerNames; try { if (isDefinition) { partnerNames= fIndex.findNames(binding, IIndex.FIND_DECLARATIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); } else { partnerNames= fIndex.findNames(binding, IIndex.FIND_DEFINITIONS | IIndex.SEARCH_ACROSS_LANGUAGE_BOUNDARIES); } if (partnerNames.length == 0) { ++fSuspect; if (fSuspect == SUSPECT_LIMIT) { fFavoriteLocation= null; return PROCESS_ABORT; } } for (int i= 0; i < partnerNames.length; i++) { IIndexName partnerName= partnerNames[i]; IASTFileLocation partnerLocation= partnerName.getFileLocation(); if (partnerLocation != null) { IPath partnerFileLocation= Path.fromOSString(partnerLocation.getFileName()); if (!fFilePath.equals(partnerFileLocation)) { addPotentialPartnerFileLocation(partnerFileLocation); if (fConfidence == CONFIDENCE_LIMIT) { return PROCESS_ABORT; } } } } } catch (CoreException exc) { CUIPlugin.log(exc.getStatus()); } } } } return PROCESS_SKIP; } private void addPotentialPartnerFileLocation(IPath partnerFileLocation) { Counter counter= fMap.get(partnerFileLocation); if (counter == null) { counter= new Counter(); fMap.put(partnerFileLocation, counter); } ++counter.fCount; if (counter.fCount > fConfidence) { fConfidence= counter.fCount; fFavoriteLocation= partnerFileLocation; } } } /** * Finds a file in the given resource container for the given basename. * * @param container * @param basename * @return a matching {@link IFile} or <code>null</code>, if no matching file was found */ private static IFile findInContainer(IContainer container, final String basename) { final IFile[] result= { null }; IResourceProxyVisitor visitor= new IResourceProxyVisitor() { public boolean visit(IResourceProxy proxy) throws CoreException { if (result[0] != null) { return false; } if (!proxy.isAccessible()) { return false; } if (proxy.getType() == IResource.FILE && proxy.getName().equals(basename)) { result[0]= (IFile)proxy.requestResource(); return false; } return true; }}; try { container.accept(visitor, 0); } catch (CoreException exc) { // ignore } return result[0]; } private static IContentType[] getPartnerContentTypes(String contentTypeId) { IContentTypeManager mgr= Platform.getContentTypeManager(); if (contentTypeId.equals(CCorePlugin.CONTENT_TYPE_CHEADER)) { return new IContentType[] { mgr.getContentType(CCorePlugin.CONTENT_TYPE_CSOURCE), mgr.getContentType(CCorePlugin.CONTENT_TYPE_CXXSOURCE) }; } if (contentTypeId.equals(CCorePlugin.CONTENT_TYPE_CSOURCE)) { return new IContentType[] { mgr.getContentType(CCorePlugin.CONTENT_TYPE_CHEADER), mgr.getContentType(CCorePlugin.CONTENT_TYPE_CXXHEADER) }; } if (contentTypeId.equals(CCorePlugin.CONTENT_TYPE_CXXHEADER)) { return new IContentType[] { mgr.getContentType(CCorePlugin.CONTENT_TYPE_CXXSOURCE), mgr.getContentType(CCorePlugin.CONTENT_TYPE_CSOURCE) }; } if (contentTypeId.equals(CCorePlugin.CONTENT_TYPE_CXXSOURCE)) { return new IContentType[] { mgr.getContentType(CCorePlugin.CONTENT_TYPE_CXXHEADER), mgr.getContentType(CCorePlugin.CONTENT_TYPE_CHEADER) }; } return new IContentType[0]; } /** * Finds a partner translation unit based on filename/extension matching. * * @param a partner translation unit or <code>null</code> */ private static ITranslationUnit getPartnerFileFromFilename(ITranslationUnit tu) { IPath sourceFileLocation= tu.getLocation(); if (sourceFileLocation == null) { return null; } IPath partnerBasePath= sourceFileLocation.removeFileExtension(); IContentType[] contentTypes= getPartnerContentTypes(tu.getContentTypeId()); HashSet<String> extensionsTried= new HashSet<String>(); for (int j = 0; j < contentTypes.length; j++) { IContentType contentType= contentTypes[j]; String[] partnerExtensions; partnerExtensions= contentType.getFileSpecs(IContentType.FILE_EXTENSION_SPEC); for (int i= 0; i < partnerExtensions.length; i++) { String ext= partnerExtensions[i]; if (extensionsTried.add(ext)) { String partnerFileBasename= partnerBasePath.addFileExtension(ext).lastSegment(); IFile partnerFile= null; IResource resource = tu.getResource(); IContainer container = resource != null ? resource.getParent() : null; while (container != null && partnerFile == null && !(container instanceof IWorkspaceRoot)) { partnerFile= findInContainer(container, partnerFileBasename); container = container.getParent(); } if (partnerFile != null) { ITranslationUnit partnerUnit= (ITranslationUnit) CoreModel.getDefault().create(partnerFile); if (partnerUnit != null) { return partnerUnit; } } // External translation unit - try in same directory if (resource == null) { IPath partnerFileLoation= partnerBasePath.removeLastSegments(1).append(partnerFileBasename); ITranslationUnit partnerUnit= CoreModel.getDefault().createTranslationUnitFrom( tu.getCProject(), partnerFileLoation); if (partnerUnit != null) { return partnerUnit; } } } } } return null; } public static ITranslationUnit getPartnerTranslationUnit(ITranslationUnit tu) { ITranslationUnit partnerUnit= getPartnerFileFromFilename(tu); if (partnerUnit == null) { // Search partner file based on definition/declaration association IProgressMonitor monitor= new NullProgressMonitor(); PartnerFileComputer computer= new PartnerFileComputer(); ASTProvider.getASTProvider().runOnAST(tu, ASTProvider.WAIT_ACTIVE_ONLY, monitor, computer); partnerUnit = createTranslationUnit(computer.getPartnerFileLocation(), tu.getCProject()); } return partnerUnit; } public static ITranslationUnit getPartnerTranslationUnit(ITranslationUnit tu, RefactoringASTCache astCache) throws CoreException { ITranslationUnit partnerUnit= getPartnerFileFromFilename(tu); if (partnerUnit == null) { // Search partner file based on definition/declaration association IProgressMonitor monitor= new NullProgressMonitor(); IASTTranslationUnit ast = astCache.getAST(tu, monitor); PartnerFileVisitor visitor = new PartnerFileVisitor(); ast.accept(visitor); partnerUnit = createTranslationUnit(visitor.getPartnerFileLocation(), tu.getCProject()); } return partnerUnit; } private static ITranslationUnit createTranslationUnit(IPath partnerFileLoation, ICProject cProject) { ITranslationUnit partnerUnit = null; if (partnerFileLoation != null) { partnerUnit= (ITranslationUnit) CoreModel.getDefault().create(partnerFileLoation); if (partnerUnit == null) { partnerUnit= CoreModel.getDefault().createTranslationUnitFrom(cProject.getCProject(), partnerFileLoation); } } return partnerUnit; } }