/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* QNX Software Systems
* Sergey Prigogin (Google)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.corext.codemanipulation;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.IBuffer;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.IMacro;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.ui.IRequiredInclude;
import org.eclipse.cdt.internal.ui.editor.CEditorMessages;
/**
* Adds includes and 'using' declarations to a translation unit.
* If the translation unit is open in an editor, be sure to pass over its working copy.
*/
public class AddIncludesOperation implements IWorkspaceRunnable {
private final ITranslationUnit fTranslationUnit;
private final int fBeforeOffset;
private final IRequiredInclude[] fIncludes;
private final String[] fUsings;
private String fNewLine;
private IBuffer fBuffer;
private List<ICElement> fExistingIncludes;
private List<ICElement> fExistingUsings;
private InsertEdit fIncludesInsert;
private InsertEdit fUsingsInsert;
private int fIncludesPos= -1;
/**
* @param tu a translation unit.
* @param beforeOffset includes and 'using' declarations have to be inserted before this offset.
* @param includes '#include' statements to insert.
* @param usings 'using' statements to insert.
*/
public AddIncludesOperation(ITranslationUnit tu, int beforeOffset, IRequiredInclude[] includes,
String[] usings) {
fTranslationUnit = tu;
fBeforeOffset = beforeOffset;
fIncludes= includes;
fUsings = usings;
}
/**
* @return Returns the scheduling rule for this operation
*/
public ISchedulingRule getSchedulingRule() {
return ResourcesPlugin.getWorkspace().getRoot();
}
public void run(IProgressMonitor monitor) throws CoreException {
if (monitor == null) {
monitor= new NullProgressMonitor();
}
try {
monitor.beginTask(CEditorMessages.AddIncludesOperation_description, 3);
fBuffer = fTranslationUnit.getBuffer();
fNewLine= getLineSeparator();
fExistingIncludes = fTranslationUnit.getChildrenOfType(ICElement.C_INCLUDE);
fIncludesInsert = getIncludesInsert();
monitor.worked(1);
if (fUsings != null && fUsings.length > 0) {
fExistingUsings = fTranslationUnit.getChildrenOfType(ICElement.C_USING);
}
fUsingsInsert = getUsingsInsert();
monitor.worked(1);
if (fIncludesInsert != null) {
fBuffer.replace(fIncludesInsert.getOffset(), 0, fIncludesInsert.getText());
}
if (fUsingsInsert != null) {
int offset = fUsingsInsert.getOffset();
if (fIncludesInsert != null && offset >= fIncludesInsert.getOffset()) {
offset += fIncludesInsert.getText().length();
}
fBuffer.replace(offset, 0, fUsingsInsert.getText());
}
monitor.worked(1);
} finally {
monitor.done();
}
}
private InsertEdit getIncludesInsert() throws CoreException {
if (fIncludes == null || fIncludes.length == 0) {
return null;
}
ArrayList<IRequiredInclude> toAdd = new ArrayList<IRequiredInclude>();
for (IRequiredInclude include : fIncludes) {
String name = include.getIncludeName();
boolean found = false;
for (ICElement element : fExistingIncludes) {
ISourceRange range = ((ISourceReference) element).getSourceRange();
if (range.getStartPos() + range.getLength() > fBeforeOffset) {
break;
}
if (name.equals(element.getElementName())) {
found = true;
break;
}
}
if (!found) {
toAdd.add(include);
}
}
if (toAdd.isEmpty()) {
return null;
}
// So we have our list. Now insert.
StringBuilder buf = new StringBuilder();
for (IRequiredInclude include : toAdd) {
if (include.isStandard()) {
buf.append("#include <" + include.getIncludeName() + ">").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$
} else {
buf.append("#include \"" + include.getIncludeName() + "\"").append(fNewLine); //$NON-NLS-1$ //$NON-NLS-2$
}
}
int pos= getIncludeInsertionPosition();
return new InsertEdit(pos, buf.toString());
}
private int getIncludeInsertionPosition() throws CModelException {
if (fIncludesPos < 0) {
if (fExistingIncludes.isEmpty()) {
fIncludesPos= getOffsetAfterLeadingMacroDefinitions();
} else {
fIncludesPos = getOffsetAfterLast(fExistingIncludes);
}
}
return fIncludesPos;
}
private InsertEdit getUsingsInsert() throws CoreException {
if (fUsings == null || fUsings.length == 0) {
return null;
}
ArrayList<String> toAdd = new ArrayList<String>(fUsings.length);
for (String name : fUsings) {
boolean found = false;
for (ICElement element : fExistingUsings) {
ISourceRange range = ((ISourceReference) element).getSourceRange();
if (range.getStartPos() + range.getLength() > fBeforeOffset) {
break;
}
if (name.equals(element.getElementName())) {
found = true;
break;
}
}
if (!found) {
toAdd.add(name);
}
}
if (toAdd.isEmpty()) {
return null;
}
// So we have our list. Now insert.
StringBuilder buf = new StringBuilder();
for (String using : toAdd) {
buf.append("using ").append(using).append(';').append(fNewLine); //$NON-NLS-1$
}
int pos = getOffsetAfterLast(fExistingUsings);
int pos2 = getIncludeInsertionPosition();
if (pos <= pos2) {
pos = pos2;
buf.insert(0, fNewLine); // Add a blank line between #include and using statements.
}
return new InsertEdit(pos, buf.toString());
}
/**
* Find the last of elements located before fBeforeOffset and returns offset of the following line.
* @param elements source elements to consider.
* @return offset of the line after the last of elements located before fBeforeOffset, or
* zero, if there is no such element.
* @throws CModelException
*/
private int getOffsetAfterLast(List<ICElement> elements) throws CModelException {
for (int i = elements.size(); --i >= 0;) {
ISourceRange range = ((ISourceReference) elements.get(i)).getSourceRange();
int end = range.getStartPos() + range.getLength();
if (end <= fBeforeOffset) {
return findNewLine(range.getStartPos() + range.getLength());
}
}
return 0;
}
/**
* Find the last leading macro definition before <code>fBeforeOffset</code>.
* And returns the offset of the line after.
*/
private int getOffsetAfterLeadingMacroDefinitions() throws CModelException {
ISourceRange found= null;
for (ICElement child: fTranslationUnit.getChildren()) {
if (!(child instanceof IMacro) || !(child instanceof ISourceReference))
break;
final ISourceReference sourceRef = (ISourceReference) child;
if (!sourceRef.isActive())
break;
ISourceRange range= sourceRef.getSourceRange();
if (range.getStartPos() + range.getLength() > fBeforeOffset)
break;
found= range;
}
if (found != null) {
return findNewLine(found.getStartPos() + found.getLength());
}
return 0;
}
private int findNewLine(int pos) {
while (fBuffer.getChar(pos) != '\n') {
pos++;
}
if (fBuffer.getChar(pos) == '\r') {
pos++;
}
return pos + 1;
}
private String getLineSeparator() {
try {
if (fBuffer instanceof IAdaptable) {
IDocument doc= (IDocument) ((IAdaptable) fBuffer).getAdapter(IDocument.class);
if (doc != null) {
String delim= doc.getLineDelimiter(0);
if (delim != null) {
return delim;
}
}
}
} catch (BadLocationException e) {
}
return System.getProperty("line.separator", "\n"); //$NON-NLS-1$//$NON-NLS-2$
}
}