package org.xmind.ui.internal.editor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPersistable;
import org.xmind.core.Core;
import org.xmind.core.CoreException;
import org.xmind.core.IDeserializer;
import org.xmind.core.IEntryStreamNormalizer;
import org.xmind.core.IFileEntry;
import org.xmind.core.ISerializer;
import org.xmind.core.IWorkbook;
import org.xmind.core.event.CoreEvent;
import org.xmind.core.event.CoreEventRegister;
import org.xmind.core.event.ICoreEventListener;
import org.xmind.core.event.ICoreEventSource;
import org.xmind.core.event.ICoreEventSource2;
import org.xmind.core.event.ICoreEventSupport;
import org.xmind.core.internal.zip.ArchiveConstants;
import org.xmind.core.io.ByteArrayStorage;
import org.xmind.core.io.DirectoryStorage;
import org.xmind.core.io.IStorage;
import org.xmind.core.marker.IMarker;
import org.xmind.core.marker.IMarkerGroup;
import org.xmind.core.marker.IMarkerSheet;
import org.xmind.core.util.ProgressReporter;
import org.xmind.gef.GEF;
import org.xmind.gef.command.CommandStack;
import org.xmind.gef.command.CommandStackEvent;
import org.xmind.gef.command.ICommandStack;
import org.xmind.gef.ui.editor.Editable;
import org.xmind.gef.ui.editor.IEditingContext;
import org.xmind.ui.internal.MindMapUIPlugin;
import org.xmind.ui.mindmap.IWorkbookRef;
import org.xmind.ui.mindmap.IWorkbookRefListener;
import org.xmind.ui.mindmap.MindMapImageExporter;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.prefs.PrefConstants;
import org.xmind.ui.util.ImageFormat;
/**
* This class implements basic behaviors of {@link IWorkbookRef} by extending
* the {@link Editable} class.
* <h2>Subclassing Notes</h2>
* <ul>
* <li>Each subclass <b>MUST</b> override
* {@link #doLoadWorkbookFromURI(IProgressMonitor, URI) doLoadWorkbookFromURI()}
* , unless it overrides one of {@link #doLoadWorkbook(IProgressMonitor)
* doLoadWorkbook()}, {@link #doOpen(IProgressMonitor) doOpen()} and
* {@link #open(IProgressMonitor) open()} to change the default behavior.</li>
* <li>Each subclass <b>MUST</b> override
* {@link #doSaveWorkbookToURI(IProgressMonitor, IWorkbook, URI)
* doSaveWorkbookToURI()} if it may return <code>true</code> from
* {@link #canSave()}, unless it overrides one of {@link #save(IProgressMonitor)
* save()} and {@link #doSave(IProgressMonitor) doSave()} to change the default
* behavior.</li>
* <li>Each subclass <b>MUST</b> override
* {@link #doImportFrom(IProgressMonitor, IWorkbookRef) doImportFrom()} if it
* may return <code>true</code> from {@link #canImportFrom(IWorkbookRef)
* canImportFrom()}, unless it overrides
* {@link #importFrom(IProgressMonitor, IWorkbookRef) importFrom()} to change
* the default behavior.</li>
* </ul>
*
* @author Frank Shaka
* @since 3.6.50
*/
public abstract class AbstractWorkbookRef extends Editable
implements IWorkbookRef, ISchedulingRule, IPropertyChangeListener {
protected static final int TEMP_SAVING_DELAY = 500;
protected static final String ATT_TEMP_LOCATION = "tempLocation"; //$NON-NLS-1$
private static IEditingContext defaultEditingContext = IEditingContext.NULL;
private IWorkbook workbook = null;
private CoreEventRegister globalEventRegister = null;
private IStorage tempStorage = null;
private boolean shouldLoadFromTempStorage = false;
private ICoreEventListener globalEventListener = new ICoreEventListener() {
public void handleCoreEvent(CoreEvent event) {
handleGlobalEvent(event);
}
};
private WorkbookRefEncryptable encryptable = null;
private Job tempSavingJob = null;
private Object tempSavingLock = new Object();
private List<IWorkbookRefListener> workbookRefListeners = new ArrayList<IWorkbookRefListener>();
protected AbstractWorkbookRef(URI uri, IMemento state) {
super(uri);
setEncryptable(createEncryptable());
setCommandStack(new CommandStack(Math.max(MindMapUIPlugin.getDefault()
.getPreferenceStore().getInt(PrefConstants.UNDO_LIMIT), 1)));
MindMapUIPlugin.getDefault().getPreferenceStore()
.addPropertyChangeListener(this);
if (state != null) {
IStorage savedTempStorage = restoreStateForTempStorage(state);
this.shouldLoadFromTempStorage = savedTempStorage != null;
setTempStorage(savedTempStorage);
}
}
/*
* (non-Javadoc)
* @see org.xmind.ui.mindmap.IWorkbookRef#getSaveWizardId()
*/
@Override
public String getSaveWizardId() {
return null;
}
public IWorkbook getWorkbook() {
return this.workbook;
}
protected void setWorkbook(IWorkbook workbook) {
IWorkbook oldWorkbook = this.workbook;
if (workbook == oldWorkbook)
return;
if (globalEventRegister != null) {
globalEventRegister.unregisterAll();
globalEventRegister = null;
}
if (oldWorkbook != null) {
IMarkerSheet markerSheet = oldWorkbook.getMarkerSheet();
if (markerSheet != null) {
markerSheet.setParentSheet(null);
}
}
this.workbook = workbook;
if (workbook != null) {
IMarkerSheet markerSheet = workbook.getMarkerSheet();
if (markerSheet != null) {
markerSheet.setParentSheet(
MindMapUI.getResourceManager().getSystemMarkerSheet());
}
if (globalEventRegister == null) {
globalEventRegister = new CoreEventRegister(
globalEventListener);
}
registerGlobalEvents(globalEventRegister, workbook);
}
}
private void registerGlobalEvents(CoreEventRegister register,
IWorkbook workbook) {
ICoreEventSupport support = (ICoreEventSupport) workbook
.getAdapter(ICoreEventSupport.class);
if (support != null) {
register.setNextSupport(support);
register.register(Core.MarkerRefAdd);
register.register(Core.PasswordChange);
}
}
private void handleGlobalEvent(CoreEvent event) {
String type = event.getType();
if (Core.MarkerRefAdd.equals(type)) {
handleMarkerAdded((String) event.getTarget());
} else if (Core.PasswordChange.equals(type)) {
scheduleTempSaving();
}
}
private void handleMarkerAdded(String markerId) {
IMarkerGroup recentMarkerGroup = MindMapUI.getResourceManager()
.getRecentMarkerGroup();
IMarkerSheet systemMarkerSheet = MindMapUI.getResourceManager()
.getSystemMarkerSheet();
IMarker systemMarker = systemMarkerSheet.findMarker(markerId);
if (systemMarker != null) {
IMarkerGroup group = systemMarker.getParent();
if (group != null) {
if (group.getParent() != null
&& group.getParent().equals(systemMarkerSheet)) {
recentMarkerGroup.addMarker(systemMarker);
}
}
}
IMarkerSheet userMarkerSheet = MindMapUI.getResourceManager()
.getUserMarkerSheet();
IMarker userMarker = userMarkerSheet.findMarker(markerId);
if (userMarker != null) {
IMarkerGroup group = userMarker.getParent();
if (group != null) {
if (group.getParent() != null
&& group.getParent().equals(userMarkerSheet)) {
recentMarkerGroup.addMarker(userMarker);
}
}
}
}
@Override
public <T> T getAdapter(Class<T> adapter) {
if (IWorkbook.class.equals(adapter))
return adapter.cast(getWorkbook());
if (IPersistable.class.equals(adapter))
return adapter.cast(getPersistable());
if (IEncryptable.class.equals(adapter))
return adapter.cast(encryptable);
return super.getAdapter(adapter);
}
protected void setEncryptable(WorkbookRefEncryptable encryptable) {
this.encryptable = encryptable;
}
protected WorkbookRefEncryptable getEncryptable() {
return encryptable;
}
protected IEntryStreamNormalizer getEncryptionHandler() {
return encryptable != null ? encryptable.getEncryptor()
: IEntryStreamNormalizer.NULL;
}
@Override
protected void doOpen(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
IWorkbook workbook = doLoadWorkbook(subMonitor.newChild(90));
Assert.isTrue(workbook != null);
doSaveWorkbookToTempStorage(subMonitor.newChild(10), workbook);
setWorkbook(workbook);
}
protected IWorkbook doLoadWorkbook(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
if (shouldLoadFromTempStorage) {
try {
return doLoadWorkbookFromTempStorage(monitor, getTempStorage());
} finally {
shouldLoadFromTempStorage = false;
}
}
return doLoadWorkbookFromURI(monitor, getURI());
}
protected IWorkbook doLoadWorkbookFromTempStorage(IProgressMonitor monitor,
IStorage tempStorage)
throws InterruptedException, InvocationTargetException {
try {
IDeserializer deserializer = Core.getWorkbookBuilder()
.newDeserializer();
deserializer.setEntryStreamNormalizer(getEncryptionHandler());
deserializer.setWorkbookStorage(tempStorage);
deserializer.setWorkbookStorageAsInputSource();
deserializer.deserialize(new ProgressReporter(monitor));
return deserializer.getWorkbook();
} catch (IOException e) {
throw new InvocationTargetException(e);
} catch (CoreException e) {
if (e.getType() == Core.ERROR_CANCELLATION)
throw new InterruptedException();
throw new InvocationTargetException(e);
}
}
/**
* Subclasses MUST override this method.
*
* @param monitor
* @param uri
* @return
* @throws InterruptedException
* @throws InvocationTargetException
*/
protected IWorkbook doLoadWorkbookFromURI(IProgressMonitor monitor, URI uri)
throws InterruptedException, InvocationTargetException {
throw new UnsupportedOperationException();
}
@Override
protected void doSave(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
IWorkbook workbook = getWorkbook();
Assert.isTrue(workbook != null);
URI targetURI = getURI();
Assert.isTrue(targetURI != null);
ICoreEventSource workbookAsEventSource;
if (workbook instanceof ICoreEventSource) {
workbookAsEventSource = (ICoreEventSource) workbook;
} else {
workbookAsEventSource = null;
}
if (workbookAsEventSource != null) {
workbookAsEventSource.getCoreEventSupport().dispatch(
workbookAsEventSource, new CoreEvent(workbookAsEventSource,
Core.WorkbookPreSaveOnce, null));
workbookAsEventSource.getCoreEventSupport().dispatch(
workbookAsEventSource, new CoreEvent(workbookAsEventSource,
Core.WorkbookPreSave, null));
}
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
doSaveWorkbookToTempStorage(subMonitor.newChild(20), workbook);
doSaveWorkbookToURI(subMonitor.newChild(80), workbook, targetURI);
if (workbookAsEventSource != null) {
workbookAsEventSource.getCoreEventSupport().dispatch(
workbookAsEventSource, new CoreEvent(workbookAsEventSource,
Core.WorkbookSave, null));
}
}
/// subclasses may override to prevent default behavior or add custom behaviors
protected void doSaveWorkbookToTempStorage(IProgressMonitor monitor,
IWorkbook workbook)
throws InterruptedException, InvocationTargetException {
try {
ISerializer serializer = Core.getWorkbookBuilder().newSerializer();
serializer.setWorkbook(workbook);
serializer.setWorkbookStorageAsOutputTarget();
serializer.setEntryStreamNormalizer(getEncryptionHandler());
serializer.serialize(new ProgressReporter(monitor));
} catch (IOException e) {
throw new InvocationTargetException(e);
} catch (CoreException e) {
if (e.getType() == Core.ERROR_CANCELLATION)
throw new InterruptedException();
if (e.getType() == Core.ERROR_WRONG_PASSWORD) {
if (getEncryptable() != null) {
getEncryptable().reset();
}
}
throw new InvocationTargetException(e);
}
}
/**
* The default implementation does nothing but throws
* <code>UnsupportedOperationException</code>. Subclasses <b>MUST</b>
* override and <b>MUST NOT</b> call
* <code>super.doSaveWorkbookToURI()</code> if <code>true</code> may be
* returned from {@link #canSave()}.
*
* @see #doSave(IProgressMonitor)
* @param monitor
* the progress monitor to use for reporting progress to the
* user. It is the caller's responsibility to call done() on the
* given monitor. Accepts null, indicating that no progress
* should be reported and that the operation cannot be cancelled.
* @param workbook
* the workbook to save (never <code>null</code>)
* @param uri
* the location to save workbook to (never <code>null</code>)
* @exception InvocationTargetException
* if this method must propagate a checked exception, it
* should wrap it inside an
* <code>InvocationTargetException</code>; runtime exceptions
* are automatically wrapped in an
* <code>InvocationTargetException</code> by the calling
* context
* @exception InterruptedException
* if the operation detects a request to cancel, using
* <code>IProgressMonitor.isCanceled()</code>, it should exit
* by throwing <code>InterruptedException</code>
*/
protected void doSaveWorkbookToURI(IProgressMonitor monitor,
IWorkbook workbook, URI uri)
throws InterruptedException, InvocationTargetException {
throw new UnsupportedOperationException();
}
@Override
protected void doClose(IProgressMonitor monitor)
throws InterruptedException, InvocationTargetException {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
stopTempSaving();
IWorkbook workbook = getWorkbook();
if (workbook != null) {
doUnloadWorkbook(subMonitor.newChild(30), workbook);
doClearTempStorageOnClose(subMonitor.newChild(60), workbook);
}
if (encryptable != null) {
encryptable.reset();
}
subMonitor.setWorkRemaining(10);
subMonitor.newChild(10);
setWorkbook(null);
}
/// subclasses may override to prevent default behavior or add custom behaviors
protected void doUnloadWorkbook(IProgressMonitor monitor,
IWorkbook workbook)
throws InterruptedException, InvocationTargetException {
// do nothing, subclasses may override
}
/// subclasses may override to prevent default behavior or add custom behaviors
protected void doClearTempStorageOnClose(IProgressMonitor monitor,
IWorkbook workbook)
throws InterruptedException, InvocationTargetException {
IStorage storage = (IStorage) workbook.getAdapter(IStorage.class);
if (storage != null) {
storage.clear();
}
}
protected IStorage getTempStorage() {
if (tempStorage == null) {
IWorkbook workbook = getWorkbook();
if (workbook != null) {
tempStorage = (IStorage) workbook.getAdapter(IStorage.class);
}
if (tempStorage == null) {
tempStorage = createDefaultTempStorage();
}
}
return this.tempStorage;
}
protected IStorage createDefaultTempStorage() {
return MME.createTempStorage();
}
protected void setTempStorage(IStorage storage) {
this.tempStorage = storage;
}
@Override
public boolean canImportFrom(IWorkbookRef source) {
return false;
}
public void importFrom(IProgressMonitor monitor, final IWorkbookRef source)
throws InterruptedException, InvocationTargetException {
if (!canImportFrom(source))
throw new IllegalArgumentException(
"Can't import from the given workbook ref"); //$NON-NLS-1$
if (source.isInState(CLOSED))
throw new IllegalArgumentException(
"The given workbook ref is closed"); //$NON-NLS-1$
if (!isInState(CLOSED))
throw new IllegalStateException(
"Import operation is not allowed when workbook ref is already open"); //$NON-NLS-1$
if (isInState(OPENING | CLOSING | SAVING))
throw new IllegalStateException(
"Concurrent open/close/save/import operations are not allowed in a workbook ref"); //$NON-NLS-1$
try {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
subMonitor.newChild(5);
addState(SAVING);
try {
doImportFrom(subMonitor.newChild(90), source);
} finally {
subMonitor.newChild(5);
removeState(SAVING);
}
} catch (OperationCanceledException e) {
// interpret cancellation
throw new InterruptedException();
}
}
protected void doImportFrom(IProgressMonitor monitor, IWorkbookRef source)
throws InterruptedException, InvocationTargetException {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see
* org.xmind.ui.mindmap.IWorkbookRef#getPreviewImageData(java.lang.String,
* org.xmind.ui.internal.editor.MindMapPreviewOptions)
*/
@Override
public InputStream getPreviewImageData(String sheetId,
MindMapPreviewOptions options) throws IOException {
if (sheetId == null)
throw new IllegalArgumentException();
IWorkbook workbook = getWorkbook();
/// check if workbook is encrypted,
/// then no preview image should be available
IFileEntry contentEntry = workbook.getManifest()
.getFileEntry(ArchiveConstants.CONTENT_XML);
if (contentEntry != null && contentEntry.getEncryptionData() != null)
return null;
if (workbook != null
&& sheetId.equals(workbook.getPrimarySheet().getId())) {
IFileEntry previewEntry = workbook.getManifest()
.getFileEntry(MindMapImageExporter
.toThumbnailArchivePath(ImageFormat.PNG));
if (previewEntry == null) {
previewEntry = workbook.getManifest()
.getFileEntry(MindMapImageExporter
.toThumbnailArchivePath(ImageFormat.JPEG));
}
if (previewEntry != null) {
return previewEntry.openInputStream();
}
}
return null;
}
protected void saveStateForTempStorage(IMemento state, IWorkbook workbook) {
IStorage storage = (IStorage) workbook.getAdapter(IStorage.class);
if (storage == null)
return;
if (storage instanceof DirectoryStorage) {
String path = ((DirectoryStorage) storage).getFullPath();
if (path != null) {
state.putString(ATT_TEMP_LOCATION, path);
}
} else if (storage instanceof ByteArrayStorage) {
// TODO save byte array storage state
}
}
protected IStorage restoreStateForTempStorage(IMemento state) {
String tempLocation = state.getString(ATT_TEMP_LOCATION);
if (tempLocation != null)
return new DirectoryStorage(new File(tempLocation));
// TODO restore byte array storage state
return null;
}
protected IPersistable getPersistable() {
return new IPersistable() {
@Override
public void saveState(IMemento memento) {
AbstractWorkbookRef.this.saveState(memento);
}
};
}
protected void saveState(IMemento memento) {
if (isInState(CLOSED | CLOSING))
return;
IWorkbook workbook = getWorkbook();
if (workbook != null) {
saveStateForTempStorage(memento, workbook);
}
}
@Override
public boolean isDirty() {
return super.isDirty() || (workbook instanceof ICoreEventSource2
&& ((ICoreEventSource2) workbook)
.hasOnceListeners(Core.WorkbookPreSaveOnce));
}
protected IMindMapPreviewGenerator findPreviewGenerator() {
return getService(IMindMapPreviewGenerator.class);
}
/*
* (non-Javadoc)
* @see org.xmind.gef.ui.editor.Editable#getService(java.lang.Class)
*/
@Override
protected <T> T getService(Class<T> serviceType) {
T service = super.getService(serviceType);
if (service == null) {
service = defaultEditingContext.getAdapter(serviceType);
}
return service;
}
protected WorkbookRefEncryptable createEncryptable() {
return new WorkbookRefEncryptable(this);
}
/*
* (non-Javadoc)
* @see
* org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.
* runtime.jobs.ISchedulingRule)
*/
@Override
public boolean contains(ISchedulingRule rule) {
return this.equals(rule);
}
/*
* (non-Javadoc)
* @see
* org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.
* core.runtime.jobs.ISchedulingRule)
*/
@Override
public boolean isConflicting(ISchedulingRule rule) {
return this.equals(rule);
}
/*
* (non-Javadoc)
* @see
* org.xmind.gef.ui.editor.Editable#doHandleCommandStackChange(org.xmind.gef
* .command.CommandStackEvent)
*/
@Override
protected void doHandleCommandStackChange(CommandStackEvent event) {
super.doHandleCommandStackChange(event);
if ((event.getStatus() & GEF.CS_POST_MASK) != 0) {
scheduleTempSaving();
}
}
protected void scheduleTempSaving() {
final IWorkbook theWorkbook = getWorkbook();
if (theWorkbook == null)
return;
final Object subFamily = this;
Job job = new Job("Saving Workbook To Temporary Storage") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor != null && monitor.isCanceled())
return Status.CANCEL_STATUS;
try {
/// use null progress to indicate non-stoppable task
doSaveWorkbookToTempStorage(null, theWorkbook);
} catch (InterruptedException e) {
/// canceled, ignore exception
} catch (InvocationTargetException e) {
return new Status(IStatus.WARNING,
MindMapUIPlugin.PLUGIN_ID,
"Failed to save workbook to temp location", e); //$NON-NLS-1$
}
return Status.OK_STATUS;
}
@Override
public boolean belongsTo(Object family) {
if (subFamily.equals(family))
return true;
Object jobFamily = getJobFamily();
return jobFamily != null && jobFamily.equals(family);
}
};
job.setSystem(true);
job.setRule(this);
synchronized (tempSavingLock) {
if (tempSavingJob != null) {
tempSavingJob.cancel();
tempSavingJob = null;
}
tempSavingJob = job;
}
scheduleTempSavingJob(job);
}
/**
* @param job
*/
protected void scheduleTempSavingJob(Job job) {
job.schedule(TEMP_SAVING_DELAY);
}
protected Object getJobFamily() {
return AbstractWorkbookRef.class;
}
protected void stopTempSaving() {
synchronized (tempSavingLock) {
if (tempSavingJob != null) {
tempSavingJob.cancel();
tempSavingJob = null;
}
}
Job.getJobManager().cancel(this);
}
public static void setDefaultEditingContext(IEditingContext context) {
defaultEditingContext = (context == null) ? IEditingContext.NULL
: context;
}
public void addWorkbookRefListener(
IWorkbookRefListener workbookRefListener) {
workbookRefListeners.add(workbookRefListener);
}
public void removeWorkbookRefListener(
IWorkbookRefListener workbookRefListener) {
workbookRefListeners.remove(workbookRefListener);
}
protected void fileChanged(String title, String message, String[] buttons) {
for (IWorkbookRefListener listener : new ArrayList<IWorkbookRefListener>(
workbookRefListeners)) {
listener.fileChanged(title, message, buttons);
}
}
protected void fileRemoved(String title, String message, String[] buttons,
boolean forceQuit) {
for (IWorkbookRefListener listener : new ArrayList<IWorkbookRefListener>(
workbookRefListeners)) {
listener.fileRemoved(title, message, buttons, forceQuit);
}
}
@Override
public boolean activateNotifier() {
return false;
}
public void propertyChange(PropertyChangeEvent event) {
ICommandStack commandStack = getCommandStack();
if (commandStack != null) {
if (PrefConstants.UNDO_LIMIT.equals(event.getProperty())) {
int num = 0;
try {
num = Integer.parseInt(event.getNewValue().toString());
} catch (Exception e) {
}
commandStack.setUndoLimit(Math.max(num, 1));
}
}
}
}