blob: 05562e568d29be8174f864af86f66407d5f9f844 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2017 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.model.Elements.FORCE_RECONCILING;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
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.handly.buffer.IBuffer;
import org.eclipse.handly.context.IContext;
import org.eclipse.handly.snapshot.ISnapshot;
import org.eclipse.handly.util.Property;
/**
* Holds information related to a working copy of a source file.
* <p>
* Concrete subclasses of this abstract class are expected to be safe
* for use by multiple threads.
* </p>
*
* @noextend This class is not intended to be directly extended by clients.
* However, clients may extend concrete subclasses of this class.
*/
public abstract class WorkingCopyInfo
{
private final ISourceFileImplSupport sourceFile;
private final IBuffer buffer;
private IContext context = EMPTY_CONTEXT;
final InitTask initTask = new InitTask();
volatile boolean created; // whether wc was created (from the model POV)
int refCount;
/**
* Constructs a new working copy info and associates it with the given
* source file and buffer. Does not <code>addRef</code> the given buffer.
* <p>
* Clients should explicitly {@link #dispose} the working copy info
* after it is no longer needed.
* </p>
*
* @param sourceFile the working copy's source file (not <code>null</code>)
* @param buffer the working copy's buffer (not <code>null</code>)
*/
public WorkingCopyInfo(ISourceFileImplSupport sourceFile, IBuffer buffer)
{
if ((this.sourceFile = sourceFile) == null)
throw new IllegalArgumentException();
if ((this.buffer = buffer) == null)
throw new IllegalArgumentException();
}
/**
* Returns the source file associated with this working copy info.
*
* @return the working copy's source file (never <code>null</code>)
*/
public final ISourceFileImplSupport getSourceFile()
{
return sourceFile;
}
/**
* Returns the buffer associated with this working copy info.
* Does not <code>addRef</code> the buffer.
*
* @return the working copy's buffer (never <code>null</code>)
*/
public final IBuffer getBuffer()
{
return buffer;
}
/**
* Returns the context associated with this working copy info.
*
* @return the working copy's context (never <code>null</code>)
*/
public IContext getContext()
{
return context;
}
/**
* Disposes of this working copy info. Does nothing if the working copy info
* is already disposed.
*
* @throws IllegalStateException if the working copy info is still in use
* and cannot be disposed
*/
public final void dispose()
{
synchronized (this)
{
if (refCount > 0)
throw new IllegalStateException();
if (refCount < 0)
return; // already disposed
refCount = -1;
}
onDispose();
}
/**
* Returns whether this working copy info has been disposed.
*
* @return <code>true</code> if the working copy info has been disposed;
* <code>false</code> otherwise
*/
public final boolean isDisposed()
{
synchronized (this)
{
return refCount < 0;
}
}
/**
* Initialization callback.
*/
protected void onInit() throws CoreException
{
// does nothing: subclasses may override
}
/**
* Disposal callback. Note that there is no guarantee that
* <code>onInit()</code> has been called.
*/
protected void onDispose()
{
// does nothing: subclasses may override
}
/**
* Returns whether the working copy needs reconciling, i.e.
* its buffer has been modified since the last time it was reconciled.
*
* @return <code>true</code> if the working copy needs reconciling,
* <code>false</code> otherwise
* @noreference This method is for internal use only.
*/
protected abstract boolean needsReconciling();
/**
* Makes the working copy consistent with its buffer by updating the
* element's structure and properties as necessary.
* <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#FORCE_RECONCILING
* FORCE_RECONCILING} - Indicates whether reconciling has to be performed
* even if the working copy is already consistent with its buffer.
* </li>
* </ul>
* <p>
* An implementation of this method is supposed to call {@link #reconcile0}
* with an appropriately augmented context while providing the necessary
* synchronization guarantees.
* </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
* @noreference This method is for internal use only.
*/
protected abstract void reconcile(IContext context,
IProgressMonitor monitor) throws CoreException;
/**
* Reconciles the 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 #RECONCILING_FORCED} - Indicates whether reconciling was forced,
* i.e. the working copy buffer has not been modified since the last time
* it was reconciled. If <code>false</code> (the default), the structure
* will be rebuilt.
* </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>
* This method makes no guarantees about synchronization of reconcile
* operations. Such guarantees must be provided by the {@link #reconcile}
* method.
* </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
* @noreference This method is for internal use only.
*/
protected final void reconcile0(IContext context, IProgressMonitor monitor)
throws CoreException
{
sourceFile.hReconcileOperation().reconcile(context, monitor);
}
/**
* Specifies the source AST for reconciling.
* @see #reconcile0(IContext, IProgressMonitor)
* @noreference This property is for internal use only.
*/
protected static final Property<Object> SOURCE_AST =
ISourceFileImplSupport.SOURCE_AST;
/**
* Specifies the source string for reconciling.
* @see #reconcile0(IContext, IProgressMonitor)
* @noreference This property is for internal use only.
*/
protected static final Property<String> SOURCE_CONTENTS =
ISourceFileImplSupport.SOURCE_CONTENTS;
/**
* Specifies the source snapshot for reconciling.
* @see #reconcile0(IContext, IProgressMonitor)
* @noreference This property is for internal use only.
*/
protected static final Property<ISnapshot> SOURCE_SNAPSHOT =
ISourceFileImplSupport.SOURCE_SNAPSHOT;
/**
* Indicates whether reconciling was forced, i.e. the working copy buffer
* has not been modified since the last time it was reconciled.
* @see #reconcile0(IContext, IProgressMonitor)
* @noreference This property is for internal use only.
*/
protected static final Property<Boolean> RECONCILING_FORCED =
ISourceFileImplSupport.ReconcileOperation.RECONCILING_FORCED;
/**
* Clients should not be exposed to working copy info if it has not been
* initialized.
*
* @noreference This method is for internal use only.
*/
public final boolean isInitialized()
{
try
{
return initTask.wasSuccessful(0, TimeUnit.NANOSECONDS);
}
catch (InterruptedException e)
{
throw new AssertionError(e);
}
catch (TimeoutException e)
{
return false;
}
}
/**
* A factory of working copy info.
*/
public interface Factory
{
/**
* Returns a new working copy info associated with the given source file
* and buffer. This method is not expected to <code>addRef</code> the
* given buffer.
* <p>
* Clients should explicitly {@link WorkingCopyInfo#dispose() dispose}
* the working copy info after it is no longer needed.
* </p>
*
* @param sourceFile the source file to be associated with the
* created working copy info (not <code>null</code>)
* @param buffer the buffer to be associated with the
* created working copy info (not <code>null</code>)
* @return a new working copy info (never <code>null</code>)
*/
WorkingCopyInfo newWorkingCopyInfo(ISourceFileImplSupport sourceFile,
IBuffer buffer);
}
class InitTask
{
private IContext creationContext;
private IProgressMonitor monitor;
private final FutureTask<?> futureTask = new FutureTask<Object>(
new Callable<Object>()
{
@Override
public Object call() throws CoreException
{
run();
return null;
}
});
void execute(IContext context, IProgressMonitor monitor)
throws CoreException
{
this.creationContext = context;
if (monitor == null)
monitor = new NullProgressMonitor();
this.monitor = monitor;
futureTask.run();
this.monitor = null;
this.creationContext = null;
try
{
futureTask.get();
}
catch (InterruptedException e)
{
throw new AssertionError(e);
}
catch (ExecutionException e)
{
Throwable cause = e.getCause();
if (cause instanceof CoreException)
throw (CoreException)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
if (cause instanceof Error)
throw (Error)cause;
throw new AssertionError(cause); // should never happen
}
}
boolean wasSuccessful(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException
{
try
{
futureTask.get(timeout, unit);
return true;
}
catch (ExecutionException e)
{
return false;
}
}
private void run() throws CoreException
{
context = sourceFile.hWorkingCopyContext(creationContext);
onInit();
reconcile(of(FORCE_RECONCILING, true), monitor);
if (!created)
{
throw new AssertionError(
"Working copy creation was not completed. Ill-behaved implementation of #reconcile?"); //$NON-NLS-1$
}
}
}
}