/******************************************************************************* * Copyright (c) 2004, 2011 QNX 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: * QNX Software Systems - initial API and implementation * IBM Corporation * Warren Paul (Nokia) - 173555 * Anton Leherbauer (Wind River Systems) * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.internal.ui.wizards.classwizard; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; 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.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.browser.IQualifiedTypeName; import org.eclipse.cdt.core.browser.ITypeReference; import org.eclipse.cdt.core.browser.QualifiedTypeName; import org.eclipse.cdt.core.formatter.CodeFormatter; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.CoreModel; import org.eclipse.cdt.core.model.ICContainer; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.IIncludeEntry; import org.eclipse.cdt.core.model.IPathEntry; import org.eclipse.cdt.core.model.ITranslationUnit; import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.core.parser.IScannerInfoProvider; import org.eclipse.cdt.core.parser.ast.ASTAccessVisibility; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.CodeGeneration; import org.eclipse.cdt.utils.PathUtil; import org.eclipse.cdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.cdt.internal.corext.util.CModelUtil; import org.eclipse.cdt.internal.corext.util.CodeFormatterUtil; import org.eclipse.cdt.internal.corext.util.Strings; import org.eclipse.cdt.internal.formatter.scanner.Scanner; import org.eclipse.cdt.internal.formatter.scanner.Token; import org.eclipse.cdt.internal.ui.wizards.filewizard.NewSourceFileGenerator; public class NewClassCodeGenerator { private final IPath fHeaderPath; private final IPath fSourcePath; private final IPath fTestPath; private String fClassName; private IQualifiedTypeName fNamespace; private final IBaseClassInfo[] fBaseClasses; private final IMethodStub[] fMethodStubs; private ITranslationUnit fCreatedHeaderTU; private ITranslationUnit fCreatedSourceTU; private ITranslationUnit fCreatedTestTU; private ICElement fCreatedClass; private String fFullyQualifiedClassName; private boolean fForceSourceFileCreation; /** * When set to <code>true</code>, the source file is created, even if no stubs have * been selected. */ public void setForceSourceFileCreation(boolean force) { fForceSourceFileCreation = force; } public static class CodeGeneratorException extends CoreException { /** * Comment for <code>serialVersionUID</code> */ private static final long serialVersionUID = 1L; public CodeGeneratorException(String message) { super(new Status(IStatus.ERROR, CUIPlugin.getPluginId(), IStatus.OK, message, null)); } public CodeGeneratorException(Throwable e) { super(new Status(IStatus.ERROR, CUIPlugin.getPluginId(), IStatus.OK, e.getMessage(), e)); } } /** * @param headerPath the header file path * @param sourcePath the source file path * @param testPath the test file path, can be {@code null} * @param className the class name * @param namespace the namespace name * @param baseClasses the base classes * @param methodStubs the method stubs */ public NewClassCodeGenerator(IPath headerPath, IPath sourcePath, IPath testPath, String className, String namespace, IBaseClassInfo[] baseClasses, IMethodStub[] methodStubs) { fHeaderPath = headerPath; fSourcePath = sourcePath; fTestPath = testPath; if (className != null && className.length() > 0) { fClassName = className; } if (namespace != null && namespace.length() > 0) { fNamespace = new QualifiedTypeName(namespace); } if (fNamespace != null) { fFullyQualifiedClassName= fNamespace.append(fClassName).getFullyQualifiedName(); } else { fFullyQualifiedClassName= fClassName; } fBaseClasses = baseClasses; fMethodStubs = methodStubs; } public ICElement getCreatedClass() { return fCreatedClass; } public ITranslationUnit getCreatedHeaderTU() { return fCreatedHeaderTU; } public IFile getCreatedHeaderFile() { if (fCreatedHeaderTU != null) { return (IFile) fCreatedHeaderTU.getResource(); } return null; } public ITranslationUnit getCreatedSourceTU() { return fCreatedSourceTU; } public IFile getCreatedSourceFile() { if (fCreatedSourceTU != null) { return (IFile) fCreatedSourceTU.getResource(); } return null; } public IFile getCreatedTestFile() { if (fCreatedTestTU != null) { return (IFile) fCreatedTestTU.getResource(); } return null; } /** * Creates the new class. * * @param monitor * a progress monitor to report progress. * @throws CoreException * Thrown when the creation failed. * @throws InterruptedException * Thrown when the operation was cancelled. */ public ICElement createClass(IProgressMonitor monitor) throws CodeGeneratorException, CoreException, InterruptedException { if (monitor == null) monitor = new NullProgressMonitor(); monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_mainTask, 400); ITranslationUnit headerTU = null; ITranslationUnit sourceTU = null; ITranslationUnit testTU = null; ICElement createdClass = null; IWorkingCopy headerWorkingCopy = null; IWorkingCopy sourceWorkingCopy = null; IWorkingCopy testWorkingCopy = null; try { if (fHeaderPath != null) { // Get method stubs List<IMethodStub> publicMethods = getStubs(ASTAccessVisibility.PUBLIC, false); List<IMethodStub> protectedMethods = getStubs(ASTAccessVisibility.PROTECTED, false); List<IMethodStub> privateMethods = getStubs(ASTAccessVisibility.PRIVATE, false); IFile headerFile = NewSourceFileGenerator.createHeaderFile(fHeaderPath, true, new SubProgressMonitor(monitor, 50)); if (headerFile != null) { headerTU = (ITranslationUnit) CoreModel.getDefault().create(headerFile); if (headerTU == null) { throw new CodeGeneratorException("Failed to create " + headerFile); //$NON-NLS-1$ } // Create a working copy with a new owner headerWorkingCopy = headerTU.getWorkingCopy(); // headerWorkingCopy = headerTU.getSharedWorkingCopy(null, CUIPlugin.getDefault().getBufferFactory()); String headerContent = constructHeaderFileContent(headerTU, publicMethods, protectedMethods, privateMethods, headerWorkingCopy.getBuffer().getContents(), new SubProgressMonitor(monitor, 100)); if (headerContent != null) { headerContent= formatSource(headerContent, headerTU); } else { headerContent = ""; //$NON-NLS-1$ } headerWorkingCopy.getBuffer().setContents(headerContent); if (monitor.isCanceled()) { throw new InterruptedException(); } headerWorkingCopy.reconcile(); headerWorkingCopy.commit(true, monitor); monitor.worked(50); createdClass = headerWorkingCopy.getElement(fFullyQualifiedClassName); } fCreatedClass = createdClass; fCreatedHeaderTU = headerTU; } if (fSourcePath != null) { // Get method stubs List<IMethodStub> publicMethods = getStubs(ASTAccessVisibility.PUBLIC, true); List<IMethodStub> protectedMethods = getStubs(ASTAccessVisibility.PROTECTED, true); List<IMethodStub> privateMethods = getStubs(ASTAccessVisibility.PRIVATE, true); if (!fForceSourceFileCreation && publicMethods.isEmpty() && protectedMethods.isEmpty() && privateMethods.isEmpty()) { monitor.worked(100); } else { IFile sourceFile = NewSourceFileGenerator.createSourceFile(fSourcePath, true, new SubProgressMonitor(monitor, 50)); if (sourceFile != null) { sourceTU = (ITranslationUnit) CoreModel.getDefault().create(sourceFile); if (sourceTU == null) { throw new CodeGeneratorException("Failed to create " + sourceFile); //$NON-NLS-1$ } monitor.worked(50); // Create a working copy with a new owner sourceWorkingCopy = sourceTU.getWorkingCopy(); String sourceContent = constructSourceFileContent(sourceTU, headerTU, publicMethods, protectedMethods, privateMethods, sourceWorkingCopy.getBuffer().getContents(), new SubProgressMonitor(monitor, 100)); if (sourceContent != null) { sourceContent = formatSource(sourceContent, sourceTU); } else { sourceContent = ""; //$NON-NLS-1$ } sourceWorkingCopy.getBuffer().setContents(sourceContent); if (monitor.isCanceled()) { throw new InterruptedException(); } sourceWorkingCopy.reconcile(); sourceWorkingCopy.commit(true, monitor); monitor.worked(50); } fCreatedSourceTU = sourceTU; } } if (fTestPath != null) { IFile testFile = NewSourceFileGenerator.createTestFile(fTestPath, true, new SubProgressMonitor(monitor, 50)); if (testFile != null) { testTU = (ITranslationUnit) CoreModel.getDefault().create(testFile); if (testTU == null) { throw new CodeGeneratorException("Failed to create " + testFile); //$NON-NLS-1$ } monitor.worked(50); // Create a working copy with a new owner testWorkingCopy = testTU.getWorkingCopy(); String testContent = constructTestFileContent(testTU, headerTU, testWorkingCopy.getBuffer().getContents(), new SubProgressMonitor(monitor, 100)); testContent= formatSource(testContent, testTU); testWorkingCopy.getBuffer().setContents(testContent); if (monitor.isCanceled()) { throw new InterruptedException(); } testWorkingCopy.reconcile(); testWorkingCopy.commit(true, monitor); monitor.worked(50); } fCreatedTestTU = testTU; } } catch (CodeGeneratorException e) { deleteAllCreatedFiles(); } finally { if (headerWorkingCopy != null) { headerWorkingCopy.destroy(); } if (sourceWorkingCopy != null) { sourceWorkingCopy.destroy(); } if (testWorkingCopy != null) { testWorkingCopy.destroy(); } monitor.done(); } return fCreatedClass; } private void deleteAllCreatedFiles() { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); for (IPath path : new IPath[] { fHeaderPath, fSourcePath, fSourcePath }) { if (path != null) { try { IFile file = root.getFile(path); file.delete(true, null); } catch (CoreException e) { } } } } /** * Format given source content according to the project's code style options. * * @param content the source content * @param tu the translation unit * @return the formatted source text or the original if the text could not be formatted successfully * @throws CModelException */ private String formatSource(String content, ITranslationUnit tu) throws CModelException { String lineDelimiter= StubUtility.getLineDelimiterUsed(tu); TextEdit edit= CodeFormatterUtil.format(CodeFormatter.K_TRANSLATION_UNIT, content, 0, lineDelimiter, tu.getCProject().getOptions(true)); if (edit != null) { IDocument doc= new Document(content); try { edit.apply(doc); content= doc.get(); } catch (MalformedTreeException exc) { CUIPlugin.log(exc); } catch (BadLocationException exc) { CUIPlugin.log(exc); } } return content; } public String constructHeaderFileContent(ITranslationUnit headerTU, List<IMethodStub> publicMethods, List<IMethodStub> protectedMethods, List<IMethodStub> privateMethods, String oldContents, IProgressMonitor monitor) throws CoreException { monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_task_header, 100); String lineDelimiter= StubUtility.getLineDelimiterUsed(headerTU); String namespaceBegin = fNamespace == null ? null : constructNamespaceBegin(headerTU, lineDelimiter); String namespaceEnd = fNamespace == null ? null : constructNamespaceEnd(headerTU, lineDelimiter); String classDefinition = constructClassDefinition(headerTU, publicMethods, protectedMethods, privateMethods, lineDelimiter); String includes = null; if (fBaseClasses != null && fBaseClasses.length > 0) { includes = constructBaseClassIncludes(headerTU, lineDelimiter, new SubProgressMonitor(monitor, 50)); } if (oldContents != null) { if (oldContents.length() == 0) { oldContents = null; } else if (!oldContents.endsWith(lineDelimiter)) { oldContents += lineDelimiter; } } String fileContent; if (oldContents != null) { int appendFirstCharPos = -1; StringBuilder text = new StringBuilder(); int insertionPos = getClassDefInsertionPos(oldContents); if (insertionPos == -1) { text.append(oldContents); } else { // Skip over whitespace int prependLastCharPos = insertionPos - 1; while (prependLastCharPos >= 0 && Character.isWhitespace(oldContents.charAt(prependLastCharPos))) { --prependLastCharPos; } if (prependLastCharPos >= 0) { text.append(oldContents.substring(0, prependLastCharPos + 1)); } appendFirstCharPos = prependLastCharPos + 1; } text.append(lineDelimiter); // Insert a blank line before class definition text.append(lineDelimiter); if (namespaceBegin != null) { text.append(namespaceBegin); text.append(lineDelimiter); text.append(lineDelimiter); } text.append(classDefinition); if (namespaceEnd != null) { if (!classDefinition.endsWith(lineDelimiter)) text.append(lineDelimiter); text.append(lineDelimiter); text.append(namespaceEnd); } if (appendFirstCharPos != -1) { // Insert a blank line after class definition text.append(lineDelimiter); // Skip over any extra whitespace int len = oldContents.length(); while (appendFirstCharPos < len && Character.isWhitespace(oldContents.charAt(appendFirstCharPos))) { ++appendFirstCharPos; } if (appendFirstCharPos < len) { text.append(oldContents.substring(appendFirstCharPos)); } } if (Strings.endsWith(text, lineDelimiter)) text.append(lineDelimiter); fileContent= text.toString(); } else { String namespaceName = fNamespace == null ? null : fNamespace.getFullyQualifiedName(); String classComment = getClassComment(headerTU, lineDelimiter); fileContent= CodeGeneration.getHeaderFileContent(headerTU, includes, namespaceBegin, namespaceEnd, namespaceName, classComment, classDefinition, fClassName, lineDelimiter); } monitor.done(); return fileContent; } public String constructNamespaceBegin(ITranslationUnit tu, String lineDelimiter) throws CoreException { StringBuilder text = new StringBuilder(); for (int i = 0; i < fNamespace.segmentCount(); i++) { String namespaceName = fNamespace.segment(i); if (i > 0) { text.append(lineDelimiter); } text.append(CodeGeneration.getNamespaceBeginContent(tu, namespaceName, lineDelimiter)); } return text.toString(); } public String constructNamespaceEnd(ITranslationUnit tu, String lineDelimiter) throws CoreException { StringBuilder text = new StringBuilder(); for (int i = fNamespace.segmentCount(); --i >= 0;) { String namespaceName = fNamespace.segment(i); text.append(CodeGeneration.getNamespaceEndContent(tu, namespaceName, lineDelimiter)); if (i > 0) { text.append(lineDelimiter); } } return text.toString(); } public String constructClassDefinition(ITranslationUnit tu, List<IMethodStub> publicMethods, List<IMethodStub> protectedMethods, List<IMethodStub> privateMethods, String lineDelimiter) throws CoreException { StringBuilder code = new StringBuilder(); String comment = getClassComment(tu, lineDelimiter); if (comment != null) { code.append(comment); code.append(lineDelimiter); } code.append("class "); //$NON-NLS-1$ code.append(fClassName); code.append(constructBaseClassInheritance()); code.append(" {"); //$NON-NLS-1$ code.append(lineDelimiter); String body = constructMethodDeclarations(tu, publicMethods, protectedMethods, privateMethods, lineDelimiter); body = CodeGeneration.getClassBodyContent(tu, fClassName, body, lineDelimiter); if (body != null) { code.append(body); if (!body.endsWith(lineDelimiter)) { code.append(lineDelimiter); } } code.append("};"); //$NON-NLS-1$ return removeRedundantVisibilityLabels(code.toString()); } private String removeRedundantVisibilityLabels(String code) { Scanner scanner = new Scanner(); scanner.setSource(code.toCharArray()); scanner.resetTo(0, code.length()); IDocument doc = new Document(code); try { MultiTextEdit edit = new MultiTextEdit(); int sectionType = Token.tBADCHAR; int previousTokenType = Token.tBADCHAR; int previousTokenOffset = -1; Token token; while ((token = scanner.nextToken()) != null) { if (token.type == Token.tCOLON) { switch (previousTokenType) { case Token.t_public: case Token.t_protected: case Token.t_private: if (previousTokenType == sectionType) { IRegion region1 = doc.getLineInformationOfOffset(previousTokenOffset); IRegion region2 = doc.getLineInformationOfOffset(token.offset); edit.addChild(new DeleteEdit(region1.getOffset(), region2.getOffset() + region2.getLength() - region1.getOffset())); } sectionType = previousTokenType; } } previousTokenType = token.type; previousTokenOffset = token.offset; } edit.apply(doc, 0); } catch (MalformedTreeException e) { CUIPlugin.log(e); } catch (BadLocationException e) { CUIPlugin.log(e); } return doc.get(); } private int getClassDefInsertionPos(String contents) { if (contents.length() == 0) { return -1; } //TODO temporary hack int insertPos = contents.lastIndexOf("#endif"); //$NON-NLS-1$ if (insertPos != -1) { // check if any code follows the #endif if ((contents.indexOf('}', insertPos) != -1) || (contents.indexOf(';', insertPos) != -1)) { return -1; } } return insertPos; } /** * Retrieve the class comment. Returns the content of the 'type comment' template. * * @param tu the translation unit * @param lineDelimiter the line delimiter to use * @return the type comment or <code>null</code> if a type comment * is not desired * * @since 5.0 */ private String getClassComment(ITranslationUnit tu, String lineDelimiter) { if (isAddComments(tu)) { try { String fqName= fFullyQualifiedClassName; String comment= CodeGeneration.getClassComment(tu, fqName, lineDelimiter); if (comment != null && isValidComment(comment)) { return comment; } } catch (CoreException e) { CUIPlugin.log(e); } } return null; } private boolean isValidComment(String template) { // TODO verify comment return true; } /** * Returns if comments are added. The settings as specified in the preferences is used. * * @param tu * @return Returns <code>true</code> if comments can be added * @since 5.0 */ public boolean isAddComments(ITranslationUnit tu) { return StubUtility.doAddComments(tu.getCProject()); } private String constructMethodDeclarations(ITranslationUnit tu, List<IMethodStub> publicMethods, List<IMethodStub> protectedMethods, List<IMethodStub> privateMethods, String lineDelimiter) throws CoreException { StringBuilder text = new StringBuilder(); if (!publicMethods.isEmpty()) { text.append("public:"); //$NON-NLS-1$ text.append(lineDelimiter); for (IMethodStub stub : publicMethods) { String code = stub.createMethodDeclaration(tu, fClassName, fBaseClasses, lineDelimiter); text.append('\t'); text.append(code); text.append(lineDelimiter); } } if (!protectedMethods.isEmpty()) { if (text.length() > 0) { text.append(lineDelimiter); } text.append("protected:"); //$NON-NLS-1$ text.append(lineDelimiter); for (IMethodStub stub : protectedMethods) { String code = stub.createMethodDeclaration(tu, fClassName, fBaseClasses, lineDelimiter); text.append('\t'); text.append(code); text.append(lineDelimiter); } } if (!privateMethods.isEmpty()) { if (text.length() > 0) { text.append(lineDelimiter); } text.append("private:"); //$NON-NLS-1$ text.append(lineDelimiter); for (IMethodStub stub : privateMethods) { String code = stub.createMethodDeclaration(tu, fClassName, fBaseClasses, lineDelimiter); text.append('\t'); text.append(code); text.append(lineDelimiter); } } return text.toString(); } private List<IMethodStub> getStubs(ASTAccessVisibility access, boolean skipInline) { List<IMethodStub> list = new ArrayList<IMethodStub>(); if (fMethodStubs != null) { for (int i = 0; i < fMethodStubs.length; ++i) { IMethodStub stub = fMethodStubs[i]; if (stub.getAccess() == access && (!skipInline || !stub.isInline())) { list.add(stub); } } } return list; } private String constructBaseClassInheritance() { if (fBaseClasses == null || fBaseClasses.length == 0) { return ""; //$NON-NLS-1$ } StringBuilder text = new StringBuilder(); text.append(" : "); //$NON-NLS-1$ for (int i = 0; i < fBaseClasses.length; ++i) { IBaseClassInfo baseClass = fBaseClasses[i]; String baseClassName = baseClass.getType().getQualifiedTypeName().getFullyQualifiedName(); if (i > 0) text.append(", "); //$NON-NLS-1$ if (baseClass.getAccess() == ASTAccessVisibility.PRIVATE) text.append("private"); //$NON-NLS-1$ else if (baseClass.getAccess() == ASTAccessVisibility.PROTECTED) text.append("private"); //$NON-NLS-1$ else text.append("public"); //$NON-NLS-1$ text.append(' '); if (baseClass.isVirtual()) text.append("virtual "); //$NON-NLS-1$ text.append(baseClassName); } return text.toString(); } private String constructBaseClassIncludes(ITranslationUnit headerTU, String lineDelimiter, IProgressMonitor monitor) throws CodeGeneratorException { monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_task_header_includePaths, 100); ICProject cProject = headerTU.getCProject(); IProject project = cProject.getProject(); IPath projectLocation = new Path(project.getLocationURI().getPath()); IPath headerLocation = new Path(headerTU.getResource().getLocationURI().getPath()); List<IPath> includePaths = getIncludePaths(headerTU); List<IPath> baseClassPaths = getBaseClassPaths(verifyBaseClasses()); // Add the missing include paths to the project if (createIncludePaths()) { List<IPath> newIncludePaths = getMissingIncludePaths(projectLocation, includePaths, baseClassPaths); if (!newIncludePaths.isEmpty()) { addIncludePaths(cProject, newIncludePaths, monitor); } } List<IPath> systemIncludes = new ArrayList<IPath>(); List<IPath> localIncludes = new ArrayList<IPath>(); // Sort the include paths into system and local for (IPath baseClassLocation : baseClassPaths) { boolean isSystemIncludePath = false; IPath includePath = PathUtil.makeRelativePathToProjectIncludes(baseClassLocation, project); if (includePath != null && !projectLocation.isPrefixOf(baseClassLocation)) { isSystemIncludePath = true; } else if (projectLocation.isPrefixOf(baseClassLocation) && projectLocation.isPrefixOf(headerLocation)) { includePath = PathUtil.makeRelativePath(baseClassLocation, headerLocation.removeLastSegments(1)); } if (includePath == null) includePath = baseClassLocation; // Make the new #include path in the source file only point to a relative file // (i.e. now that the path has been included above in the project) includePath = includePath.removeFirstSegments(includePath.segmentCount() - 1).setDevice(null); if (isSystemIncludePath) systemIncludes.add(includePath); else localIncludes.add(includePath); } StringBuilder text = new StringBuilder(); // Write the system include paths, e.g. #include <header.h> for (IPath includePath : systemIncludes) { if (!(headerTU.getElementName().equals(includePath.toString()))) { String include = getIncludeString(includePath.toString(), true); text.append(include); text.append(lineDelimiter); } } // Write the local include paths, e.g. #include "header.h" for (IPath includePath : localIncludes) { if (!(headerTU.getElementName().equals(includePath.toString()))) { String include = getIncludeString(includePath.toString(), false); text.append(include); text.append(lineDelimiter); } } monitor.done(); return text.toString(); } /** * Checks if the base classes need to be verified (ie they must exist in the project) * * @return <code>true</code> if the base classes should be verified */ private boolean verifyBaseClasses() { return NewClassWizardPrefs.verifyBaseClasses(); } /** * Checks if include paths can be added to the project as needed. * * @return <code>true</code> if the include paths should be added */ private boolean createIncludePaths() { return NewClassWizardPrefs.createIncludePaths(); } private void addIncludePaths(ICProject cProject, List<IPath> newIncludePaths, IProgressMonitor monitor) throws CodeGeneratorException { monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_task_header_addIncludePaths, 100); //TODO prefs option whether to add to project or parent source folder? IPath addToResourcePath = cProject.getPath(); try { List<IPathEntry> pathEntryList = new ArrayList<IPathEntry>(); List<IPathEntry> checkEntryList = new ArrayList<IPathEntry>(); IPathEntry[] checkEntries = cProject.getResolvedPathEntries(); IPathEntry[] pathEntries = cProject.getRawPathEntries(); if (pathEntries != null) { for (int i = 0; i < pathEntries.length; ++i) { pathEntryList.add(pathEntries[i]); } } for (IPathEntry checkEntrie : checkEntries) { if (checkEntrie instanceof IIncludeEntry) checkEntryList.add(checkEntrie); } for (IPath folderToAdd : newIncludePaths) { // do not add any #includes that are local to the project if (cProject.getPath().segment(0).equals(folderToAdd.segment(0))) continue; ICProject includeProject = toCProject(PathUtil.getEnclosingProject(folderToAdd)); if (includeProject != null) { // Make sure that the include is made the same way that build properties for // projects makes them, so .contains below is a valid check IIncludeEntry entry = CoreModel.newIncludeEntry(addToResourcePath, null, new Path(includeProject.getProject().getLocationURI().getPath()), true); if (!checkEntryList.contains(entry)) // if the path already exists in the #includes then don't add it pathEntryList.add(entry); } } pathEntries = pathEntryList.toArray(new IPathEntry[pathEntryList.size()]); cProject.setRawPathEntries(pathEntries, new SubProgressMonitor(monitor, 80)); } catch (CModelException e) { throw new CodeGeneratorException(e); } monitor.done(); } private ICProject toCProject(IProject enclosingProject) { if (enclosingProject != null) return CoreModel.getDefault().create(enclosingProject); return null; } private List<IPath> getMissingIncludePaths(IPath projectLocation, List<IPath> includePaths, List<IPath> baseClassPaths) { // check for missing include paths List<IPath> newIncludePaths = new ArrayList<IPath>(); for (IPath baseClassLocation : baseClassPaths) { // skip any paths inside the same project //TODO possibly a preferences option? if (projectLocation.isPrefixOf(baseClassLocation)) { continue; } IPath folderToAdd = baseClassLocation.removeLastSegments(1); IPath canonPath = PathUtil.getCanonicalPath(folderToAdd); if (canonPath != null) folderToAdd = canonPath; // see if folder or its parent hasn't already been added for (IPath newFolder : newIncludePaths) { if (newFolder.isPrefixOf(folderToAdd)) { folderToAdd = null; break; } } if (folderToAdd != null) { // search include paths boolean foundPath = false; for (IPath includePath : includePaths) { if (includePath.isPrefixOf(folderToAdd) || includePath.equals(folderToAdd)) { foundPath = true; break; } } if (!foundPath) { // remove any children of this folder for (Iterator<IPath> newIter = newIncludePaths.iterator(); newIter.hasNext(); ) { IPath newFolder = newIter.next(); if (folderToAdd.isPrefixOf(newFolder)) { newIter.remove(); } } if (!newIncludePaths.contains(folderToAdd)) { newIncludePaths.add(folderToAdd); } } } } return newIncludePaths; } private List<IPath> getIncludePaths(ITranslationUnit headerTU) throws CodeGeneratorException { IProject project = headerTU.getCProject().getProject(); // get the parent source folder ICContainer sourceFolder = CModelUtil.getSourceFolder(headerTU); if (sourceFolder == null) { throw new CodeGeneratorException("Could not find source folder"); //$NON-NLS-1$ } // get the include paths IScannerInfoProvider provider = CCorePlugin.getDefault().getScannerInfoProvider(project); if (provider != null) { IScannerInfo info = provider.getScannerInformation(sourceFolder.getResource()); if (info != null) { String[] includePaths = info.getIncludePaths(); if (includePaths != null) { List<IPath> list = new ArrayList<IPath>(); for (int i = 0; i < includePaths.length; ++i) { //TODO do we need to canonicalize these paths first? IPath path = new Path(includePaths[i]); if (!list.contains(path)) { list.add(path); } } return list; } } } return null; } private List<IPath> getBaseClassPaths(boolean verifyLocation) throws CodeGeneratorException { List<IPath> list = new ArrayList<IPath>(); for (int i = 0; i < fBaseClasses.length; ++i) { IBaseClassInfo baseClass = fBaseClasses[i]; ITypeReference ref = baseClass.getType().getResolvedReference(); IPath baseClassLocation = null; if (ref != null) { baseClassLocation = ref.getLocation(); } if (baseClassLocation == null) { if (verifyLocation) { throw new CodeGeneratorException("Could not find base class " + baseClass.toString()); //$NON-NLS-1$ } } else if (!list.contains(baseClassLocation)) { list.add(baseClassLocation); } } return list; } public String constructSourceFileContent(ITranslationUnit sourceTU, ITranslationUnit headerTU, List<IMethodStub> publicMethods, List<IMethodStub> protectedMethods, List<IMethodStub> privateMethods, String oldContents, IProgressMonitor monitor) throws CoreException { monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_task_source, 150); String lineDelimiter= StubUtility.getLineDelimiterUsed(sourceTU); String includeString = null; if (headerTU != null) { includeString = getHeaderIncludeString(sourceTU, headerTU, new SubProgressMonitor(monitor, 50)); if (includeString != null) { // Check if file already has the include. if (oldContents != null && hasInclude(oldContents, includeString)) { // Don't bother to add it. includeString = null; } } } String methodBodies = null; if (!publicMethods.isEmpty() || !protectedMethods.isEmpty() || !privateMethods.isEmpty()) { // TODO sort methods (e.g. constructor always first?) methodBodies = constructMethodBodies(sourceTU, publicMethods, protectedMethods, privateMethods, lineDelimiter, new SubProgressMonitor(monitor, 50)); } String namespaceBegin = fNamespace == null ? null : constructNamespaceBegin(sourceTU, lineDelimiter); String namespaceEnd = fNamespace == null ? null : constructNamespaceEnd(sourceTU, lineDelimiter); if (oldContents != null) { if (oldContents.length() == 0) { oldContents = null; } else if (!oldContents.endsWith(lineDelimiter)) { oldContents += lineDelimiter; } } String fileContent; if (oldContents != null) { StringBuilder text = new StringBuilder(); if (includeString != null) { int insertionPos = getIncludeInsertionPos(oldContents); if (insertionPos == -1) { text.append(oldContents); text.append(lineDelimiter); text.append(includeString); text.append(lineDelimiter); } else { text.append(oldContents.substring(0, insertionPos)); text.append(includeString); text.append(lineDelimiter); text.append(oldContents.substring(insertionPos)); } } else { text.append(oldContents); } // Add a blank line text.append(lineDelimiter); if (methodBodies != null) { if (namespaceBegin != null) { text.append(namespaceBegin); text.append(lineDelimiter); text.append(lineDelimiter); } text.append(methodBodies); if (namespaceEnd != null) { if (!methodBodies.endsWith(lineDelimiter)) text.append(lineDelimiter); text.append(lineDelimiter); text.append(namespaceEnd); } } if (Strings.endsWith(text, lineDelimiter)) text.append(lineDelimiter); fileContent = text.toString(); } else { String namespaceName = fNamespace == null ? null : fNamespace.getFullyQualifiedName(); fileContent= CodeGeneration.getBodyFileContent(sourceTU, includeString, namespaceBegin, namespaceEnd, namespaceName, null, methodBodies, fClassName, lineDelimiter); } monitor.done(); return fileContent; } public String constructTestFileContent(ITranslationUnit testTU, ITranslationUnit headerTU, String oldContents, IProgressMonitor monitor) throws CoreException { monitor.beginTask(NewClassWizardMessages.NewClassCodeGeneration_createType_task_source, 150); String lineDelimiter= StubUtility.getLineDelimiterUsed(testTU); String includeString = null; if (headerTU != null) { includeString = getHeaderIncludeString(testTU, headerTU, new SubProgressMonitor(monitor, 50)); if (includeString != null) { // Check if file already has the include. if (oldContents != null && hasInclude(oldContents, includeString)) { // Don't bother to add it. includeString = null; } } } if (oldContents != null) { if (oldContents.length() == 0) { oldContents = null; } else if (!oldContents.endsWith(lineDelimiter)) { oldContents += lineDelimiter; } } String fileContent; if (oldContents != null) { StringBuilder text = new StringBuilder(); if (includeString != null) { int insertionPos = getIncludeInsertionPos(oldContents); if (insertionPos == -1) { text.append(oldContents); text.append(lineDelimiter); text.append(includeString); text.append(lineDelimiter); } else { text.append(oldContents.substring(0, insertionPos)); text.append(includeString); text.append(lineDelimiter); text.append(oldContents.substring(insertionPos)); } } else { text.append(oldContents); } if (Strings.endsWith(text, lineDelimiter)) text.append(lineDelimiter); fileContent = text.toString(); } else { String namespaceBegin = fNamespace == null ? null : constructNamespaceBegin(testTU, lineDelimiter); String namespaceEnd = fNamespace == null ? null : constructNamespaceEnd(testTU, lineDelimiter); String namespaceName = fNamespace == null ? null : fNamespace.getFullyQualifiedName(); fileContent= CodeGeneration.getTestFileContent(testTU, includeString, namespaceBegin, namespaceEnd, namespaceName, null, fClassName, lineDelimiter); } monitor.done(); return fileContent; } private String getHeaderIncludeString(ITranslationUnit sourceTU, ITranslationUnit headerTU, IProgressMonitor monitor) { IProject project = headerTU.getCProject().getProject(); IPath projectLocation = new Path(project.getLocationURI().getPath()); IPath headerLocation = new Path(headerTU.getResource().getLocationURI().getPath()); IPath sourceLocation = new Path(sourceTU.getResource().getLocationURI().getPath()); IPath includePath = PathUtil.makeRelativePathToProjectIncludes(headerLocation, project); boolean isSystemIncludePath = false; if (headerTU.getResource() == null && includePath != null && !projectLocation.isPrefixOf(headerLocation)) { isSystemIncludePath = true; } else if (projectLocation.isPrefixOf(headerLocation) && projectLocation.isPrefixOf(sourceLocation)) { includePath = PathUtil.makeRelativePath(headerLocation, sourceLocation.removeLastSegments(1)); } if (includePath == null) includePath = headerLocation; return getIncludeString(includePath.toString(), isSystemIncludePath); } private boolean hasInclude(String contents, String include) { int maxStartPos = contents.length() - include.length() - 1; if (maxStartPos < 0) { return false; } int startPos = 0; while (startPos <= maxStartPos) { int includePos = contents.indexOf(include, startPos); if (includePos == -1) { return false; } if (includePos == startPos) { return true; } // TODO detect if it's commented out // make sure it's on a line by itself int linePos = findFirstLineChar(contents, includePos); if (linePos == -1 || linePos == includePos) { return true; } boolean badLine = false; for (int pos = linePos; pos < includePos; ++pos) { char c = contents.charAt(pos); if (!Character.isWhitespace(c)) { badLine = true; break; } } if (!badLine) { return true; } // keep searching startPos = includePos + include.length(); } return false; } private int getIncludeInsertionPos(String contents) { if (contents.length() == 0) { return -1; } //TODO temporary hack int includePos = contents.lastIndexOf("#include "); //$NON-NLS-1$ if (includePos != -1) { // Find the end of line int startPos = includePos + "#include ".length(); //$NON-NLS-1$ int eolPos = findLastLineChar(contents, startPos); if (eolPos != -1) { int insertPos = eolPos + 1; if (insertPos < (contents.length() - 1)) { return insertPos; } } } return -1; } private String constructMethodBodies(ITranslationUnit tu, List<IMethodStub> publicMethods, List<IMethodStub> protectedMethods, List<IMethodStub> privateMethods, String lineDelimiter, IProgressMonitor monitor) throws CoreException { StringBuilder text = new StringBuilder(); if (!publicMethods.isEmpty()) { for (Iterator<IMethodStub> i = publicMethods.iterator(); i.hasNext();) { IMethodStub stub = i.next(); String code = stub.createMethodImplementation(tu, fClassName, fBaseClasses, lineDelimiter); text.append(code); text.append(lineDelimiter); if (i.hasNext()) text.append(lineDelimiter); } } if (!protectedMethods.isEmpty()) { for (Iterator<IMethodStub> i = protectedMethods.iterator(); i.hasNext();) { IMethodStub stub = i.next(); String code = stub.createMethodImplementation(tu, fClassName, fBaseClasses, lineDelimiter); text.append(code); text.append(lineDelimiter); if (i.hasNext()) text.append(lineDelimiter); } } if (!privateMethods.isEmpty()) { for (Iterator<IMethodStub> i = privateMethods.iterator(); i.hasNext();) { IMethodStub stub = i.next(); String code = stub.createMethodImplementation(tu, fClassName, fBaseClasses, lineDelimiter); text.append(code); text.append(lineDelimiter); if (i.hasNext()) text.append(lineDelimiter); } } return text.toString(); } private String getIncludeString(String fileName, boolean isSystemInclude) { StringBuilder buf = new StringBuilder(); buf.append("#include "); //$NON-NLS-1$ if (isSystemInclude) buf.append('<'); else buf.append('\"'); buf.append(fileName); if (isSystemInclude) buf.append('>'); else buf.append('\"'); return buf.toString(); } private int findLastLineChar(String contents, int startPos) { int endPos = contents.length() - 1; int linePos = startPos; while (linePos <= endPos) { char c = contents.charAt(linePos); if (c == '\r') { // could be '\r\n' as one delimiter if (linePos < endPos && contents.charAt(linePos + 1) == '\n') { return linePos + 1; } return linePos; } else if (c == '\n') { return linePos; } ++linePos; } return -1; } private int findFirstLineChar(String contents, int startPos) { int linePos = startPos; while (linePos >= 0) { char c = contents.charAt(linePos); if (c == '\n' || c == '\r') { if (linePos + 1 < startPos) { return linePos + 1; } return -1; } --linePos; } return -1; } }