blob: 8b491f250f02a9c743aed932a06356888420435c [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
* (inspired by Eclipse JDT work)
*******************************************************************************/
package org.eclipse.handly.model;
import java.util.Objects;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.handly.buffer.IBuffer;
import org.eclipse.handly.internal.Activator;
import org.eclipse.handly.model.impl.IElementImpl;
import org.eclipse.handly.model.impl.ISourceElementImpl;
import org.eclipse.handly.model.impl.ISourceFileImpl;
import org.eclipse.handly.model.impl.SimpleSourceElementInfo;
import org.eclipse.handly.snapshot.ISnapshot;
import org.eclipse.handly.snapshot.StaleSnapshotException;
/**
* Provides methods for generic access to elements of a Handly-based model.
* <p>
* Methods annotated as "handle-only" do not require underlying elements
* to exist. Methods that require underlying elements to exist throw a
* <code>CoreException</code> when the underlying element is missing.
* </p>
* <p>
* Note that despite having a dependency on {@link IResource} and {@link IFile}
* this class can safely be used even when <code>org.eclipse.core.resources</code>
* bundle is not available. This is based on the "outward impression" of late
* resolution of symbolic references a JVM must provide according to the JVMS.
* </p>
*
* @see IElement
*/
public class Elements
{
/**
* A 'null object' indicating that no info is available for source element.
* <p>
* The instance's methods return either <code>null</code> (if allowed
* by the method contract) or an appropriate 'null object' (such as
* a zero-length array).
* </p>
*/
public static final ISourceElementInfo NO_SOURCE_INFO =
new SimpleSourceElementInfo();
/**
* Returns the name of the element, or <code>null</code>
* if the element has no name. This is a handle-only method.
*
* @param element not <code>null</code>
* @return the element name, or <code>null</code> if the element has no name
*/
public static String getName(IElement element)
{
return ((IElementImpl)element).hName();
}
/**
* Returns the immediate parent of the element,
* or <code>null</code> if the element has no parent.
* This is a handle-only method.
*
* @param element not <code>null</code>
* @return the parent element, or <code>null</code> if the element has
* no parent
*/
public static IElement getParent(IElement element)
{
return ((IElementImpl)element).hParent();
}
/**
* Returns the root element for the element.
* Returns the element itself if it has no parent.
* This is a handle-only method.
*
* @param element not <code>null</code>
* @return the root element (never <code>null</code>)
*/
public static IElement getRoot(IElement element)
{
return ((IElementImpl)element).hRoot();
}
/**
* Returns the element's closest ancestor that has the given type.
* Returns <code>null</code> if no such ancestor can be found.
* This is a handle-only method.
*
* @param element not <code>null</code>
* @param ancestorType the given type (not <code>null</code>)
* @return the closest ancestor element that has the given type,
* or <code>null</code> if no such ancestor can be found
*/
public static <T> T getAncestor(IElement element, Class<T> ancestorType)
{
return ((IElementImpl)element).hAncestor(ancestorType);
}
/**
* Returns the innermost resource enclosing the element, or <code>null</code>
* if the element is not enclosed in a workspace resource.
* This is a handle-only method.
* <p>
* Note that it is safe to call this method and test the return value
* for <code>null</code> even when <code>org.eclipse.core.resources</code>
* bundle is not available.
* </p>
*
* @param element not <code>null</code>
* @return the innermost resource enclosing the element, or <code>null</code>
* if the element is not enclosed in a workspace resource
*/
public static IResource getResource(IElement element)
{
return ((IElementImpl)element).hResource();
}
/**
* Returns the path to the innermost resource enclosing the element.
* If the element is enclosed in a workspace resource, the path returned
* is the full, absolute path to the underlying resource, relative to
* the workspace. Otherwise, the path returned is the absolute path to
* a file or to a folder in the file system.
* This is a handle-only method.
*
* @param element not <code>null</code>
* @return the path to the innermost resource enclosing the element
* (never <code>null</code>)
*/
public static IPath getPath(IElement element)
{
return ((IElementImpl)element).hPath();
}
/**
* Returns whether the element exists in the model.
* <p>
* Handles may or may not be backed by an actual element. Handles that are
* backed by an actual element are said to "exist". It is always the case
* that if an element exists, then its parent also exists (provided
* it has one) and includes that element as one of its children.
* It is therefore possible to navigate to any existing element
* from the root element along a chain of existing elements.
* </p>
*
* @param element not <code>null</code>
* @return <code>true</code> if the element exists in the model, and
* <code>false</code> if the element does not exist
*/
public static boolean exists(IElement element)
{
return ((IElementImpl)element).hExists();
}
/**
* Returns the immediate children of the element. Unless otherwise specified
* by the implementing element, the children are in no particular order.
*
* @param element not <code>null</code>
* @return the immediate children of the element (never <code>null</code>).
* Clients <b>must not</b> modify the returned array.
* @throws CoreException if the element does not exist or if an
* exception occurs while accessing its corresponding resource
*/
public static IElement[] getChildren(IElement element) throws CoreException
{
return ((IElementImpl)element).hChildren();
}
/**
* Returns the immediate children of the element that have the given type.
* Unless otherwise specified by the implementing element, the children are
* in no particular order.
*
* @param element not <code>null</code>
* @param childType the given type (not <code>null</code>)
* @return the immediate children of the element that have the given type
* (never <code>null</code>). Clients <b>must not</b> modify the returned
* array.
* @throws CoreException if the element does not exist or if an
* exception occurs while accessing its corresponding resource
*/
public static <T> T[] getChildren(IElement element, Class<T> childType)
throws CoreException
{
return ((IElementImpl)element).hChildren(childType);
}
/**
* Debugging purposes. Returns a string representation of the element.
* Note that the specified style serves as a hint that implementations
* may or may not fully support.
*
* @param element not <code>null</code>
* @param style style hint (not <code>null</code>)
* @return a string representation of the element (never <code>null</code>)
*/
public static String toString(IElement element, ToStringStyle style)
{
return ((IElementImpl)element).hToString(style);
}
/**
* Returns whether the given elements are equal and have the same parent.
*
* @param e1 the first element (not <code>null</code>)
* @param e2 the second element (may be <code>null</code>)
* @return <code>true</code> if the given elements are equal and have
* the same parent, <code>false</code> otherwise
*/
public static boolean equalsAndSameParent(IElement e1, IElement e2)
{
if (!e1.equals(e2))
return false;
return Objects.equals(getParent(e1), getParent(e2));
}
/**
* Returns the smallest element that includes the given source position,
* or <code>null</code> if the given position is not within the source range
* of the given element. If no finer grained element is found at the
* position, the given element itself is returned.
*
* @param context a source element (not <code>null</code>)
* @param position a source position (0-based)
* @param base a snapshot on which the given position is based,
* or <code>null</code> if the snapshot is unknown or doesn't matter
* @return the innermost element enclosing the given source position,
* or <code>null</code> if none (including the given element itself)
* @throws CoreException if the given element does not exist or if an
* exception occurs while accessing its corresponding resource
* @throws StaleSnapshotException if snapshot inconsistency is detected,
* i.e. the given element's current structure and properties are based on
* a different snapshot
*/
public static ISourceElement getSourceElementAt(ISourceElement context,
int position, ISnapshot base) throws CoreException
{
return ((ISourceElementImpl)context).hSourceElementAt(position, base);
}
/**
* Returns the smallest element that includes the given position,
* or <code>null</code> if the given position is not within the source
* range of the given element, or if the given element does not exist or
* an exception occurs while accessing its corresponding resource, or if
* snapshot inconsistency is detected. If no finer grained element is
* found at the position, the given element itself is returned.
*
* @param context a source element (not <code>null</code>)
* @param position a source position (0-based)
* @param base a snapshot on which the given position is based,
* or <code>null</code> if the snapshot is unknown or doesn't matter
* @return the innermost element enclosing the given source position,
* or <code>null</code> if none (including the given element itself)
*/
public static ISourceElement getSourceElementAt2(ISourceElement context,
int position, ISnapshot base)
{
try
{
return getSourceElementAt(context, position, base);
}
catch (CoreException e)
{
if (!exists(context))
; // this is considered normal
else
Activator.log(e.getStatus());
}
catch (StaleSnapshotException e)
{
// ignore
}
return null;
}
/**
* Returns an object holding cached structure and properties for the
* source element.
*
* @param element not <code>null</code>
* @return {@link ISourceElementInfo} for the element
* (never <code>null</code>)
* @throws CoreException if the element does not exist or if an
* exception occurs while accessing its corresponding resource
*/
public static ISourceElementInfo getSourceElementInfo(
ISourceElement element) throws CoreException
{
return ((ISourceElementImpl)element).hSourceElementInfo();
}
/**
* Returns an object holding cached structure and properties for the
* source element, or {@link #NO_SOURCE_INFO} if source info is not
* available.
*
* @param element not <code>null</code>
* @return {@link ISourceElementInfo} for the element
* (never <code>null</code>)
*/
public static ISourceElementInfo getSourceElementInfo2(
ISourceElement element)
{
try
{
return getSourceElementInfo(element);
}
catch (CoreException e)
{
if (!exists(element))
; // this is considered normal
else
Activator.log(e.getStatus());
}
return NO_SOURCE_INFO;
}
/**
* Returns the source file that contains the given element,
* or <code>null</code> if the given element is not contained in a
* source file. Returns the given element itself if it is a source file.
*
* @param element not <code>null</code>
* @return the source file containing the given element,
* or <code>null</code> if none
*/
public static ISourceFile getSourceFile(ISourceElement element)
{
if (element instanceof ISourceFile)
return (ISourceFile)element;
else
return getAncestor(element, ISourceFile.class);
}
/**
* Ensures that, if the given element is contained in a source file,
* the source file is reconciled. Note that the call may result in
* change of existence status for the given element: if the element
* did not exist before, it may be brought into existence; conversely,
* if the element existed, it may cease to exist.
*
* @param element not <code>null</code>
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @return <code>true</code> if the call completed successfully,
* <code>false</code> in case of a failure
* @throws OperationCanceledException if this method is canceled
*/
public static boolean ensureReconciled(ISourceElement element,
IProgressMonitor monitor)
{
ISourceFile sourceFile = getSourceFile(element);
if (sourceFile != null)
{
try
{
reconcile(sourceFile, false, monitor);
}
catch (CoreException e)
{
Activator.log(e.getStatus());
return false;
}
}
return true;
}
/**
* Returns the underlying {@link IFile}, or <code>null</code>
* if the source file has no underlying file in the workspace.
* This is a handle-only method.
* <p>
* This method returns the same value as {@link #getResource(IElement)},
* but saves a downcast.
* </p>
*
* @param sourceFile not <code>null</code>
* @return the underlying <code>IFile</code>, or <code>null</code>
* if the source file has no underlying file in the workspace
*/
public static IFile getFile(ISourceFile sourceFile)
{
return ((ISourceFileImpl)sourceFile).hFile();
}
/**
* Returns whether the source file is a working copy.
*
* @param sourceFile not <code>null</code>
* @return <code>true</code> if the source file is a working copy,
* <code>false</code> otherwise
*/
public static boolean isWorkingCopy(ISourceFile sourceFile)
{
return ((ISourceFileImpl)sourceFile).hIsWorkingCopy();
}
/**
* Returns whether the source file needs reconciling.
* A source file needs reconciling if it is a working copy and
* its buffer has been modified since the last time it was reconciled.
*
* @param sourceFile not <code>null</code>
* @return <code>true</code> if the source file needs reconciling,
* <code>false</code> otherwise
*/
public static boolean needsReconciling(ISourceFile sourceFile)
{
return ((ISourceFileImpl)sourceFile).hNeedsReconciling();
}
/**
* Makes the working copy consistent with its buffer by updating
* the element's structure and properties as necessary. Does nothing
* if the source file is not in working copy mode. The boolean argument
* allows to force reconciling even if the working copy is already
* consistent with its buffer.
*
* @param sourceFile not <code>null</code>
* @param force indicates whether reconciling has to be performed
* even if the working copy is already consistent with its buffer
* @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
*/
public static void reconcile(ISourceFile sourceFile, boolean force,
IProgressMonitor monitor) throws CoreException
{
((ISourceFileImpl)sourceFile).hReconcile(force, monitor);
}
/**
* Returns the buffer opened for the 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 when finished. The buffer will be
* disposed only after it is released by every owner. The buffer must not
* be accessed by clients which don't own it.
* </p>
*
* @param sourceFile not <code>null</code>
* @return the buffer opened for the source file (never <code>null</code>)
* @throws CoreException if the source file does not exist
* or if its contents cannot be accessed
* @see IBuffer
*/
public static IBuffer getBuffer(ISourceFile sourceFile) throws CoreException
{
return ((ISourceFileImpl)sourceFile).hBuffer();
}
/**
* Returns the buffer opened for the 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 when finished. The buffer will be
* disposed only after it is released by every owner. The buffer must not
* be accessed by clients which don't own it.
* </p>
* <p>
* If <code>create == false</code> and there is no buffer currently
* opened for the source file, <code>null</code> is returned.
* </p>
*
* @param sourceFile not <code>null</code>
* @param create indicates whether a new buffer should be created
* if none already exists for the source file
* @param monitor a progress monitor, or <code>null</code>
* if progress reporting is not desired
* @return the buffer opened for the source file, or <code>null</code>
* if <code>create == false</code> and there is no buffer currently opened
* for the source file
* @throws CoreException if the source file does not exist
* or if its contents cannot be accessed
* @throws OperationCanceledException if this method is canceled
* @see IBuffer
*/
public static IBuffer getBuffer(ISourceFile sourceFile, boolean create,
IProgressMonitor monitor) throws CoreException
{
return ((ISourceFileImpl)sourceFile).hBuffer(create, monitor);
}
private Elements()
{
}
}