/*******************************************************************************
* Copyright (c) 2000, 2008 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
*******************************************************************************/
package com.aptana.ide.search.epl.filesystem.ui.text;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.ContentStamp;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextEditBasedChangeGroup;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditProcessor;
import org.eclipse.text.edits.UndoEdit;
public class FileSystemTextFileChange extends TextFileChange {
// the file to change
private File fFile;
private int fAcquireCount;
private ITextFileBuffer fBuffer;
private ContentStamp fContentStamp;
/**
* Creates a new <code>TextFileChange</code> for the given file.
*
* @param name
* the change's name mainly used to render the change in the UI
* @param file
* the file this text change operates on
*/
public FileSystemTextFileChange(String name, File file) {
super(name, new FileSystemFile(file));
Assert.isNotNull(file);
fFile = file;
}
/**
* Returns the <code>File</code> this change is working on.
*
* @return the file this change is working on
*/
public File getFileSystemFile() {
return fFile;
}
/**
* @see org.eclipse.ltk.core.refactoring.TextFileChange#createUndoChange(org.eclipse.text.edits.UndoEdit,
* org.eclipse.ltk.core.refactoring.ContentStamp)
*/
protected Change createUndoChange(UndoEdit edit, ContentStamp stampToRestore) {
return new UndoFileSystemTextFileChange(getName(), fFile, edit,
fContentStamp, getSaveMode());
}
/**
* {@inheritDoc}
*/
public Object getModifiedElement() {
return fFile;
}
/**
* {@inheritDoc}
*/
public void initializeValidationData(IProgressMonitor monitor) {
}
/**
* {@inheritDoc}
*/
public RefactoringStatus isValid(IProgressMonitor monitor)
throws CoreException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
try {
monitor.beginTask("", 1); //$NON-NLS-1$
RefactoringStatus result = new RefactoringStatus();
return result;
} finally {
monitor.done();
}
}
/**
* {@inheritDoc}
*/
protected IDocument acquireDocument(IProgressMonitor pm)
throws CoreException {
fAcquireCount++;
if (fAcquireCount > 1) {
return fBuffer.getDocument();
}
ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
IPath path = new Path(fFile.getAbsolutePath());
manager.connect(path, LocationKind.LOCATION, pm);
fBuffer = manager.getTextFileBuffer(path, LocationKind.LOCATION);
IDocument result = fBuffer.getDocument();
fContentStamp = FileSystemContentStamps.get(fFile, result);
return result;
}
/**
* {@inheritDoc}
* <p>
* The implementation of this method only commits the underlying buffer if
* {@link #needsSaving()} and {@link #isDocumentModified()} returns
* <code>true</code>.
* </p>
*/
protected void commit(IDocument document, IProgressMonitor pm)
throws CoreException {
if (needsSaving()) {
fBuffer.commit(pm, false);
}
}
/**
* {@inheritDoc}
*/
protected void releaseDocument(IDocument document, IProgressMonitor pm)
throws CoreException {
Assert.isTrue(fAcquireCount > 0);
if (fAcquireCount == 1) {
ITextFileBufferManager manager = FileBuffers
.getTextFileBufferManager();
manager.disconnect(new Path(fFile.getAbsolutePath()),
LocationKind.LOCATION, pm);
}
fAcquireCount--;
}
/**
* @see org.eclipse.ltk.core.refactoring.TextChange#performEdits(org.eclipse.jface.text.IDocument)
*/
protected UndoEdit performEdits(final IDocument document)
throws BadLocationException, MalformedTreeException {
if (!fBuffer.isSynchronizationContextRequested()) {
return performEdits2(document);
}
ITextFileBufferManager fileBufferManager = FileBuffers
.getTextFileBufferManager();
/** The lock for waiting for computation in the UI thread to complete. */
final Lock completionLock = new Lock();
final UndoEdit[] result = new UndoEdit[1];
final BadLocationException[] exception = new BadLocationException[1];
Runnable runnable = new Runnable() {
public void run() {
synchronized (completionLock) {
try {
result[0] = performEdits2(document);
} catch (BadLocationException e) {
exception[0] = e;
} finally {
completionLock.fDone = true;
completionLock.notifyAll();
}
}
}
};
synchronized (completionLock) {
fileBufferManager.execute(runnable);
while (!completionLock.fDone) {
try {
completionLock.wait(500);
} catch (InterruptedException x) {
}
}
}
if (exception[0] != null) {
throw exception[0];
}
return result[0];
}
/**
* @see org.eclipse.ltk.core.refactoring.TextFileChange#isDocumentAcquired()
*/
protected boolean isDocumentAcquired() {
return fAcquireCount > 0;
}
/**
* @see org.eclipse.ltk.core.refactoring.TextFileChange#isDocumentModified()
*/
protected boolean isDocumentModified() {
if (fAcquireCount > 0) {
ContentStamp currentStamp = FileSystemContentStamps.get(fFile,
fBuffer.getDocument());
return !currentStamp.equals(fContentStamp);
}
return false;
}
private static class Lock {
/**
* <code>true</code> iff the operation is done.
*/
public boolean fDone;
}
private UndoEdit performEdits2(final IDocument document)
throws BadLocationException {
DocumentRewriteSession session = null;
try {
if (document instanceof IDocumentExtension4) {
session = ((IDocumentExtension4) document)
.startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
}
LinkedModeModel.closeAllModels(document);
TextEditProcessor processor = createTextEditProcessor(document,
TextEdit.CREATE_UNDO);
return processor.performEdits();
} finally {
if (session != null) {
((IDocumentExtension4) document).stopRewriteSession(session);
}
}
}
private TextEditProcessor createTextEditProcessor(IDocument document,
int flags) {
if (getEdit() == null) {
return new TextEditProcessor(document, new MultiTextEdit(0, 0),
flags);
}
List<TextEdit> excludes = new ArrayList<TextEdit>(0);
TextEditBasedChangeGroup[] groups = getChangeGroups();
for (int index = 0; index < groups.length; index++) {
TextEditBasedChangeGroup edit = groups[index];
if (!edit.isEnabled()) {
excludes.addAll(Arrays.asList(edit.getTextEditGroup()
.getTextEdits()));
}
}
LocalTextEditProcessor result = new LocalTextEditProcessor(document,
getEdit(), flags | TextEdit.UPDATE_REGIONS);
result.setExcludes((TextEdit[]) excludes.toArray(new TextEdit[excludes
.size()]));
return result;
}
/**
* Text edit processor which has the ability to selectively include or
* exclude single text edits.
*/
private static final class LocalTextEditProcessor extends TextEditProcessor {
private TextEdit[] fExcludes;
private TextEdit[] fIncludes;
protected LocalTextEditProcessor(IDocument document, TextEdit root,
int flags) {
super(document, root, flags);
}
public void setExcludes(TextEdit[] excludes) {
Assert.isNotNull(excludes);
Assert.isTrue(fIncludes == null);
fExcludes = flatten(excludes);
}
protected boolean considerEdit(TextEdit edit) {
if (fExcludes != null) {
for (int i = 0; i < fExcludes.length; i++) {
if (edit.equals(fExcludes[i]))
return false;
}
return true;
}
if (fIncludes != null) {
for (int i = 0; i < fIncludes.length; i++) {
if (edit.equals(fIncludes[i]))
return true;
}
return false;
}
return true;
}
private TextEdit[] flatten(TextEdit[] edits) {
List<TextEdit> result = new ArrayList<TextEdit>(5);
for (int i = 0; i < edits.length; i++) {
flatten(result, edits[i]);
}
return result.toArray(new TextEdit[result.size()]);
}
private void flatten(List<TextEdit> result, TextEdit edit) {
result.add(edit);
TextEdit[] children = edit.getChildren();
for (int i = 0; i < children.length; i++) {
flatten(result, children[i]);
}
}
}
}