/**
* Copyright 2014 SAP AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spotter.eclipse.ui.editors;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.part.FileEditorInput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.eclipse.ui.UICoreException;
import org.spotter.eclipse.ui.util.DialogUtils;
/**
* Abstract super class for all Spotter editors. Implements basic functionality
* and leaves open {@link #createPartControl(org.eclipse.swt.widgets.Composite)
* createPartControl (...)} and several template methods to be implemented
* by subclasses.
* <p>
* This implementation does not support the Save As operation, so extending
* classes must override {@link #isSaveAsAllowed()} and {@link #doSaveAs()} to
* change this. But <code>doSave(IProgressMonitor)</code> has basic
* functionality implemented and may be used by subclasses.
* </p>
*
* @author Denis Knoepfle
*
*/
public abstract class AbstractSpotterEditor extends EditorPart {
/**
* Title for general error dialogs.
*/
protected static final String TITLE_ERR_DIALOG = "Editor Error";
/**
* Title for configuration error dialogs.
*/
protected static final String TITLE_CONFIG_ERR_DIALOG = "Configuration Error";
/**
* Error message for failure when saving.
*/
protected static final String ERR_MSG_SAVE = "Could not save file!";
/**
* Error message for failing to initialize editor.
*/
protected static final String ERR_MSG_INIT = "Could not initialize editor with configuration data.";
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSpotterEditor.class);
private static final String ERR_MSG_INPUT_INVALID = "Invalid input: The editor's input is corrupted or the linked file does not exist.";
private static final String MSG_CREATE_NEW = "The required file '%s' for project '%s' could not be found. Do you want to create a new file instead?";
private static final String MSG_REPAIR_CORRUPTED = "The file '%s' of project '%s' is corrupted or not applicable for "
+ "this editor type. Do you want to create a new file instead?\n\nWarning: This may cause "
+ "loss of data! You should manually backup any important data before you continue.";
private static final String ERR_MSG_MAKE_APPLICABLE_FAILED = "It was not possible to create an applicable editor input for this editor!";
private static final String TITLE_INPUT_INVALID = "Input invalid";
private boolean dirtyFlag = false;
/**
* @return The name of the editor
*/
protected abstract String getEditorName();
/**
* @return The id of the editor
*/
public abstract String getEditorId();
/**
* Implementing editors should create a suitable editor input and return it.
*
* @param file
* the resource file
* @return an editor input for this editor
* @throws UICoreException
* when the editor input with the given file could not be
* created
*/
protected abstract AbstractSpotterEditorInput createEditorInput(IFile file) throws UICoreException;
/**
* Implementing editors should check whether the given input is readable and
* can be opened.
*
* @param input
* the input to check
* @return <code>true</code> if the input is readable, otherwise
* <code>false</code>
* @throws Exception
* if an error occurs while parsing the input
*/
protected abstract boolean isInputApplicable(AbstractSpotterEditorInput input) throws Exception;
/**
* Implementing editors are supposed to repair the given input in order to
* make it applicable for the editor.
*
* @param input
* the corrupted input
* @throws UICoreException
* when the editor was unable to make the input applicable again
*/
protected abstract void makeInputApplicable(AbstractSpotterEditorInput input) throws UICoreException;
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
AbstractSpotterEditorInput spotterInput;
if (input instanceof AbstractSpotterEditorInput) {
spotterInput = (AbstractSpotterEditorInput) input;
} else if (input instanceof FileEditorInput) {
FileEditorInput fileInput = (FileEditorInput) input;
try {
spotterInput = createEditorInput(fileInput.getFile());
} catch (UICoreException e) {
spotterInput = null;
}
input = spotterInput;
} else {
throw new PartInitException("Invalid input type: '" + input.getClass().getName() + "' not understood.");
}
if (!checkAndRepairFileInput(spotterInput)) {
throw new PartInitException(ERR_MSG_INPUT_INVALID);
}
setSite(site);
setInput(input);
}
/**
* Save as is not allowed and currently not supported.
*/
@Override
public void doSaveAs() {
}
/**
* Saves the contents of this editor.
* <p>
* This implementation resets the dirty flag including firing an according
* property change event and calling the monitor's <code>done()</code>
* method. Implementing editors should override this method to properly save
* their input but are advised to call this super method at the end to
* properly reflect changes.
* </p>
*
* @param monitor
* the monitor used to give progress feedback
*/
@Override
public void doSave(IProgressMonitor monitor) {
updateDirtyFlag(false);
if (monitor != null) {
monitor.done();
}
}
/**
* Returns the project this editor's input currently is associated with or
* <code>null</code> if none.
*
* @return the associated project or <code>null</code> if none
*/
public IProject getProject() {
IEditorInput editorInput = getEditorInput();
if (editorInput instanceof AbstractSpotterEditorInput) {
return ((AbstractSpotterEditorInput) editorInput).getProject();
}
return null;
}
@Override
public boolean isDirty() {
return dirtyFlag;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
/**
* Marks the editor as dirty. This is a convenience method and has the same
* effect as calling <code>updateDirtyFlag(true)</code>.
*/
public void markDirty() {
updateDirtyFlag(true);
}
/**
* Convenience method to open an editor.
*
* @param editorInput
* the editor input for the editor
* @param editorId
* the id of the editor
*/
public static void openInstance(IEditorInput editorInput, String editorId) {
try {
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
page.openEditor(editorInput, editorId);
} catch (PartInitException e) {
// Usually exceptions of type PartInitException are handled in
// CompatibilityPart.create() after openEditor(...) is called,
// so a NullEditorInput page is shown, but when the exception
// could not be handled, it will be thrown again and returns here
LOGGER.error("Unhandled PartInitException will be rethrown", e);
throw new RuntimeException(e);
}
}
/**
* Updates the dirty flag of this editor and fires an according property
* change event if the flag has changed.
*
* @param dirtyFlag
* dirtyFlag to set
*/
protected void updateDirtyFlag(boolean dirtyFlag) {
boolean fireChange = this.dirtyFlag != dirtyFlag;
this.dirtyFlag = dirtyFlag;
if (fireChange) {
firePropertyChange(PROP_DIRTY);
}
}
/**
* Checks the given input whether it is applicable for this editor and asks
* the user whether the input should be repaired if it was corrupted.
*
* @param input
* the input to check
* @return <code>true</code> when the input was applicable or repaired,
* otherwise <code>false</code>
*/
protected boolean checkAndRepairFileInput(AbstractSpotterEditorInput input) {
if (input == null) {
return false;
}
IFile containedFile = input.getFile();
boolean applicableOrRepaired;
String dialogQuestion;
try {
if (!containedFile.isSynchronized(IResource.DEPTH_ZERO)) {
containedFile.refreshLocal(IResource.DEPTH_ZERO, null);
}
} catch (CoreException e) {
applicableOrRepaired = false;
}
if (!containedFile.exists()) {
dialogQuestion = MSG_CREATE_NEW;
applicableOrRepaired = false;
} else {
dialogQuestion = MSG_REPAIR_CORRUPTED;
try {
applicableOrRepaired = isInputApplicable(input);
} catch (Exception e) {
applicableOrRepaired = false;
}
}
String projectName = containedFile.getProject().getName();
dialogQuestion = String.format(dialogQuestion, containedFile.getLocation(), projectName);
if (!applicableOrRepaired && DialogUtils.openConfirm(TITLE_INPUT_INVALID, dialogQuestion)) {
try {
makeInputApplicable(input);
applicableOrRepaired = true;
} catch (UICoreException e) {
DialogUtils.handleError(ERR_MSG_MAKE_APPLICABLE_FAILED, e);
}
}
return applicableOrRepaired;
}
}