/*******************************************************************************
* Copyright (c) 2011, 2015 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
* William Chen (Wind River)- [345552] Edit the remote files with a proper editor
*******************************************************************************/
package org.eclipse.tcf.te.tcf.filesystem.ui.internal.compare;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.ICompareContainer;
import org.eclipse.compare.IContentChangeListener;
import org.eclipse.compare.IContentChangeNotifier;
import org.eclipse.compare.ISharedDocumentAdapter;
import org.eclipse.compare.SharedDocumentAdapter;
import org.eclipse.compare.contentmergeviewer.ContentMergeViewer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.graphics.Image;
import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.runtime.IFSTreeNode;
import org.eclipse.tcf.te.tcf.filesystem.ui.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener;
import org.eclipse.tcf.te.tcf.filesystem.ui.internal.operations.UiExecutor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.ISaveablesLifecycleListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartConstants;
import org.eclipse.ui.Saveable;
import org.eclipse.ui.SaveablesLifecycleEvent;
import org.eclipse.ui.texteditor.IDocumentProvider;
/**
* A <code>LocalFileSaveable</code> is used as the saveable object that provides
* the save behavior for the merge editor input(<code>MergeEditorInput</code>).
*/
public class LocalFileSaveable extends Saveable implements
IPropertyChangeListener, ISharedDocumentAdapterListener,
IContentChangeListener {
// The property listener list.
private ListenerList listeners = new ListenerList(ListenerList.IDENTITY);
// The merge input that provides the left and the right compared elements.
private final MergeInput input;
// The input of the editor that provides the comparing view.
private final MergeEditorInput editorInput;
// If the current document of the file is being saved.
private boolean saving;
// The local file element.
private LocalTypedElement fileElement;
// The document provided by the local file.
private IDocument document;
// If the current document has been connected.
private boolean connected;
/**
* Create the file-based saveable comparison.
*
* @param input
* the compare input to be save
* @param editorInput
* the editor input containing the comparison
* @param fileElement
* the file element that handles the saving and change
* notification
*/
public LocalFileSaveable(MergeInput input, MergeEditorInput editorInput, LocalTypedElement fileElement) {
Assert.isNotNull(input);
this.input = input;
this.editorInput = editorInput;
this.fileElement = fileElement;
this.fileElement.addContentChangeListener(this);
this.fileElement.setDocumentListener(this);
}
/**
* Dispose of the saveable.
*/
public void dispose() {
fileElement.removeContentChangeListener(this);
fileElement.discardBuffer();
fileElement.setDocumentListener(null);
}
/**
* Performs the save.
*
* @param monitor The progress monitor.
*
* @throws CoreException If the save operation fails.
*/
protected void performSave(IProgressMonitor monitor) throws CoreException {
try {
saving = true;
monitor.beginTask(null, 100);
// First, we need to flush the viewers so the changes get buffered
// in the input
flushViewers(monitor);
// Then we tell the input to commit its changes
// Only the left is ever saveable
if (fileElement.isDirty()) {
if (fileElement.isConnected()) {
fileElement.store2Document(monitor);
} else {
fileElement.store2Cache(monitor);
}
}
} finally {
// Make sure we fire a change for the compare input to update the
// viewers
fireInputChange();
setDirty(false);
saving = false;
monitor.done();
//Trigger upload action
IFSTreeNode node = fileElement.getFSTreeNode();
UiExecutor.execute(node.operationUploadContent(node.getCacheFile()));
}
}
/**
* Flush the contents of any viewers into the compare input.
*
* @param monitor
* a progress monitor
* @throws CoreException
*/
protected void flushViewers(IProgressMonitor monitor) throws CoreException {
editorInput.saveChanges(monitor);
}
/**
* Fire an input change for the compare input after it has been saved.
*/
protected void fireInputChange() {
editorInput.getCompareResult().fireInputChanged();
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#isDirty()
*/
@Override
public boolean isDirty() {
return editorInput.isSaveNeeded();
}
/**
* Marks the editor input dirty.
*
* @param dirty <code>True</code> if the editor is dirty, <code>false</code> if not.
*/
protected void setDirty(boolean dirty) {
if (isDirty() != dirty) {
editorInput.setDirty(dirty);
firePropertyChange(IWorkbenchPartConstants.PROP_DIRTY);
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#getName()
*/
@Override
public String getName() {
// Return the name of the file element as held in the compare input
if (fileElement.equals(input.getLeft())) {
return input.getLeft().getName();
}
if (fileElement.equals(input.getRight())) {
return input.getRight().getName();
}
// Fallback call returning name of the main non-null element of the input
//
// see org.eclipse.team.internal.ui.mapping.AbstractCompareInput#getMainElement()
return input.getName();
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#getToolTipText()
*/
@Override
public String getToolTipText() {
return editorInput.getToolTipText();
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#getImageDescriptor()
*/
@Override
public ImageDescriptor getImageDescriptor() {
Image image = input.getImage();
if (image != null)
return ImageDescriptor.createFromImage(image);
return null;
}
/* (non-Javadoc)
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getProperty();
if (CompareEditorInput.DIRTY_STATE.equals(propertyName)) {
boolean changed = false;
Object newValue = e.getNewValue();
if (newValue instanceof Boolean)
changed = ((Boolean) newValue).booleanValue();
ContentMergeViewer cmv = (ContentMergeViewer) e.getSource();
if (fileElement.equals(input.getLeft())) {
if (changed && cmv.internalIsLeftDirty())
setDirty(changed);
else if (!changed && !cmv.internalIsLeftDirty()) {
setDirty(changed);
}
}
if (fileElement.equals(input.getRight())) {
if (changed && cmv.internalIsRightDirty())
setDirty(changed);
else if (!changed && !cmv.internalIsRightDirty()) {
setDirty(changed);
}
}
}
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#hashCode()
*/
@Override
public int hashCode() {
if (document != null) {
return document.hashCode();
}
return input.hashCode();
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Saveable))
return false;
if (document != null) {
Object otherDocument = ((Saveable) obj).getAdapter(IDocument.class);
if (otherDocument == null)
return false;
return document.equals(otherDocument);
}
if (obj instanceof LocalFileSaveable) {
LocalFileSaveable saveable = (LocalFileSaveable) obj;
return saveable.input.equals(input);
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.ui.Saveable#getAdapter(java.lang.Class)
*/
@Override
public Object getAdapter(Class adapter) {
if (adapter == IDocument.class) {
if (document != null)
return document;
if (fileElement.isConnected()) {
ISharedDocumentAdapter sda = (ISharedDocumentAdapter) fileElement
.getAdapter(ISharedDocumentAdapter.class);
if (sda != null) {
IEditorInput input = sda.getDocumentKey(fileElement);
if (input != null) {
IDocumentProvider provider = SharedDocumentAdapter
.getDocumentProvider(input);
if (provider != null)
return provider.getDocument(input);
}
}
}
}
if (adapter == IEditorInput.class) {
return fileElement.getEditorInput();
}
return super.getAdapter(adapter);
}
/**
* Get an ISaveablesLifecycleListener from the specified workbench
* part.
* @param part The workbench part.
* @return The listener.
*/
private ISaveablesLifecycleListener getSaveablesLifecycleListener(
IWorkbenchPart part) {
if (part instanceof ISaveablesLifecycleListener)
return (ISaveablesLifecycleListener) part;
Object adapted = part.getAdapter(ISaveablesLifecycleListener.class);
if (adapted instanceof ISaveablesLifecycleListener)
return (ISaveablesLifecycleListener) adapted;
adapted = Platform.getAdapterManager().loadAdapter(part,
ISaveablesLifecycleListener.class.getName());
if (adapted instanceof ISaveablesLifecycleListener)
return (ISaveablesLifecycleListener) adapted;
return (ISaveablesLifecycleListener) part.getSite().getService(
ISaveablesLifecycleListener.class);
}
/**
* When the document of the local file is connected, register this saveable.
*/
private void registerSaveable() {
ICompareContainer container = editorInput.getContainer();
IWorkbenchPart part = container.getWorkbenchPart();
if (part != null) {
ISaveablesLifecycleListener lifecycleListener = getSaveablesLifecycleListener(part);
// Remove this saveable from the lifecycle listener
lifecycleListener.handleLifecycleEvent(new SaveablesLifecycleEvent(
part, SaveablesLifecycleEvent.POST_CLOSE,
new Saveable[] { this }, false));
// Now fix the hashing so it uses the fConnected fDocument
IDocument document = (IDocument) getAdapter(IDocument.class);
if (document != null) {
this.document = document;
}
// Finally, add this saveable back to the listener
lifecycleListener.handleLifecycleEvent(new SaveablesLifecycleEvent(
part, SaveablesLifecycleEvent.POST_OPEN,
new Saveable[] { this }, false));
}
}
/*
* (non-Javadoc)
* @see org.eclipse.tcf.te.tcf.filesystem.internal.compare.EditableSharedDocumentAdapter.ISharedDocumentAdapterListener#handleDocumentConnected()
*/
@Override
public void handleDocumentConnected() {
if (connected)
return;
connected = true;
registerSaveable();
fileElement.setDocumentListener(null);
}
@Override
public void handleDocumentDeleted() {
// Ignore
}
@Override
public void handleDocumentDisconnected() {
// Ignore
}
@Override
public void handleDocumentFlushed() {
// Ignore
}
@Override
public void handleDocumentSaved() {
// Ignore
}
/*
* (non-Javadoc)
* @see org.eclipse.compare.IContentChangeListener#contentChanged(org.eclipse.compare.IContentChangeNotifier)
*/
@Override
public void contentChanged(IContentChangeNotifier source) {
if (!saving) {
try {
performSave(new NullProgressMonitor());
} catch (CoreException e) {
e.printStackTrace();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void doSave(IProgressMonitor monitor) throws CoreException {
if (isDirty()) {
performSave(monitor);
setDirty(false);
}
}
/**
* Revert any changes in the buffer back to the last saved action.
*
* @param monitor
* a progress monitor on <code>null</code> if progress feedback
* is not required
*/
public void doRevert(IProgressMonitor monitor) {
if (!isDirty())
return;
fileElement.discardBuffer();
setDirty(false);
}
/**
* Add a property change listener. Adding a listener that is already
* registered has no effect.
*
* @param listener
* the listener
*/
public void addPropertyListener(IPropertyListener listener) {
listeners.add(listener);
}
/**
* Remove a property change listener. Removing a listener that is not
* registered has no effect.
*
* @param listener
* the listener
*/
public void removePropertyListener(IPropertyListener listener) {
listeners.remove(listener);
}
/**
* Fire a property change event for this buffer.
*
* @param property
* the property that changed
*/
protected void firePropertyChange(final int property) {
Object[] allListeners = listeners.getListeners();
for (int i = 0; i < allListeners.length; i++) {
final Object object = allListeners[i];
SafeRunner.run(new SafeRunnable() {
@Override
public void handleException(Throwable e) {
// Ignore exception
}
@Override
public void run() throws Exception {
((IPropertyListener) object).propertyChanged(
LocalFileSaveable.this, property);
}
});
}
}
}