blob: b6b9bfe8fb111381713e3fd3be0a9304fecf6879 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2016 1C-Soft LLC 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:
* Vladimir Piskarev (1C) - initial API and implementation
*******************************************************************************/
package org.eclipse.handly.model.impl;
import static org.eclipse.handly.context.Contexts.EMPTY_CONTEXT;
import static org.eclipse.handly.context.Contexts.of;
import static org.eclipse.handly.context.Contexts.with;
import static org.eclipse.handly.model.IElementDeltaConstants.F_WORKING_COPY;
import static org.eclipse.handly.util.ToStringOptions.FORMAT_STYLE;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.MEDIUM;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.handly.buffer.IBuffer;
import org.eclipse.handly.context.IContext;
import org.eclipse.handly.internal.Activator;
import org.eclipse.handly.model.IElement;
import org.eclipse.handly.snapshot.ISnapshot;
import org.eclipse.handly.snapshot.ISnapshotProvider;
import org.eclipse.handly.snapshot.NonExpiringSnapshot;
import org.eclipse.handly.util.Property;
import org.eclipse.handly.util.TextRange;
/**
* Common superclass for source files.
* <p>
* This abstract implementation doesn't depend on a specific representation for
* the underlying file, like <code>IFile</code> or <code>IFileStore</code>.
* The binding has to be made in a subclass.
* </p>
* <p>
* If a notification manager is registered in the model context, this class
* will take advantage of it to send out working copy notifications. See
* {@link #hWorkingCopyModeChanged()} and {@link NotifyingReconcileOperation}.
* </p>
*
* @see WorkspaceSourceFile
*/
public abstract class SourceFile
extends SourceElement
implements ISourceFileImpl
{
/**
* Constructs a handle for a source file with the given parent element and
* the given name.
*
* @param parent the parent of the element,
* or <code>null</code> if the element has no parent
* @param name the name of the element, or <code>null</code>
* if the element has no name
*/
public SourceFile(Element parent, String name)
{
super(parent, name);
}
@Override
public final IBuffer hBuffer(IContext context, IProgressMonitor monitor)
throws CoreException
{
if (monitor == null)
monitor = new NullProgressMonitor();
monitor.beginTask("", 100); //$NON-NLS-1$
try
{
WorkingCopyInfo info = hAcquireExistingWorkingCopy(
new SubProgressMonitor(monitor, 10));
if (info == null)
{
return hFileBuffer(context, new SubProgressMonitor(monitor,
90));
}
else
{
try
{
IBuffer buffer = info.getBuffer();
buffer.addRef();
return buffer;
}
finally
{
hReleaseWorkingCopy();
}
}
}
finally
{
monitor.done();
}
}
/**
* If this source file is not already in working copy mode, switches it
* into a working copy, associates it with a working copy buffer via a new
* working copy info, and acquires an independent ownership of the working
* copy (and, hence, of the working copy buffer). Performs atomically.
* <p>
* In working copy mode the source file's structure and properties
* shall no longer correspond to the underlying resource contents
* and must no longer be updated by a resource delta processor.
* Instead, the source file's structure and properties can be explicitly
* {@link #hReconcile(IContext, IProgressMonitor) reconciled} with the
* current contents of the working copy buffer.
* </p>
* <p>
* This method supports the following options, which may be specified
* in the given context:
* </p>
* <ul>
* <li>
* {@link #WORKING_COPY_BUFFER} - Specifies the working copy buffer.
* If not set, a default buffer is associated with the working copy.
* </li>
* <li>
* {@link #WORKING_COPY_INFO_FACTORY} - Specifies the working copy info factory.
* If not set, a default factory is used for obtaining a new working copy info.
* </li>
* </ul>
* <p>
* If the source file was already in working copy mode, this method acquires
* a new independent ownership of the working copy by incrementing an internal
* counter and returns the info associated with the working copy; the given
* context is ignored. The returned info is owned by the working copy and
* must not be explicitly disposed by the client.
* </p>
* <p>
* Each successful call to this method must ultimately be followed
* by exactly one call to <code>hReleaseWorkingCopy</code>.
* </p>
*
* @param context the operation context (not <code>null</code>)
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @return the working copy info previously associated with
* this source file, or <code>null</code> if there was no
* working copy info for this source file
* @throws CoreException if the working copy could not be created successfully
* @throws OperationCanceledException if this method is canceled
* @see #hReleaseWorkingCopy()
* @see #hAcquireExistingWorkingCopy(IProgressMonitor)
*/
public final WorkingCopyInfo hBecomeWorkingCopy(IContext context,
IProgressMonitor monitor) throws CoreException
{
if (context == null)
throw new IllegalArgumentException();
IBuffer buffer = context.get(WORKING_COPY_BUFFER);
if (buffer == null)
{
try (IBuffer defaultBuffer = hFileBuffer(context, monitor))
{
return hBecomeWorkingCopy(with(of(WORKING_COPY_BUFFER,
defaultBuffer), context), monitor);
}
}
WorkingCopyProvider provider = new WorkingCopyProvider()
{
@Override
protected WorkingCopyInfo doAcquireWorkingCopy()
{
return hElementManager().putWorkingCopyInfoIfAbsent(
SourceFile.this, buffer, context.get(
WORKING_COPY_INFO_FACTORY));
}
@Override
protected boolean isCanceled()
{
if (monitor == null)
return false;
return monitor.isCanceled();
}
};
WorkingCopyInfo oldInfo = provider.acquireWorkingCopy();
if (oldInfo == null)
{
boolean success = false;
try
{
WorkingCopyInfo newInfo =
hElementManager().peekAtWorkingCopyInfo(this);
newInfo.initTask.execute(context, monitor);
success = true;
}
finally
{
if (!success)
hReleaseWorkingCopy();
}
}
return oldInfo;
}
/**
* Specifies the working copy buffer.
* @see #hBecomeWorkingCopy(IContext, IProgressMonitor)
*/
public static final Property<IBuffer> WORKING_COPY_BUFFER = Property.get(
SourceFile.class.getName() + ".workingCopyBuffer", IBuffer.class); //$NON-NLS-1$
/**
* Specifies the working copy info factory.
* @see #hBecomeWorkingCopy(IContext, IProgressMonitor)
*/
public static final Property<WorkingCopyInfo.Factory> WORKING_COPY_INFO_FACTORY =
Property.get(SourceFile.class.getName() + ".workingCopyInfoFactory", //$NON-NLS-1$
WorkingCopyInfo.Factory.class);
/**
* If this source file is in working copy mode, acquires a new independent
* ownership of the working copy by incrementing an internal counter and
* returns the info associated with the working copy. The returned info is
* owned by the working copy and must not be explicitly disposed by the client.
* Returns <code>null</code> if this source file is not a working copy.
* Performs atomically.
* <p>
* Each successful call to this method that did not return <code>null</code>
* must ultimately be followed by exactly one call to <code>hReleaseWorkingCopy</code>.
* </p>
*
* @return the working copy info for this source file,
* or <code>null</code> if this source file is not a working copy
* @see #hReleaseWorkingCopy()
* @see #hBecomeWorkingCopy(IContext, IProgressMonitor)
*/
public final WorkingCopyInfo hAcquireExistingWorkingCopy(
IProgressMonitor monitor)
{
WorkingCopyProvider provider = new WorkingCopyProvider()
{
@Override
protected WorkingCopyInfo doAcquireWorkingCopy()
{
return hElementManager().getWorkingCopyInfo(SourceFile.this);
}
@Override
protected boolean isCanceled()
{
if (monitor == null)
return false;
return monitor.isCanceled();
}
};
return provider.acquireWorkingCopy();
}
/**
* Relinquishes an independent ownership of the working copy by decrementing
* an internal counter. If there are no remaining owners of the working copy,
* switches this source file from working copy mode back to its original mode
* and releases the working copy buffer. Performs atomically.
* <p>
* Each independent ownership of the working copy must ultimately end
* with exactly one call to this method. Clients that do not own the
* working copy must not call this method.
* </p>
*
* @return <code>true</code> if this source file was switched from
* working copy mode back to its original mode, <code>false</code>
* otherwise
*/
public final boolean hReleaseWorkingCopy()
{
WorkingCopyInfo info = hElementManager().releaseWorkingCopyInfo(this);
if (info == null)
throw new IllegalStateException("Not a working copy: " + hToString( //$NON-NLS-1$
of(FORMAT_STYLE, MEDIUM)));
if (info.isDisposed() && info.created)
{
hWorkingCopyModeChanged();
return true;
}
return false;
}
/**
* If this source file is in working copy mode, returns the working copy info
* without acquiring an independent ownership of the working copy. The
* returned info is owned by the working copy and must not be explicitly
* disposed by the client. Returns <code>null</code> if this source file
* is not a working copy.
* <p>
* Note that if this method is invoked by a client that does not own the
* working copy, the returned info may get disposed from another thread.
* </p>
*
* @return the working copy info for this source file,
* or <code>null</code> if this source file is not a working copy
* @see #hAcquireExistingWorkingCopy(IProgressMonitor)
* @see #hBecomeWorkingCopy(IContext, IProgressMonitor)
*/
public final WorkingCopyInfo hWorkingCopyInfo()
{
WorkingCopyInfo info = hElementManager().peekAtWorkingCopyInfo(this);
if (info == null)
return null;
if (info.created)
return info;
// special case: wc creation is in progress on the current thread
if (this.equals(CURRENTLY_RECONCILED.get()))
return info;
return null;
}
@Override
public final boolean hIsWorkingCopy()
{
return hWorkingCopyInfo() != null;
}
@Override
public final boolean hNeedsReconciling()
{
WorkingCopyInfo info = hAcquireExistingWorkingCopy(null);
if (info == null)
return false;
else
{
try
{
return info.needsReconciling();
}
finally
{
hReleaseWorkingCopy();
}
}
}
@Override
public final void hReconcile(IContext context, IProgressMonitor monitor)
throws CoreException
{
if (monitor == null)
monitor = new NullProgressMonitor();
monitor.beginTask("", 100); //$NON-NLS-1$
try
{
WorkingCopyInfo info = hAcquireExistingWorkingCopy(
new SubProgressMonitor(monitor, 10));
if (info == null)
return; // not a working copy
else
{
try
{
info.reconcile(context, new SubProgressMonitor(monitor,
90));
}
finally
{
hReleaseWorkingCopy();
}
}
}
finally
{
monitor.done();
}
}
/**
* Returns a reconcile operation for this source file.
* <p>
* This implementation returns a new instance of {@link
* NotifyingReconcileOperation} if there is a notification manager
* registered in the model context. Otherwise, a new instance of
* {@link ReconcileOperation} is returned.
* </p>
* <p>
* Clients are not intended to invoke this method, but may override it.
* </p>
*
* @return a reconcile operation for this source file (not <code>null</code>)
*/
protected ReconcileOperation hReconcileOperation()
{
if (hModel().getModelContext().get(INotificationManager.class) != null)
return new NotifyingReconcileOperation();
return new ReconcileOperation();
}
/**
* Returns a context to be associated with a new working copy of this
* source file. The given operation context is propagated from the
* {@link #hBecomeWorkingCopy} method.
* <p>
* This implementation returns an empty context. Subclasses may override.
* </p>
*
* @param context the operation context (never <code>null</code>)
* @return the working copy context (not <code>null</code>)
*/
protected IContext hWorkingCopyContext(IContext context)
{
return EMPTY_CONTEXT;
}
/**
* Notifies about a working copy mode change: either the source file
* became a working copy or reverted back from the working copy mode.
* <p>
* This implementation does nothing if no notification manager is
* registered in the model context. Otherwise, it sends out a delta
* notification indicating the nature of the working copy mode change.
* </p>
* <p>
* Clients are not intended to invoke this method, but may override it.
* </p>
*/
protected void hWorkingCopyModeChanged()
{
INotificationManager notificationManager =
hModel().getModelContext().get(INotificationManager.class);
if (notificationManager == null)
return;
ElementDelta.Factory deltaFactory = hModel().getModelContext().get(
ElementDelta.Factory.class);
if (deltaFactory == null)
deltaFactory = element -> new ElementDelta(element);
ElementDelta rootDelta = deltaFactory.newDelta(hRoot());
ElementDelta.Builder builder = new ElementDelta.Builder(rootDelta);
if (hFileExists())
builder.changed(this, F_WORKING_COPY);
else if (hIsWorkingCopy())
builder.added(this, F_WORKING_COPY);
else
builder.removed(this, F_WORKING_COPY);
notificationManager.fireElementChangeEvent(new ElementChangeEvent(
ElementChangeEvent.POST_CHANGE, builder.getDelta()));
}
@Override
protected void hValidateExistence(IContext context) throws CoreException
{
if (!hIsWorkingCopy())
{
if (!hFileExists())
throw hDoesNotExistException();
}
}
@Override
void hBuildStructure0(IContext context, IProgressMonitor monitor)
throws CoreException
{
if (!context.containsKey(SOURCE_CONTENTS) && !context.containsKey(
SOURCE_AST))
{
if (hIsWorkingCopy())
throw new AssertionError();
// NOTE: source files that are not working copies must reflect
// the structure of the underlying file rather than the buffer
NonExpiringSnapshot snapshot;
try (ISnapshotProvider provider = hFileSnapshotProvider())
{
try
{
snapshot = new NonExpiringSnapshot(provider);
}
catch (IllegalStateException e)
{
Throwable cause = e.getCause();
if (cause instanceof CoreException)
throw (CoreException)cause;
throw new CoreException(Activator.createErrorStatus(
e.getMessage(), e));
}
}
context = with(of(SOURCE_CONTENTS, snapshot.getContents()), of(
SOURCE_SNAPSHOT, snapshot.getWrappedSnapshot()), context);
}
super.hBuildStructure0(context, monitor);
Map<IElement, Object> newElements = context.get(NEW_ELEMENTS);
Object body = newElements.get(this);
if (body instanceof SourceElementBody)
{
SourceElementBody thisBody = (SourceElementBody)body;
String source = context.get(SOURCE_CONTENTS);
if (source != null)
thisBody.setFullRange(new TextRange(0, source.length()));
ISnapshot snapshot = context.get(SOURCE_SNAPSHOT);
if (snapshot != null)
setSnapshot(thisBody, snapshot, newElements);
}
}
/**
* Returns whether the underlying file exists.
*
* @return <code>true</code> if the underlying file exists,
* and <code>false</code> otherwise
*/
protected abstract boolean hFileExists();
/**
* Returns a snapshot provider for the underlying file's stored contents.
* <p>
* The client takes (potentially shared) ownership of the returned provider
* and is responsible for releasing it. The provider will be disposed
* only after it is released by every owner. The provider must not
* be accessed by clients that don't own it.
* </p>
*
* @return a snapshot provider for the underlying file's stored contents
* (not <code>null</code>)
* @see ISnapshotProvider
*/
protected abstract ISnapshotProvider hFileSnapshotProvider();
/**
* Returns the buffer opened for the underlying file of this source file.
* Note that buffers may be shared by multiple clients, so the returned buffer
* may have unsaved changes if it has been modified by another client.
* <p>
* The client takes (potentially shared) ownership of the returned buffer
* and is responsible for releasing it. The buffer will be disposed
* only after it is released by every owner. The buffer must not
* be accessed by clients that don't own it.
* </p>
* <p>
* Implementations are encouraged to support the following standard options,
* which may be specified in the given context:
* </p>
* <ul>
* <li>
* {@link org.eclipse.handly.model.Elements#CREATE_BUFFER CREATE_BUFFER} -
* Indicates whether a new buffer should be created if none already exists.
* </li>
* </ul>
*
* @param context the operation context (never <code>null</code>)
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @return the buffer opened for the underlying file of this source file,
* or <code>null</code> if <code>CREATE_BUFFER == false</code> and
* there is currently no buffer opened for that file
* @throws CoreException if the buffer could not be opened successfully
* @throws OperationCanceledException if this method is canceled
* @see IBuffer
*/
protected abstract IBuffer hFileBuffer(IContext context,
IProgressMonitor monitor) throws CoreException;
/**
* Creates and initializes bodies for this element and for each
* of its descendant elements according to options specified in the
* given context. Uses the {@link #NEW_ELEMENTS} map in the given context
* to associate the created bodies with their respective elements.
* <p>
* The following context options influence how the structure is built and,
* if simultaneously present, must be mutually consistent:
* </p>
* <ul>
* <li>
* {@link #SOURCE_AST} - Specifies the AST to use when building the structure.
* The AST is safe to read in the dynamic context of this method call, but
* must not be modified.
* </li>
* <li>
* {@link #SOURCE_CONTENTS} - Specifies the source string to use when
* building the structure.
* </li>
* </ul>
* <p>
* At least one of <code>SOURCE_AST</code> or <code>SOURCE_CONTENTS</code>
* will have a non-null value in the given context.
* </p>
* <p>
* The given context may provide additional data that this method can use,
* including the following:
* </p>
* <ul>
* <li>
* {@link #SOURCE_SNAPSHOT} - Specifies the source snapshot from which
* <code>SOURCE_AST</code> was created or <code>SOURCE_CONTENTS</code>
* was obtained. The snapshot may expire.
* </li>
* </ul>
*
* @param context the operation context (never <code>null</code>)
* @param monitor a progress monitor (never <code>null</code>)
* @throws CoreException if this method fails
* @throws OperationCanceledException if this method is canceled
*/
@Override
protected abstract void hBuildStructure(IContext context,
IProgressMonitor monitor) throws CoreException;
/**
* Specifies the source AST.
* @see #hBuildStructure(IContext, IProgressMonitor)
*/
protected static final Property<Object> SOURCE_AST = Property.get(
SourceFile.class.getName() + ".sourceAst", Object.class); //$NON-NLS-1$
/**
* Specifies the source string.
* @see #hBuildStructure(IContext, IProgressMonitor)
*/
protected static final Property<String> SOURCE_CONTENTS = Property.get(
SourceFile.class.getName() + ".sourceContents", String.class); //$NON-NLS-1$
/**
* Specifies the source snapshot.
* @see #hBuildStructure(IContext, IProgressMonitor)
*/
protected static final Property<ISnapshot> SOURCE_SNAPSHOT = Property.get(
SourceFile.class.getName() + ".sourceSnapshot", ISnapshot.class); //$NON-NLS-1$
@Override
protected void hGenerateAncestorBodies(IContext context,
IProgressMonitor monitor) throws CoreException
{
if (hIsWorkingCopy())
return; // don't open ancestors for a working copy
super.hGenerateAncestorBodies(context, monitor);
}
@Override
protected boolean hClose(boolean external)
{
if (hIsWorkingCopy())
return false; // a working copy cannot be removed
return super.hClose(external);
}
@Override
protected void hToStringName(StringBuilder builder, IContext context)
{
if (hIsWorkingCopy())
builder.append("[Working copy] "); //$NON-NLS-1$
super.hToStringName(builder, context);
}
private static void setSnapshot(SourceElementBody body, ISnapshot snapshot,
Map<IElement, Object> newElements)
{
body.setSnapshot(snapshot);
for (IElement child : body.getChildren())
{
Object childBody = newElements.get(child);
if (childBody instanceof SourceElementBody)
{
setSnapshot((SourceElementBody)childBody, snapshot,
newElements);
}
}
}
/**
* Indicates whether the structure should be rebuilt when reconciling
* is forced. Default value: <code>false</code>.
* @see ReconcileOperation#reconcile(IContext, IProgressMonitor)
*/
protected static final Property<Boolean> REBUILD_STRUCTURE_IF_FORCED =
Property.get(SourceFile.class.getName() + ".rebuildStructureIfForced", //$NON-NLS-1$
Boolean.class).withDefault(false);
/**
* Indicates whether reconciling was forced, i.e. the working copy buffer
* has not been modified since the last time it was reconciled.
* Default value: <code>false</code>.
* @see ReconcileOperation#reconcile(IContext, IProgressMonitor)
*/
static final Property<Boolean> RECONCILING_FORCED = Property.get(
SourceFile.class.getName() + ".reconcilingForced", //$NON-NLS-1$
Boolean.class).withDefault(false);
private static final ThreadLocal<SourceFile> CURRENTLY_RECONCILED =
new ThreadLocal<SourceFile>(); // the source file being reconciled
/**
* A reconcile operation for this source file.
* <p>
* Clients are not intended to use instances of this class or a subclass
* of this class, but may extend it to augment the default behavior, e.g.
* to send out a delta notification indicating the nature of the change
* of the working copy since the last time it was reconciled. Clients that
* extend this class or a subclass of this class should consider
* overriding {@link SourceFile#hReconcileOperation()} method.
* </p>
*
* @see NotifyingReconcileOperation
*/
protected class ReconcileOperation
{
/**
* Reconciles this working copy according to options specified
* in the given context.
* <p>
* The following context options can influence whether the structure
* of the working copy gets rebuilt:
* </p>
* <ul>
* <li>
* {@link #REBUILD_STRUCTURE_IF_FORCED} - Indicates whether the structure
* should be rebuilt even if reconciling was forced, i.e. the working copy
* buffer has not been modified since the last time it was reconciled.
* </li>
* </ul>
* <p>
* The following context options influence rebuilding of the structure
* of the working copy and, if simultaneously present, must be mutually
* consistent:
* </p>
* <ul>
* <li>
* {@link #SOURCE_AST} - Specifies the AST to use when building the
* structure. The AST is safe to read in the dynamic context of this
* method call, but must not be modified.
* </li>
* <li>
* {@link #SOURCE_CONTENTS} - Specifies the source string to use when
* building the structure.
* </li>
* </ul>
* <p>
* At least one of <code>SOURCE_AST</code> or <code>SOURCE_CONTENTS</code>
* must have a non-null value in the given context.
* </p>
* <p>
* The given context may provide additional data that this method can use,
* including the following:
* </p>
* <ul>
* <li>
* {@link #SOURCE_SNAPSHOT} - Specifies the source snapshot from which
* <code>SOURCE_AST</code> was created or <code>SOURCE_CONTENTS</code>
* was obtained. The snapshot may expire.
* </li>
* </ul>
* <p>
* Subclasses may override this method, but must call the <b>super</b>
* implementation.
* </p>
*
* @param context the operation context (not <code>null</code>)
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @throws CoreException if the working copy cannot be reconciled
* @throws OperationCanceledException if this method is canceled
*/
protected void reconcile(IContext context, IProgressMonitor monitor)
throws CoreException
{
if (context.get(SOURCE_AST) == null && context.get(
SOURCE_CONTENTS) == null)
{
throw new IllegalArgumentException();
}
WorkingCopyInfo info = hElementManager().peekAtWorkingCopyInfo(
SourceFile.this);
boolean create = !info.created; // case of wc creation
if (create || !context.getOrDefault(RECONCILING_FORCED)
|| context.getOrDefault(REBUILD_STRUCTURE_IF_FORCED))
{
if (CURRENTLY_RECONCILED.get() != null)
throw new AssertionError(); // should never happen
CURRENTLY_RECONCILED.set(SourceFile.this);
try
{
hOpen(with(of(FORCE_OPEN, true), context), monitor);
}
finally
{
CURRENTLY_RECONCILED.set(null);
}
}
if (create)
{
if (!info.created)
throw new AssertionError(); // should never happen
hWorkingCopyModeChanged(); // notify about wc creation
}
}
}
/**
* Reconciles this working copy and sends out a delta notification
* indicating the nature of the change of the working copy since
* the last time it was reconciled. Uses the notification manager
* registered in the model context.
*
* @see INotificationManager
*/
protected class NotifyingReconcileOperation
extends ReconcileOperation
{
@Override
protected void reconcile(IContext context, IProgressMonitor monitor)
throws CoreException
{
ElementDelta.Factory deltaFactory = hModel().getModelContext().get(
ElementDelta.Factory.class);
if (deltaFactory == null)
deltaFactory = element -> new ElementDelta(element);
ElementDelta rootDelta = deltaFactory.newDelta(SourceFile.this);
ElementDifferencer differ = new ElementDifferencer(
new ElementDelta.Builder(rootDelta));
doReconcile(context, monitor);
differ.buildDelta();
if (!differ.isEmptyDelta())
{
hModel().getModelContext().get(
INotificationManager.class).fireElementChangeEvent(
new ElementChangeEvent(
ElementChangeEvent.POST_RECONCILE,
differ.getDelta()));
}
}
/**
* This implementation calls {@link ReconcileOperation#reconcile(
* IContext, IProgressMonitor) super.reconcile(..)}.
* <p>
* Subclasses may override this method, but must call its <b>super</b>
* implementation.
* </p>
*
* @param context the operation context (not <code>null</code>)
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @throws CoreException if the working copy cannot be reconciled
* @throws OperationCanceledException if this method is canceled
*/
protected void doReconcile(IContext context, IProgressMonitor monitor)
throws CoreException
{
super.reconcile(context, monitor);
}
}
private abstract class WorkingCopyProvider
{
public WorkingCopyInfo acquireWorkingCopy()
{
for (;;)
{
if (isCanceled())
throw new OperationCanceledException();
WorkingCopyInfo info = doAcquireWorkingCopy();
if (info == null)
return null;
boolean success = false;
try
{
success = waitForInit(info);
}
finally
{
if (!success)
hReleaseWorkingCopy();
}
if (success)
return info;
}
}
protected abstract WorkingCopyInfo doAcquireWorkingCopy();
protected boolean isCanceled()
{
return false;
}
private boolean waitForInit(WorkingCopyInfo info)
{
for (int i = 0; i < 10; i++)
{
if (isCanceled())
throw new OperationCanceledException();
try
{
return info.initTask.wasSuccessful(10,
TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
}
catch (TimeoutException e)
{
}
}
return false;
}
}
}