blob: 9e3be047705bb92e936aa5ff3f1a95d14bef9cd8 [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
* (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.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.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;
/**
* This "trait-like" interface provides a skeletal implementation of {@link
* IElementImplExtension} to minimize the effort required to implement that
* interface. Clients may "mix in" this interface directly or extend the class
* {@link Element}.
* <p>
* In general, the members first defined in this interface are not intended
* to be referenced outside the subtype hierarchy.
* </p>
*
* @noextend This interface is not intended to be extended by clients.
*/
public interface IElementImplSupport
extends IElementImplExtension, IModelManager.Provider
{
/**
* A default implementation of {@link #hashCode()} cannot be provided in
* an interface, but clients can implement <code>hashCode</code> by
* delegating to this default method.
* <p>
* By default, the hash code for an element is a combination of its
* name and parent's hash code. This method is specialized in {@link
* ISourceFileImplSupport} to return hash code of the underlying
* <code>IFile</code> if the source file has an underlying file in the
* workspace. This method is not intended to be replaced by clients;
* if necessary, clients should override <code>hashCode</code> directly.
* </p>
*
* @return a hash code value
*/
default int hDefaultHashCode()
{
final int prime = 31;
int result = 1;
IElement parent = hParent();
String name = hName();
result = prime * result + (parent == null ? 0 : parent.hashCode());
result = prime * result + (name == null ? 0 : name.hashCode());
return result;
}
/**
* A default implementation of {@link #equals(Object)} cannot be provided in
* an interface, but clients can implement <code>equals</code> by
* delegating to this default method.
* <p>
* By default, two elements that implement this interface are equal
* if they are identical or if they {@link #hCanEqual(Object) can equal} to
* each other and have equal parents and names. This method is specialized
* in {@link ISourceConstructImplSupport} and {@link ISourceFileImplSupport}
* to also take into consideration the elements' occurrence count and
* underlying <code>IFile</code> respectively. This method is not intended
* to be replaced by clients; if necessary, clients should override
* <code>hashCode</code> directly.
* <p>
*
* @param obj the object with which to compare
* @return <code>true</code> if this object is the same as the obj argument,
* <code>false</code> otherwise
*/
default boolean hDefaultEquals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof IElementImplSupport))
return false;
IElementImplSupport other = (IElementImplSupport)obj;
if (!other.hCanEqual(this))
return false;
IElement parent = hParent();
if (parent == null)
{
if (other.hParent() != null)
return false;
}
else if (!parent.equals(other.hParent()))
return false;
String name = hName();
if (name == null)
{
if (other.hName() != null)
return false;
}
else if (!name.equals(other.hName()))
return false;
return true;
}
/**
* Returns whether this element can equal the given object. If this method
* returns <code>false</code>, the <code>equals</code> method must also
* return <code>false</code> for the same argument object.
* <p>
* This implementation compares run-time classes of the objects; as such,
* it doesn't allow creating a subclass whose instances can equal superclass
* instances. Clients may provide their own implementation for this method
* and implement a less discriminating technique such as <code>instanceof</code>
* check.
* </p>
* <p>
* For details, see <a href="http://www.artima.com/pins1ed/object-equality.html">
* http://www.artima.com/pins1ed/object-equality.html</a>.
* <p>
*
* @param obj not <code>null</code>
* @return <code>true</code> if this element can equal the given object;
* <code>false</code> otherwise
*/
default boolean hCanEqual(Object obj)
{
return getClass() == obj.getClass();
}
@Override
default IModel hModel()
{
return hModelManager().getModel();
}
/**
* 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>)
*/
default ElementManager hElementManager()
{
return hModelManager().getElementManager();
}
@Override
default IElement[] hChildren(Object body)
{
return ((Body)body).getChildren();
}
@Override
default Object hFindBody()
{
return hElementManager().get(this);
}
@Override
default Object hPeekAtBody()
{
return hElementManager().peek(this);
}
@Override
default boolean hExists()
{
if (hFindBody() != null)
return true;
IElement parent = hParent();
if (parent != null && !Elements.exists(parent))
return false;
try
{
hValidateExistence(EMPTY_CONTEXT);
return true;
}
catch (CoreException e)
{
return false;
}
}
/**
* 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()
*/
void hValidateExistence(IContext context) throws CoreException;
/**
* 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>)
*/
default CoreException hDoesNotExistException()
{
return new CoreException(Activator.createErrorStatus(
MessageFormat.format(Messages.Element_Does_not_exist__0,
hToDisplayString(of(FORMAT_STYLE, FULL))), null));
}
/**
* {@inheritDoc}
* <p>
* This implementation creates and initializes bodies for this element,
* its ancestors and its children as necessary and then atomically puts
* them into the body cache.
* </p>
* @throws CoreException {@inheritDoc}
* @throws OperationCanceledException {@inheritDoc}
*/
@Override
default 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
IElementImplSupport openable = hOpenableParent();
Object openableBody = newElements.get(openable);
if (openableBody != null)
openable.hRemoving(openableBody); // give a chance for cleanup
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();
}
}
}
/**
* 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
*/
default 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();
hBuildStructure(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();
}
}
/**
* 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
*/
default void hGenerateAncestorBodies(IContext context,
IProgressMonitor monitor) throws CoreException
{
IElementImplSupport openableParent = hOpenableParent();
if (openableParent != null && openableParent.hFindBody() == null)
openableParent.hGenerateBodies(context, monitor);
}
/**
* 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
*/
default 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
*/
default IElementImplSupport hOpenableParent()
{
IElement parent = hParent();
while (parent != null)
{
if (parent instanceof IElementImplSupport)
{
IElementImplSupport p = (IElementImplSupport)parent;
if (p.hIsOpenable())
return p;
}
parent = Elements.getParent(parent);
}
return null;
}
/**
* A map containing handle/body relationships.
* @see #hBuildStructure(IContext, IProgressMonitor)
*/
Property<Map<IElement, Object>> NEW_ELEMENTS =
new Property<Map<IElement, Object>>(IElementImplSupport.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
*/
void hBuildStructure(IContext context, IProgressMonitor monitor)
throws CoreException;
/**
* {@inheritDoc}
* <p>
* If the current state of this element permits closing, this implementation
* invokes {@link #hRemove(IContext)} method, which closes this element.
* </p>
*/
@Override
default void hClose(IContext context)
{
CloseHint hint = context.get(CLOSE_HINT);
if (hint != CloseHint.PARENT_CLOSING && !hIsOpenable())
return;
hRemove(context);
}
/**
* Removes this element from the body cache according to options specified
* in the given context. In general, not only removes the cached body for this
* element but also attempts to close this element's children. Does nothing
* if the cache contained no body for this element. Performs atomically.
* <p>
* This is a low-level operation, which removes this element's body and
* thus closes this element even if the current state of this element does
* not permit closing. Consider using a higher-level {@link #hClose()} method.
* <p>
* If there is a cached body for this element, this implementation invokes
* {@link #hRemoving(Object)} method to notify this element of the upcoming
* removal of its body, calls <code>hClose(of(CLOSE_HINT, PARENT_CLOSING))</code>
* for each of this element's children, then removes the cached body for
* this element, all while holding the element manager lock.
* </p>
*
* @param context the operation context (not <code>null</code>)
*/
default void hRemove(IContext context)
{
hElementManager().remove(this);
}
/**
* The cached body for this element is going to be removed from the cache.
* Do any necessary cleanup.
* <p>
* This method is called internally; it is not intended to be invoked by clients.
* This method is called under the element manager lock.
* </p>
*
* @param body the cached body for this element (never <code>null</code>)
*/
default void hRemoving(Object body)
{
}
@Override
default 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.
*/
default void hToStringAncestors(StringBuilder builder, IContext context)
{
IElement parent = hParent();
if (parent != null && Elements.getParent(parent) != null)
{
builder.append(" [in "); //$NON-NLS-1$
builder.append(Elements.toString(parent, with(of(FORMAT_STYLE,
MEDIUM), of(INDENT_LEVEL, 0), context)));
builder.append(']');
}
}
/**
* Debugging purposes.
*/
default 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.
*/
Object NO_BODY = new Object();
/**
* Debugging purposes.
*/
default void hToStringBody(StringBuilder builder, Object body,
IContext context)
{
hToStringName(builder, context);
if (body == null)
builder.append(" (not open)"); //$NON-NLS-1$
}
/**
* Debugging purposes.
*/
default void hToStringName(StringBuilder builder, IContext context)
{
builder.append(hName());
}
}