blob: 7bcd1f44054db80a3c68d455d9e6b07435b894c9 [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.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.util.ToStringOptions.FORMAT_STYLE;
import static org.eclipse.handly.util.ToStringOptions.INDENT_LEVEL;
import static org.eclipse.handly.util.ToStringOptions.INDENT_POLICY;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.FULL;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.LONG;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.MEDIUM;
import java.text.MessageFormat;
import java.util.Map;
import org.eclipse.core.resources.IResource;
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.PlatformObject;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.handly.context.IContext;
import org.eclipse.handly.internal.Activator;
import org.eclipse.handly.model.Elements;
import org.eclipse.handly.model.IElement;
import org.eclipse.handly.model.IModel;
import org.eclipse.handly.util.IndentPolicy;
import org.eclipse.handly.util.Property;
import org.eclipse.handly.util.ToStringOptions.FormatStyle;
/**
* The root of the element class hierarchy.
* <p>
* Note that despite having a dependency on {@link IResource} 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 abstract class Element
extends PlatformObject
implements IElementImpl, IModelManager.Provider
{
private final Element parent;
private final String name;
/**
* Constructs a handle for an element 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 Element(Element parent, String name)
{
this.parent = parent;
this.name = name;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Element))
return false;
Element other = (Element)obj;
if (!hElementType().equals(other.hElementType()))
return false;
if (!hModel().equals(other.hModel()))
return false;
if (parent == null)
{
if (other.parent != null)
return false;
}
else if (!parent.equals(other.parent))
return false;
if (name == null)
{
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + (parent == null ? 0 : parent.hashCode());
result = prime * result + (name == null ? 0 : name.hashCode());
return result;
}
@Override
public String toString()
{
return hToString(EMPTY_CONTEXT);
}
@Override
public final String hName()
{
return name;
}
@Override
public final Element hParent()
{
return parent;
}
@Override
public IModel hModel()
{
return hModelManager().getModel();
}
@Override
public boolean hExists()
{
if (hFindBody() != null)
return true;
if (parent != null && !parent.hExists())
return false;
try
{
hValidateExistence(EMPTY_CONTEXT);
return true;
}
catch (CoreException e)
{
return false;
}
}
@Override
public IElement[] hChildren() throws CoreException
{
return hChildren(hBody());
}
/**
* Returns the cached body for this element, or <code>null</code>
* if none.
*
* @return the cached body for this element, or <code>null</code>
* if none
*/
public Object hFindBody()
{
return hElementManager().get(this);
}
/**
* Returns the cached body for this element without disturbing
* cache ordering, or <code>null</code> if none.
*
* @return the cached body for this element, or <code>null</code>
* if none
*/
public Object hPeekAtBody()
{
return hElementManager().peek(this);
}
/**
* Closes this element, removing any previously registered handle/body
* relationships for it and its existing descendants.
*
* @return <code>true</code> if this element was successfully closed;
* <code>false</code> if the current state of this element does not
* permit closing (e.g., a working copy)
*/
public final boolean hClose()
{
return hClose(true);
}
@Override
public String hToString(IContext context)
{
StringBuilder builder = new StringBuilder();
IndentPolicy indentPolicy = context.getOrDefault(INDENT_POLICY);
int indentLevel = context.getOrDefault(INDENT_LEVEL);
FormatStyle style = context.getOrDefault(FORMAT_STYLE);
if (style == FULL || style == LONG)
{
Object body = hPeekAtBody();
indentPolicy.appendIndent(builder, indentLevel);
hToStringBody(builder, body, context);
if (style == FULL)
hToStringAncestors(builder, context);
if (body != null && hChildren(body).length > 0)
{
indentPolicy.appendLine(builder);
hToStringChildren(builder, body, with(of(FORMAT_STYLE, LONG),
of(INDENT_LEVEL, indentLevel + 1), context));
}
}
else
{
indentPolicy.appendIndent(builder, indentLevel);
hToStringBody(builder, NO_BODY, context);
if (style == MEDIUM)
hToStringAncestors(builder, context);
}
return builder.toString();
}
/**
* Debugging purposes.
*/
protected void hToStringAncestors(StringBuilder builder, IContext context)
{
if (parent != null && parent.hParent() != null)
{
builder.append(" [in "); //$NON-NLS-1$
parent.hToStringBody(builder, NO_BODY, context);
parent.hToStringAncestors(builder, context);
builder.append(']');
}
}
/**
* Debugging purposes.
*/
protected void hToStringChildren(StringBuilder builder, Object body,
IContext context)
{
if (body == null)
return;
IndentPolicy indentPolicy = context.getOrDefault(INDENT_POLICY);
IElement[] children = hChildren(body);
for (int i = 0; i < children.length; i++)
{
if (i > 0)
indentPolicy.appendLine(builder);
builder.append(Elements.toString(children[i], context));
}
}
/**
* Special-purpose value for the <code>body</code> argument of the
* {@link #hToStringBody(StringBuilder, Object, IContext) hToStringBody}
* method. Indicates that information about the body is not relevant.
*/
protected static final Object NO_BODY = new Object();
/**
* Debugging purposes.
*/
protected void hToStringBody(StringBuilder builder, Object body,
IContext context)
{
hToStringName(builder, context);
if (body == null)
builder.append(" (not open)"); //$NON-NLS-1$
}
/**
* Debugging purposes.
*/
protected void hToStringName(StringBuilder builder, IContext context)
{
builder.append(name);
}
/**
* Returns an opaque object representing the <i>type</i> of this element.
* Equal elements must have equal types.
*
* @return the type of this element (never <code>null</code>)
*/
protected Object hElementType()
{
return getClass();
}
/**
* Returns the element manager for this element. The manager must be shared
* between all elements of a Handly-based model. Typical implementations
* would answer a model-specific singleton.
*
* @return the element manager for this element (never <code>null</code>)
*/
protected ElementManager hElementManager()
{
return hModelManager().getElementManager();
}
/**
* Returns a new instance of a generic "element does not exist" exception.
* The exception's message identifies the non-existing element without
* giving any details about the reason for nonexistence.
*
* @return a new "element does not exist" exception (never <code>null</code>)
*/
protected CoreException hDoesNotExistException()
{
return new CoreException(Activator.createErrorStatus(
MessageFormat.format(Messages.Element_Does_not_exist__0,
hToDisplayString(of(FORMAT_STYLE, FULL))), null));
}
/**
* Validates if the element represented by the handle may be "opened",
* i.e. begin existence in the model. For example, a necessary condition
* for element existence might be that the underlying resource exists.
* <p>
* Note that ancestor elements may or may not exist. This method need not
* explicitly verify their existence.
* </p>
*
* @param context the operation context (never <code>null</code>)
* @throws CoreException if this element shall not exist
* @see #hDoesNotExistException()
*/
protected abstract void hValidateExistence(IContext context)
throws CoreException;
/**
* A map containing handle/body relationships.
* @see #hBuildStructure(IContext, IProgressMonitor)
*/
protected static final Property<Map<IElement, Object>> NEW_ELEMENTS =
new Property<Map<IElement, Object>>(Element.class.getName()
+ ".newElements") //$NON-NLS-1$
{
};
/**
* Creates and initializes bodies for this element and for each non-{@link
* #hIsOpenable() openable} child element (and their non-openable children,
* recursively). Uses the {@link #NEW_ELEMENTS} map in the given context
* to associate the created bodies with their respective elements.
*
* @param context the operation context (never <code>null</code>)
* @param monitor a progress monitor (never <code>null</code>)
* @throws CoreException if an exception occurs while accessing
* the element's corresponding resource
* @throws OperationCanceledException if this method is canceled
*/
protected abstract void hBuildStructure(IContext context,
IProgressMonitor monitor) throws CoreException;
/**
* Returns the cached body for this element. If this element is not
* already present in the body cache, its body will be created,
* initialized, and put in the cache.
*
* @return the cached body for this element (never <code>null</code>)
* @throws CoreException if this element does not exist or if an
* exception occurs while accessing its corresponding resource
*/
protected final Object hBody() throws CoreException
{
return hBody(EMPTY_CONTEXT, null);
}
/**
* Returns the cached body for this element. If this element is not
* already present in the body cache, its body will be created,
* initialized, and put in the cache.
*
* @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 cached body for this element (never <code>null</code>)
* @throws CoreException if this element does not exist or if an
* exception occurs while accessing its corresponding resource
* @throws OperationCanceledException if this method is canceled
*/
protected final Object hBody(IContext context, IProgressMonitor monitor)
throws CoreException
{
Object body = hFindBody();
if (body != null)
return body;
return hOpen(context, monitor);
}
/**
* Given a body, returns the immediate children of this element.
*
* @param body the body corresponding to this element
* (never <code>null</code>)
* @return the immediate children of this element (not <code>null</code>)
*/
protected IElement[] hChildren(Object body)
{
return ((Body)body).getChildren();
}
/**
* Indicates whether to forcibly reopen this element if it is already open
* (i.e. already present in the body cache). Default value: <code>false</code>.
*/
static final Property<Boolean> FORCE_OPEN = Property.get(
Element.class.getName() + ".forceOpen", Boolean.class).withDefault( //$NON-NLS-1$
false);
/**
* Creates and initializes bodies for this element, its ancestors and its
* children as necessary and then atomically puts them into the body cache.
* Returns the cached body for this element.
* <p>
* The following context options influence the operation's behavior:
* </p>
* <ul>
* <li>
* {@link #FORCE_OPEN} - Indicates whether to forcibly reopen this element
* if it is already open (i.e. already present in the body cache).
* </li>
* </ul>
*
* @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 cached body for this element (never <code>null</code>)
* @throws CoreException if this element does not exist or if an
* exception occurs while accessing its corresponding resource
* @throws OperationCanceledException if this method is canceled
*/
final Object hOpen(IContext context, IProgressMonitor monitor)
throws CoreException
{
if (monitor == null)
monitor = new NullProgressMonitor();
ElementManager elementManager = hElementManager();
boolean hadTemporaryCache = elementManager.hasTemporaryCache();
try
{
Map<IElement, Object> newElements =
elementManager.getTemporaryCache();
hGenerateBodies(with(of(NEW_ELEMENTS, newElements), context),
monitor);
Object body = newElements.get(this);
if (body == null)
{
// the openable parent did not create a body for this element
throw hDoesNotExistException();
}
if (monitor.isCanceled())
throw new OperationCanceledException();
if (!hadTemporaryCache)
{
if (context.getOrDefault(FORCE_OPEN))
elementManager.put(this, newElements);
else
{
Object existingBody = elementManager.putIfAbsent(this,
newElements);
if (existingBody != null)
body = existingBody;
}
}
return body;
}
finally
{
if (!hadTemporaryCache)
{
elementManager.resetTemporaryCache();
}
}
}
/**
* Returns whether this element is "openable".
* <p>
* An openable element knows how to open itself on demand (i.e. initialize
* its body and put it in the body cache). When opening an element, it is
* ensured that all openable parent elements are open. On the other hand,
* opening an element should open only those child elements that are not
* openable: all other children will open themselves on demand.
* </p>
* <p>
* This implementation returns <code>true</code>. Non-openable elements
* must override this method and return <code>false</code>.
* </p>
*
* @return <code>true</code> if this element is openable,
* <code>false</code> otherwise
*/
protected boolean hIsOpenable()
{
return true;
}
/**
* Returns the innermost {@link #hIsOpenable() openable} element
* in the parent chain of this element, or <code>null</code>
* if this element has no openable parent.
*
* @return the innermost openable element in the parent chain of this
* element, or <code>null</code> if this element has no openable parent
*/
protected final Element hOpenableParent()
{
Element result = parent;
while (result != null && !result.hIsOpenable())
result = result.parent;
return result;
}
/**
* Creates and initializes bodies for ancestors of this element
* as necessary and puts them into the {@link #NEW_ELEMENTS} map
* available in the given context.
*
* @param context the operation context (never <code>null</code>)
* @param monitor a progress monitor (never <code>null</code>)
* @throws CoreException if an ancestor does not exist or if an
* exception occurs while accessing its corresponding resource
* @throws OperationCanceledException if this method is canceled
*/
protected void hGenerateAncestorBodies(IContext context,
IProgressMonitor monitor) throws CoreException
{
Element openableParent = hOpenableParent();
if (openableParent != null && openableParent.hFindBody() == null)
openableParent.hGenerateBodies(context, monitor);
}
/**
* Creates and initializes bodies for this element, its ancestors
* and its children as necessary and puts them into the {@link
* #NEW_ELEMENTS} map available in the given context.
*
* @param context the operation context (never <code>null</code>)
* @param monitor a progress monitor (never <code>null</code>)
* @throws CoreException if this element does not exist or if an
* exception occurs while accessing its corresponding resource
* @throws OperationCanceledException if this method is canceled
*/
protected final void hGenerateBodies(IContext context,
IProgressMonitor monitor) throws CoreException
{
monitor.beginTask("", 2); //$NON-NLS-1$
try
{
hGenerateAncestorBodies(context, new SubProgressMonitor(monitor,
1));
if (hIsOpenable())
{
hValidateExistence(context);
if (monitor.isCanceled())
throw new OperationCanceledException();
hBuildStructure0(context, new SubProgressMonitor(monitor, 1));
Object body = context.get(NEW_ELEMENTS).get(this);
if (body == null)
{
throw new AssertionError(MessageFormat.format(
"No body for {0}. Incorrect {1}#hBuildStructure implementation?", //$NON-NLS-1$
toString(), getClass().getSimpleName()));
}
}
}
finally
{
monitor.done();
}
}
void hBuildStructure0(IContext context, IProgressMonitor monitor)
throws CoreException
{
hBuildStructure(context, monitor);
}
/**
* Closes this element, removing any previously registered handle/body
* relationships for it and its existing descendants.
*
* @param external <code>false</code> if this is a recursive call,
* <code>true</code> otherwise
* @return <code>true</code> if this element was successfully closed;
* <code>false</code> if the current state of this element does not
* permit closing (e.g., a working copy)
*/
protected boolean hClose(boolean external)
{
if (external && !hIsOpenable())
return false;
hElementManager().remove(this);
return true;
}
/**
* The cached body for this element is going to be removed from the cache.
* Do any necessary cleanup.
*
* @param body the cached body for this element (never <code>null</code>)
*/
protected void hRemoving(Object body)
{
// Does nothing. Subclasses may override
}
}