/*******************************************************************************
* Copyright (c) 2008, 2010 Symbian Software Systems 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:
* Andrew Ferguson (Symbian) - Initial implementation
* Anton Leherbauer (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.ui.text.doctools.doxygen;
import java.util.LinkedHashSet;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.cdt.core.dom.ast.ExpansionOverlapsBoundaryException;
import org.eclipse.cdt.core.dom.ast.IASTArrayDeclarator;
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.IASTEnumerationSpecifier.IASTEnumerator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.IASTFunctionDefinition;
import org.eclipse.cdt.core.dom.ast.IASTNodeLocation;
import org.eclipse.cdt.core.dom.ast.IASTParameterDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclSpecifier;
import org.eclipse.cdt.core.dom.ast.IASTSimpleDeclaration;
import org.eclipse.cdt.core.dom.ast.IASTStandardFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTFunctionDeclarator;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTLinkageSpecification;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateDeclaration;
import org.eclipse.cdt.core.parser.IToken;
import org.eclipse.cdt.ui.text.doctools.DefaultMultilineCommentAutoEditStrategy;
/**
* {@link IAutoEditStrategy} for adding Doxygen tags for comments.
*
* @since 5.0
* @noextend This class is not intended to be subclassed by clients.
*/
public class DoxygenMultilineAutoEditStrategy extends DefaultMultilineCommentAutoEditStrategy {
private static final String SINGLELINE_COMMENT_PRECEDING = "//!< "; //$NON-NLS-1$
private static final String PARAM = "@param "; //$NON-NLS-1$
private static final String RETURN = "@return"; //$NON-NLS-1$
protected boolean documentPureVirtuals= true;
protected boolean documentDeclarations= true;
private String fLineDelimiter;
public DoxygenMultilineAutoEditStrategy() {
}
/**
* @param decl the function declarator to document
* @param ds the function specifier to document
* @return content describing the specified function
*/
protected StringBuilder documentFunction(IASTFunctionDeclarator decl, IASTDeclSpecifier ds) {
StringBuilder result= new StringBuilder();
result.append(documentFunctionParameters(getParameterDecls(decl)));
boolean hasReturn= true;
if(ds instanceof IASTSimpleDeclSpecifier) {
IASTSimpleDeclSpecifier sds= (IASTSimpleDeclSpecifier) ds;
if(sds.getType() == IASTSimpleDeclSpecifier.t_void ||
sds.getType() == IASTSimpleDeclSpecifier.t_unspecified) {
hasReturn= false;
}
}
if(hasReturn) {
result.append(documentFunctionReturn());
}
return result;
}
/**
* Returns the comment content to add to the documentation comment.
* @param decls The parameter declarations to describe
* @return a buffer containing the comment content to generate to describe the parameters of
* the specified {@link IASTParameterDeclaration} objects.
*/
protected StringBuilder documentFunctionParameters(IASTParameterDeclaration[] decls) {
StringBuilder result= new StringBuilder();
for(int i=0; i<decls.length; i++) {
if(!isVoidParameter(decls[i])) {
result.append(PARAM).append(getParameterName(decls[i])).append(getLineDelimiter());
}
}
return result;
}
/**
* Get the default line delimiter for the currently customized document
* which should be used for new lines.
*
* @return the default line delimiter
*/
private String getLineDelimiter() {
return fLineDelimiter;
}
/**
* @param decl
* @return the name of the parameter
*/
String getParameterName(IASTParameterDeclaration decl) {
IASTDeclarator dtor= decl.getDeclarator();
for(int i=0; i<8 && dtor.getName().getRawSignature().length()==0 && dtor.getNestedDeclarator() != null; i++) {
dtor= dtor.getNestedDeclarator();
}
return dtor.getName().getRawSignature();
}
/**
* @param decl
* @return true if the specified parameter declaration is of void type
*/
boolean isVoidParameter(IASTParameterDeclaration decl) {
if(decl.getDeclSpecifier() instanceof IASTSimpleDeclSpecifier) {
if(((IASTSimpleDeclSpecifier)decl.getDeclSpecifier()).getType() == IASTSimpleDeclSpecifier.t_void) {
IASTDeclarator dtor= decl.getDeclarator();
if(dtor.getPointerOperators().length == 0) {
if(!(dtor instanceof IASTFunctionDeclarator) && !(dtor instanceof IASTArrayDeclarator)) {
return true;
}
}
}
}
return false;
}
/**
* @return the comment content to describe the return
*/
protected StringBuilder documentFunctionReturn() {
return new StringBuilder(RETURN).append(getLineDelimiter());
}
/**
* @param decl the function declarator to analyze
* @return the parameter declarations for the specified function definition
*/
protected IASTParameterDeclaration[] getParameterDecls(IASTFunctionDeclarator decl) {
IASTParameterDeclaration[] result;
if (decl instanceof IASTStandardFunctionDeclarator) {
IASTStandardFunctionDeclarator standardFunctionDecl= (IASTStandardFunctionDeclarator)decl;
result= standardFunctionDecl.getParameters();
} else /*if (def instanceof ICASTKnRFunctionDeclarator) {
ICASTKnRFunctionDeclarator knrDeclarator= (ICASTKnRFunctionDeclarator)decl;
result= knrDeclarator.getParameterDeclarations();
} else */{
result= new IASTParameterDeclaration[0];
}
return result;
}
/*
* @see org.eclipse.cdt.ui.text.doctools.DefaultMultilineCommentAutoEditStrategy#customizeAfterNewLineForDeclaration(org.eclipse.jface.text.IDocument, org.eclipse.cdt.core.dom.ast.IASTDeclaration, org.eclipse.jface.text.ITypedRegion)
*/
@Override
protected StringBuilder customizeAfterNewLineForDeclaration(IDocument doc, IASTDeclaration dec, ITypedRegion partition) {
fLineDelimiter = TextUtilities.getDefaultLineDelimiter(doc);
IASTDeclaration declToDocument = dec;
if(declToDocument instanceof ICPPASTLinkageSpecification) {
ICPPASTLinkageSpecification linkageSpecification = (ICPPASTLinkageSpecification)declToDocument;
IASTDeclaration[] declarations = linkageSpecification.getDeclarations();
if(declarations.length == 1) {
boolean isCurlyExtern = false;
IToken token = null;
try {
token = declarations[0].getTrailingSyntax();
} catch (UnsupportedOperationException e) {
return new StringBuilder();
} catch (ExpansionOverlapsBoundaryException e) {
return new StringBuilder();
}
if(token != null && token.getType() == IToken.tRBRACE) {
isCurlyExtern = true;
}
if(!isCurlyExtern) {
declToDocument = declarations[0];
}
}
}
while(declToDocument instanceof ICPPASTTemplateDeclaration) /* if? */
declToDocument= ((ICPPASTTemplateDeclaration)declToDocument).getDeclaration();
if(declToDocument instanceof IASTFunctionDefinition) {
IASTFunctionDefinition fd= (IASTFunctionDefinition) declToDocument;
return documentFunction(fd.getDeclarator(), fd.getDeclSpecifier());
}
if(declToDocument instanceof IASTSimpleDeclaration) {
IASTSimpleDeclaration sdec= (IASTSimpleDeclaration) declToDocument;
StringBuilder result= new StringBuilder();
if(sdec.getDeclSpecifier() instanceof IASTCompositeTypeSpecifier) {
return result;
} else {
IASTDeclarator[] dcs= sdec.getDeclarators();
if(dcs.length == 1 && dcs[0] instanceof IASTFunctionDeclarator) {
IASTFunctionDeclarator fdecl = (IASTFunctionDeclarator)dcs[0];
boolean shouldDocument= documentDeclarations;
if(documentPureVirtuals && dcs[0] instanceof ICPPASTFunctionDeclarator) {
ICPPASTFunctionDeclarator cppfdecl= (ICPPASTFunctionDeclarator) dcs[0];
shouldDocument = shouldDocument || cppfdecl.isPureVirtual();
}
if(shouldDocument) {
return documentFunction(fdecl, sdec.getDeclSpecifier());
}
}
}
}
try {
alterDoc(doc, declToDocument);
} catch(BadLocationException ble) {
/*ignore*/
}
return new StringBuilder();
}
/*
* Add post-declaration comments to enumerators, after initializing a doc-comment on an enumeration
*/
private void alterDoc(IDocument doc, IASTDeclaration dec) throws BadLocationException {
if(dec instanceof IASTSimpleDeclaration && ((IASTSimpleDeclaration)dec).getDeclSpecifier() instanceof IASTEnumerationSpecifier) {
IASTEnumerationSpecifier spc= (IASTEnumerationSpecifier) ((IASTSimpleDeclaration)dec).getDeclSpecifier();
IASTEnumerator[] enms= spc.getEnumerators();
class Entry {
final int offset, length;
StringBuilder comment;
Entry(int offset, int length, String comment) {
this.offset= offset;
this.length= length;
this.comment= new StringBuilder(comment);
}
@Override
public int hashCode() {
return offset;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Entry) {
Entry other= (Entry) obj;
return offset == other.offset;
}
return false;
}
}
boolean noCollisions= true;
LinkedHashSet<Entry> entries= new LinkedHashSet<Entry>();
for(IASTEnumerator enumerator : enms) {
IASTNodeLocation loc= enumerator.getName().getFileLocation();
if(loc != null) {
int nodeOffset= loc.getNodeOffset()+loc.getNodeLength();
String cmt= SINGLELINE_COMMENT_PRECEDING+enumerator.getName();
IRegion line= doc.getLineInformationOfOffset(nodeOffset);
if(!doc.get(line.getOffset(), line.getLength()).contains("//")) { //$NON-NLS-1$
noCollisions &= entries.add(new Entry(line.getOffset(),line.getLength(), cmt));
}
}
}
/*
* Only auto-insert comments if each enumerator is declared on a unique line
*/
if(noCollisions) {
int max= Integer.MIN_VALUE;
for(Entry e : entries) {
if(e.length > max)
max= e.length;
}
int addedLength=0;
for(Entry e : entries) {
// pad with whitespace
int toAdd= max-e.length;
for(int j=0; j<toAdd; j++) {
e.comment.insert(0, " "); //$NON-NLS-1$
}
doc.replace(e.offset+e.length+addedLength, 0, e.comment.toString());
addedLength+= e.comment.length();
}
}
}
}
}