| /******************************************************************************* |
| * 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()); |
| } |
| } |