/******************************************************************************* * Copyright (c) 2007, 2008 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 *******************************************************************************/ package org.eclipse.cdt.internal.ui.compare; import java.util.Stack; import org.eclipse.compare.structuremergeviewer.DocumentRangeNode; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Position; import org.eclipse.cdt.core.dom.ast.ASTVisitor; import org.eclipse.cdt.core.dom.ast.IASTASMDeclaration; import org.eclipse.cdt.core.dom.ast.IASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclSpecifier; import org.eclipse.cdt.core.dom.ast.IASTDeclaration; import org.eclipse.cdt.core.dom.ast.IASTDeclarator; import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier; import org.eclipse.cdt.core.dom.ast.IASTFileLocation; import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator; import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition; import org.eclipse.cdt.core.dom.ast.IASTName; import org.eclipse.cdt.core.dom.ast.IASTNode; import org.eclipse.cdt.core.dom.ast.IASTNodeLocation; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorIncludeStatement; import org.eclipse.cdt.core.dom.ast.IASTPreprocessorMacroDefinition; import org.eclipse.cdt.core.dom.ast.IASTProblemDeclaration; import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTCompositeTypeSpecifier; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTExplicitTemplateInstantiation; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLinkageSpecification; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceAlias; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateSpecialization; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDeclaration; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTUsingDirective; import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTVisibilityLabel; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.internal.core.model.ASTStringUtil; import org.eclipse.cdt.internal.core.model.CoreModelMessages; import org.eclipse.cdt.internal.ui.CUIMessages; /** * AST visitor to create compare structure. * * @since 5.0 */ class CStructureCreatorVisitor extends ASTVisitor { private static final String TRANSLATION_UNIT_NAME = CUIMessages.CStructureCreatorVisitor_translationUnitName; private static final String ANONYMOUS_NAME= CoreModelMessages.getString("CElementLabels.anonymous"); //$NON-NLS-1$ private Stack<DocumentRangeNode> fStack = new Stack<DocumentRangeNode>(); private IDocument fDocument; private String fTranslationUnitFileName; /** * Create visitor adding nodes to given root. * * @param root */ public CStructureCreatorVisitor(DocumentRangeNode root) { fDocument = root.getDocument(); fStack.clear(); fStack.push(root); // visitor options shouldVisitTranslationUnit= true; shouldVisitDeclarations= true; shouldVisitEnumerators=true; shouldVisitNamespaces=true; } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#visit(org.eclipse.cdt.core.dom.ast.IASTTranslationUnit) */ @Override public int visit(IASTTranslationUnit tu) { fTranslationUnitFileName= tu.getFilePath(); push(ICElement.C_UNIT, TRANSLATION_UNIT_NAME, 0); // TODO fix ordering of includes and macros // includes final IASTPreprocessorIncludeStatement[] includeDirectives= tu.getIncludeDirectives(); for (int i= 0; i < includeDirectives.length; i++) { IASTPreprocessorIncludeStatement includeDirective= includeDirectives[i]; if (isLocalToFile(includeDirective)) { push(ICElement.C_INCLUDE, new String(includeDirective.getName().toCharArray()), getStartOffset(includeDirective)); pop(getEndOffset(includeDirective)); } } // macros final IASTPreprocessorMacroDefinition[] macroDefinitions= tu.getMacroDefinitions(); for (int i= 0; i < macroDefinitions.length; i++) { IASTPreprocessorMacroDefinition macroDefinition= macroDefinitions[i]; if (isLocalToFile(macroDefinition)) { push(ICElement.C_MACRO, new String(macroDefinition.getName().toCharArray()), getStartOffset(macroDefinition)); pop(getEndOffset(macroDefinition)); } } return super.visit(tu); } /** * Test whether given AST node is local to the source file * and not part of an inclusion. * * @param node * @return <code>true</code> if the node is part of the source file. */ private boolean isLocalToFile(IASTNode node) { return fTranslationUnitFileName.equals(node.getContainingFilename()); } /** * Compute the start offset of given AST node. * * @param node * @return */ private int getStartOffset(IASTNode node) { IASTFileLocation fileLocation= getMinFileLocation(node.getNodeLocations()); if (fileLocation != null) { return fileLocation.getNodeOffset(); } DocumentRangeNode container= getCurrentContainer(); Object[] children= container.getChildren(); if (children != null && children.length > 0) { Position prevRange= ((DocumentRangeNode)children[children.length - 1]).getRange(); return prevRange.getOffset() + prevRange.getLength(); } // fallback: use container range start Position containerRange= container.getRange(); return containerRange.getOffset(); } /** * Compute the end offset of give AST node. * * @param node * @return */ private int getEndOffset(IASTNode node) { IASTFileLocation fileLocation= getMaxFileLocation(node.getNodeLocations()); if (fileLocation != null) { return fileLocation.getNodeOffset() + fileLocation.getNodeLength(); } // fallback: use container range end DocumentRangeNode container= getCurrentContainer(); Position containerRange= container.getRange(); return containerRange.getOffset() + containerRange.getLength(); } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#leave(org.eclipse.cdt.core.dom.ast.IASTTranslationUnit) */ @Override public int leave(IASTTranslationUnit tu) { super.leave(tu); assert getCurrentContainer().getTypeCode() == ICElement.C_UNIT; pop(fDocument.getLength()); return PROCESS_SKIP; } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#visit(org.eclipse.cdt.core.dom.ast.IASTDeclaration) */ @Override public int visit(IASTDeclaration node) { boolean isTemplateDecl= isTemplateDecl(node); final int startOffset= isTemplateDecl ? getStartOffset(node.getParent()) : getStartOffset(node); final int endOffset= getEndOffset(node); if (node instanceof IASTFunctionDefinition) { IASTFunctionDefinition functionDef= (IASTFunctionDefinition)node; final int nodeType; if (inClassBody()) { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_METHOD : ICElement.C_METHOD; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_FUNCTION : ICElement.C_FUNCTION; } push(nodeType, getDeclaratorName(functionDef.getDeclarator()), startOffset); pop(endOffset); return PROCESS_SKIP; } else if (node instanceof IASTSimpleDeclaration) { IASTSimpleDeclaration simpleDecl= (IASTSimpleDeclaration)node; IASTDeclSpecifier declSpec= simpleDecl.getDeclSpecifier(); if (declSpec instanceof ICPPASTCompositeTypeSpecifier) { ICPPASTCompositeTypeSpecifier compositeTypeSpec= (ICPPASTCompositeTypeSpecifier)declSpec; final String nodeName= getTypeName(compositeTypeSpec); final int nodeType; switch (compositeTypeSpec.getKey()) { case IASTCompositeTypeSpecifier.k_struct: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_STRUCT : ICElement.C_STRUCT; break; case IASTCompositeTypeSpecifier.k_union: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_UNION : ICElement.C_UNION; break; case ICPPASTCompositeTypeSpecifier.k_class: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_CLASS : ICElement.C_CLASS; break; default: assert false : "Unexpected composite type specifier"; //$NON-NLS-1$ return PROCESS_CONTINUE; } push(nodeType, nodeName, startOffset); } else if (declSpec instanceof IASTEnumerationSpecifier) { IASTEnumerationSpecifier enumSpecifier= (IASTEnumerationSpecifier)declSpec; push(ICElement.C_ENUMERATION, getEnumerationName(enumSpecifier), startOffset); } else { IASTDeclarator[] declarators= simpleDecl.getDeclarators(); for (int i = 0; i < declarators.length; i++) { IASTDeclarator declarator= declarators[i]; int declStartOffset= declarators.length == 1 ? startOffset : getStartOffset(declarator); int declEndOffset= declarators.length == 1 ? endOffset : getEndOffset(declarator); final String nodeName = getDeclaratorName(declarator); if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_typedef) { push(ICElement.C_TYPEDEF, nodeName, declStartOffset); pop(declEndOffset); } else if (declarator instanceof IASTFunctionDeclarator && !hasNestedPointerOperators(declarator)) { final int nodeType; if (inClassBody()) { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_METHOD_DECLARATION : ICElement.C_METHOD_DECLARATION; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_FUNCTION_DECLARATION : ICElement.C_FUNCTION_DECLARATION; } push(nodeType, nodeName, declStartOffset); pop(declEndOffset); } else if (declarator != null) { final int nodeType; if (inClassBody()) { nodeType= ICElement.C_FIELD; } else { if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_extern) { nodeType= ICElement.C_VARIABLE_DECLARATION; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_VARIABLE : ICElement.C_VARIABLE; } } push(nodeType, nodeName, declStartOffset); pop(declEndOffset); } } } } else if (node instanceof IASTASMDeclaration) { // ignored } else if (node instanceof ICPPASTVisibilityLabel) { // ignored } else if (node instanceof ICPPASTNamespaceDefinition) { // handled below } else if (node instanceof ICPPASTNamespaceAlias) { // ignored } else if (node instanceof ICPPASTUsingDeclaration) { ICPPASTUsingDeclaration usingDecl= (ICPPASTUsingDeclaration)node; push(ICElement.C_USING, ASTStringUtil.getQualifiedName(usingDecl.getName()), startOffset); pop(endOffset); } else if (node instanceof ICPPASTUsingDirective) { ICPPASTUsingDirective usingDirective= (ICPPASTUsingDirective)node; push(ICElement.C_USING, ASTStringUtil.getQualifiedName(usingDirective.getQualifiedName()), startOffset); pop(endOffset); } else if (node instanceof ICPPASTLinkageSpecification) { // declarations get flattened } else if (node instanceof ICPPASTTemplateDeclaration) { // handled at child declaration level } else if (node instanceof ICPPASTTemplateSpecialization) { // ignored } else if (node instanceof ICPPASTExplicitTemplateInstantiation) { // ignored } else if (node instanceof IASTProblemDeclaration) { // ignored } return super.visit(node); } /* * @see org.eclipse.cdt.core.dom.ast.cpp.CPPASTVisitor#visit(org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition) */ @Override public int visit(ICPPASTNamespaceDefinition namespace) { push(ICElement.C_NAMESPACE, ASTStringUtil.getQualifiedName(namespace.getName()), getStartOffset(namespace)); return super.visit(namespace); } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#visit(org.eclipse.cdt.core.dom.ast.IASTEnumerationSpecifier.IASTEnumerator) */ @Override public int visit(IASTEnumerator enumerator) { push(ICElement.C_ENUMERATOR, ASTStringUtil.getQualifiedName(enumerator.getName()), getStartOffset(enumerator)); pop(getEndOffset(enumerator)); return super.visit(enumerator); } /* * @see org.eclipse.cdt.core.dom.ast.ASTVisitor#leave(org.eclipse.cdt.core.dom.ast.IASTDeclaration) */ @Override public int leave(IASTDeclaration node) { super.leave(node); boolean isTemplateDecl= isTemplateDecl(node); final int endOffset= isTemplateDecl ? getEndOffset(node.getParent()) : getEndOffset(node); if (node instanceof IASTFunctionDefinition) { final int nodeType; if (inClassBody()) { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_METHOD : ICElement.C_METHOD; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_FUNCTION : ICElement.C_FUNCTION; } assert getCurrentContainer().getTypeCode() == nodeType; pop(endOffset); } else if (node instanceof IASTSimpleDeclaration) { IASTSimpleDeclaration simpleDecl= (IASTSimpleDeclaration)node; IASTDeclSpecifier declSpec= simpleDecl.getDeclSpecifier(); boolean isCompositeType= false; if (declSpec instanceof ICPPASTCompositeTypeSpecifier) { isCompositeType= true; ICPPASTCompositeTypeSpecifier compositeTypeSpec= (ICPPASTCompositeTypeSpecifier)declSpec; final int nodeType; switch (compositeTypeSpec.getKey()) { case IASTCompositeTypeSpecifier.k_struct: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_STRUCT : ICElement.C_STRUCT; break; case IASTCompositeTypeSpecifier.k_union: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_UNION : ICElement.C_UNION; break; case ICPPASTCompositeTypeSpecifier.k_class: nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_CLASS : ICElement.C_CLASS; break; default: assert false : "Unexpected composite type specifier"; //$NON-NLS-1$ return PROCESS_CONTINUE; } assert getCurrentContainer().getTypeCode() == nodeType; pop(isTemplateDecl ? endOffset : getEndOffset(declSpec)); } else if (declSpec instanceof IASTEnumerationSpecifier) { isCompositeType= true; assert getCurrentContainer().getTypeCode() == ICElement.C_ENUMERATION; pop(getEndOffset(declSpec)); } if (isCompositeType) { IASTDeclarator[] declarators= simpleDecl.getDeclarators(); for (int i= 0; i < declarators.length; i++) { IASTDeclarator declarator= declarators[i]; final String nodeName= getDeclaratorName(declarator); final int declStartOffset= getStartOffset(declarator); final int declEndOffset= getEndOffset(declarator); if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_typedef) { push(ICElement.C_TYPEDEF, nodeName, declStartOffset); pop(declEndOffset); } else if (declarator instanceof IASTFunctionDeclarator && !hasNestedPointerOperators(declarator)) { final int nodeType; if (inClassBody()) { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_METHOD_DECLARATION : ICElement.C_METHOD_DECLARATION; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_FUNCTION_DECLARATION : ICElement.C_FUNCTION_DECLARATION; } push(nodeType, nodeName, declStartOffset); pop(declEndOffset); } else if (declarator != null) { final int nodeType; if (inClassBody()) { nodeType= ICElement.C_FIELD; } else { if (declSpec.getStorageClass() == IASTDeclSpecifier.sc_extern) { nodeType= ICElement.C_VARIABLE_DECLARATION; } else { nodeType= isTemplateDecl ? ICElement.C_TEMPLATE_VARIABLE : ICElement.C_VARIABLE; } } push(nodeType, nodeName, declStartOffset); pop(declEndOffset); } } } } else if (node instanceof IASTASMDeclaration) { // ignored } else if (node instanceof ICPPASTVisibilityLabel) { // ignored } else if (node instanceof ICPPASTNamespaceDefinition) { // handled below } else if (node instanceof ICPPASTNamespaceAlias) { // ignored } else if (node instanceof ICPPASTUsingDeclaration) { // handled in visit } else if (node instanceof ICPPASTUsingDirective) { // handled in visit } else if (node instanceof ICPPASTLinkageSpecification) { // declarations get flattened } else if (node instanceof ICPPASTTemplateDeclaration) { // handled at child declaration level } else if (node instanceof ICPPASTTemplateSpecialization) { // ignored } else if (node instanceof ICPPASTExplicitTemplateInstantiation) { // ignored } else if (node instanceof IASTProblemDeclaration) { // ignored } return PROCESS_CONTINUE; } /* * @see org.eclipse.cdt.core.dom.ast.cpp.CPPASTVisitor#leave(org.eclipse.cdt.core.dom.ast.cpp.ICPPASTNamespaceDefinition) */ @Override public int leave(ICPPASTNamespaceDefinition namespace) { assert getCurrentContainer().getTypeCode() == ICElement.C_NAMESPACE; pop(getEndOffset(namespace)); return super.leave(namespace); } private DocumentRangeNode getCurrentContainer() { return fStack.peek(); } /** * Adds a new node with the given type and name to the current container. */ private void push(int type, String name, int declarationStart) { if (name.length() == 0) { name= ANONYMOUS_NAME; } fStack.push(new CNode(getCurrentContainer(), type, name, declarationStart, 0)); } /** * Closes the current node by setting its end position * and pops it off the stack. */ private void pop(int declarationEnd) { DocumentRangeNode current= getCurrentContainer(); current.setAppendPosition(declarationEnd); current.setLength(declarationEnd - current.getRange().getOffset()); fStack.pop(); } /** * @return <code>true</code> if the current container is class-like. */ private boolean inClassBody() { int typeCode= getCurrentContainer().getTypeCode(); return typeCode == ICElement.C_CLASS || typeCode == ICElement.C_TEMPLATE_CLASS || typeCode == ICElement.C_STRUCT || typeCode == ICElement.C_TEMPLATE_STRUCT || typeCode == ICElement.C_UNION || typeCode == ICElement.C_TEMPLATE_UNION; } /** * Test whether the given declaration is a templated declaration. * * @param node * @return <code>true</code> if the declaration is templated. */ private boolean isTemplateDecl(IASTDeclaration node) { return node.getParent() instanceof ICPPASTTemplateDeclaration; } private boolean hasNestedPointerOperators(IASTDeclarator declarator) { declarator= declarator.getNestedDeclarator(); while (declarator != null) { if (declarator.getPointerOperators().length > 0) { return true; } declarator= declarator.getNestedDeclarator(); } return false; } private String getEnumerationName(IASTEnumerationSpecifier enumSpecifier) { String nodeName= ASTStringUtil.getQualifiedName(enumSpecifier.getName()); if (nodeName.length() == 0) { nodeName= ANONYMOUS_NAME; } return nodeName; } private String getTypeName(IASTCompositeTypeSpecifier compositeTypeSpec) { String nodeName= ASTStringUtil.getQualifiedName(compositeTypeSpec.getName()); if (nodeName.length() == 0) { nodeName= ANONYMOUS_NAME; } return nodeName; } private String getDeclaratorName(IASTDeclarator node) { node= getInnermostDeclarator(node); IASTName name= node.getName(); String nodeName= ASTStringUtil.getQualifiedName(name); if (nodeName.length() == 0) { nodeName= ANONYMOUS_NAME; } return nodeName; } private IASTDeclarator getInnermostDeclarator(IASTDeclarator node) { IASTDeclarator nested= node.getNestedDeclarator(); while (nested != null) { node= nested; nested= node.getNestedDeclarator(); } return node; } private static IASTFileLocation getMaxFileLocation(IASTNodeLocation[] locations) { if (locations == null || locations.length == 0) { return null; } final IASTNodeLocation nodeLocation= locations[locations.length-1]; return nodeLocation.asFileLocation(); } private static IASTFileLocation getMinFileLocation(IASTNodeLocation[] locations) { if (locations == null || locations.length == 0) { return null; } final IASTNodeLocation nodeLocation= locations[0]; return nodeLocation.asFileLocation(); } }