package com.sap.furcas.ide.editor.document;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultLineTracker;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.GapTextStore;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.ISynchronizable;
import com.sap.furcas.ide.editor.CtsActivator;
import com.sap.furcas.ide.editor.imp.FurcasParseController;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.metamodel.FURCAS.textblocks.Version;
import com.sap.furcas.runtime.textblocks.model.TextBlocksModel;
import com.sap.furcas.runtime.textblocks.model.TextBlocksModel.TokenChange;
import com.sap.furcas.runtime.textblocks.modifcation.TbVersionUtil;
import com.sap.furcas.runtime.textblocks.shortprettyprint.ShortPrettyPrinter;
/**
* A document implementation that is responsible for presenting a text blocks
* model as an eclipse document to work on.<p>
*
* This document is synchronized. Background reconcilers can use {@link #getLockObject()} in order
* to synchronize them on the content of this document. This is required as users typing within
* the editor modify this document from within the UI thread.<p>
*
* This documents implements a buffer for document changes, meaning that the underlying
* TextBlocksModel is not instantly updated. Clients have to call {@link #flushUserEditsToTextBlocskModel()}
* to apply the buffered changes to the underlying model. The reverse operation is
* {@link #refreshContentFromTextBlocksModel()}.
*
* @author C5106462
* @author Stephan Erb
*
*/
public class CtsDocument extends AbstractDocument implements ISynchronizable {
private final Object internalLockObject = new Object();
private Object lockObject;
private final TextBlocksModel model;
/**
* True when changes are self-triggered and do not correspond to user actions
* which have to be forwarded to the TextBlocks model.
*/
private boolean inDocumentRefreshMode = false;
private final Collection<DocumentEvent> bufferedChanges = new ArrayList<DocumentEvent>();
public CtsDocument(ModelEditorInput editorInput) {
super();
setTextStore(new GapTextStore());
setLineTracker(new DefaultLineTracker());
model = new TextBlocksModel(getReferenceOrWorkingCopy(editorInput));
model.setUsecache(true);
completeInitialization();
// set textual content
set(model.get(0, model.getLength()));
addDocumentListener(new IDocumentListener() {
@Override
public void documentChanged(DocumentEvent event) {
synchronized (getLockObject()) {
if (!inDocumentRefreshMode) {
bufferedChanges.add(event);
}
}
}
@Override
public void documentAboutToBeChanged(DocumentEvent event) { }
});
}
private TextBlock getReferenceOrWorkingCopy(ModelEditorInput editorInput) {
TextBlock tb = TbVersionUtil.getOtherVersion(editorInput.getRootBlock(), Version.PREVIOUS);
return tb != null ? tb : editorInput.getRootBlock();
}
public TextBlock getRootBlock() {
return model.getRoot();
}
/**
* Sets the content of this document.
*/
public void setRootBlock(TextBlock newRootBlock) {
assert model.getActiveVersion() == newRootBlock.getVersion();
model.setRootTextBlock(newRootBlock);
}
/**
* Has to be called before starting to work on the
* block returned by {@link #getRootBlock()}
*/
public boolean flushUserEditsToTextBlocskModel() {
Collection<DocumentEvent> events = new ArrayList<DocumentEvent>();
synchronized (getLockObject()) {
events.addAll(bufferedChanges);
bufferedChanges.clear();
}
for (DocumentEvent event : events) {
model.replace(event.getOffset(), event.getLength(), event.getText());
}
return events.size() > 0;
}
public void refreshContentFromTextBlocksModelChanges(final ShortPrettyPrinter shortPrettyPrinter) {
synchronized (getLockObject()) {
flushUserEditsToTextBlocskModel();
final ArrayList<TokenChange> tokenChanges = model.doShortPrettyPrintToEditableVersion(shortPrettyPrinter);
try {
inDocumentRefreshMode = true;
for (TokenChange change : tokenChanges) {
replace(change.oldOffset, change.oldLength, change.token.getValue());
}
} catch (BadLocationException e) {
CtsActivator.logger.logError("Failed to refresh document", e);
} finally {
inDocumentRefreshMode = false;
}
}
}
public void resetAfterError() {
synchronized (getLockObject()) {
inDocumentRefreshMode = true;
set(model.get(0, model.getLength()));
bufferedChanges.clear();
inDocumentRefreshMode = false;
}
}
/**
* Returns a lock object for synchronization<p>
*
* It is required that each information exchange from the
* document content to the TextBlocksModel is synchronized, as the
* first is modified from within the UI thread and the latter by the
* {@link FurcasParseController} from within a background thread.
*/
@Override
public Object getLockObject() {
return lockObject == null ? internalLockObject : lockObject;
}
@Override
public void setLockObject(Object lockObject) {
this.lockObject = lockObject;
}
}