| /******************************************************************************* |
| * Copyright (c) 2000, 2010 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| * Patrick Chuong (Texas Instruments) - Improve usability of the breakpoint view (Bug 238956) |
| *****************************************************************/ |
| package org.eclipse.debug.internal.ui.views.breakpoints; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.debug.core.model.IBreakpoint; |
| import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointContainer; |
| import org.eclipse.debug.internal.ui.breakpoints.provisional.IBreakpointOrganizer; |
| import org.eclipse.debug.internal.ui.breakpoints.provisional.OtherBreakpointCategory; |
| import org.eclipse.debug.internal.ui.model.elements.ElementContentProvider; |
| import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; |
| import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; |
| import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate; |
| import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta; |
| import org.eclipse.debug.ui.IDebugUIConstants; |
| |
| /** |
| * This class contains the list of container or a list of breakpoint, elements are sorted according to rules |
| * in the comparator. |
| */ |
| public class BreakpointContainer extends ElementContentProvider implements IAdaptable, IBreakpointContainer { |
| /** |
| * Child breakpoints - inserting new element into this collection should use the insertBreakpoint method |
| */ |
| final private List fBreakpoints = new ArrayList(); |
| |
| /** |
| * Child containers - inserting new element into this container should use the insertChildContainer method |
| */ |
| final private List fChildContainers = new ArrayList(); |
| |
| /** |
| * The category for this container |
| */ |
| private IAdaptable fCategory; |
| |
| /** |
| * The breakpoint organizer for this container |
| */ |
| private IBreakpointOrganizer fOrganizer; |
| |
| /** |
| * The nested breakpoint organizer |
| */ |
| private IBreakpointOrganizer[] fNesting; |
| |
| /** |
| * A flag to indicate this is the default container |
| */ |
| private boolean fDefaultContainer; |
| |
| /** |
| * Parent container |
| */ |
| private BreakpointContainer fParent; |
| |
| /** |
| * The comparator, will be use to compare the order for inserting new element into this container |
| */ |
| private ElementComparator fComparator; |
| |
| /** |
| * Constructor, intended to be call when creating the root container. |
| * |
| * @param organizers the breakpoint organizer for this container |
| * @param comparator the element comparator, can be <code>null</code>. If <code>null</code> than new element |
| * will be added to the end of the list. |
| */ |
| public BreakpointContainer(IBreakpointOrganizer[] organizers, ElementComparator comparator) { |
| fNesting = organizers; |
| fComparator = comparator; |
| } |
| |
| /** |
| * Constructor, intended to be call within this class only. |
| * |
| * @param parent the parent breakpoint container |
| * @param category the category for this container |
| * @param organizer the organizer for this container |
| * @param comparator the element comparator |
| * @param nesting the nested breakpoint organizer |
| */ |
| private BreakpointContainer(BreakpointContainer parent, IAdaptable category, IBreakpointOrganizer organizer, |
| ElementComparator comparator, IBreakpointOrganizer[] nesting) { |
| this(category, organizer, nesting); |
| fParent = parent; |
| fComparator = comparator; |
| } |
| |
| /** |
| * Constructor, intended to be call when reorganizing the content. |
| * |
| * @param category the breakpoint category |
| * @param organizer the breakpoint organizer |
| * @param nesting the nested breakpoint organizer |
| */ |
| BreakpointContainer(IAdaptable category, IBreakpointOrganizer organizer, IBreakpointOrganizer[] nesting) { |
| fCategory = category; |
| fOrganizer = organizer; |
| fNesting = nesting; |
| } |
| |
| /** |
| * Initialize the default containers. |
| * |
| * @param parentDelta the parent delta, addition child delta will be added to the parent |
| */ |
| public void initDefaultContainers(ModelDelta parentDelta) { |
| // seed with all nested categories |
| if (fNesting != null && fNesting.length > 0) { |
| IAdaptable[] emptyCategories = fNesting[0].getCategories(); |
| if (emptyCategories != null) { |
| for (int i = 0; i < emptyCategories.length; i++) { |
| IAdaptable empty = emptyCategories[i]; |
| BreakpointContainer container = findExistingContainer(fChildContainers, empty); |
| if (container == null) { |
| IBreakpointOrganizer[] siblings = new IBreakpointOrganizer[fNesting.length - 1]; |
| System.arraycopy(fNesting, 1, siblings, 0, siblings.length); |
| container = new BreakpointContainer(this, empty, fNesting[0], fComparator, siblings); |
| insertChildContainer(container); |
| container.fDefaultContainer = true; |
| |
| int size = container.getChildren().length; |
| parentDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.INSTALL|IModelDelta.ADDED|IModelDelta.EXPAND, size); |
| |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Insert the breakpoint to this container. |
| * |
| * @param breakpoint the new breakpoint |
| * @return the index of the breakpoint in the cache, -1 if the breakpoint already exist |
| */ |
| private int insertBreakpoint(IBreakpoint breakpoint) { |
| if (fBreakpoints.contains(breakpoint)) |
| return -1; |
| |
| int index = fBreakpoints.size(); |
| for (; fComparator != null && index > 0; index--) { |
| if (fComparator.compare(fBreakpoints.get(index-1), breakpoint) < 0) |
| break; |
| } |
| |
| if (index < 0) |
| index = 0; |
| fBreakpoints.add(index, breakpoint); |
| |
| return index; |
| } |
| |
| /** |
| * Insert the child container this container. |
| * |
| * @param container the child container |
| * @return the index of the container in the cache, -1 if the child container already exist |
| */ |
| private int insertChildContainer(BreakpointContainer container) { |
| int index = fChildContainers.size(); |
| for (; fComparator != null && index > 0; index--) { |
| if (fComparator.compare(fChildContainers.get(index-1), container) < 0) |
| break; |
| } |
| |
| if (index < 0) |
| index = 0; |
| fChildContainers.add(index, container); |
| |
| return index; |
| } |
| |
| |
| /** |
| * Returns the element comparator. |
| * |
| * @return the element comparator |
| */ |
| public ElementComparator getElementComparator() { |
| return fComparator; |
| } |
| |
| /** |
| * Returns the parent container, can be <code>null</code>. |
| * |
| * @return the parent container |
| */ |
| public BreakpointContainer getParent() { |
| return fParent; |
| } |
| |
| /** |
| * Determine whether there is any nested container. |
| * |
| * @return true if has nested container |
| */ |
| private boolean hasNesting() { |
| return fNesting != null && fNesting.length > 0; |
| } |
| |
| /** |
| * Get the categories for the breakpoint with the given organizer. |
| * |
| * @param breakpoint the breakpoint |
| * @param organizer the organizer |
| * @return the categories |
| */ |
| private static IAdaptable[] getCategories(IBreakpoint breakpoint, IBreakpointOrganizer organizer) { |
| IAdaptable[] categories = organizer.getCategories(breakpoint); |
| |
| if (categories == null || categories.length == 0) |
| categories = OtherBreakpointCategory.getCategories(organizer); |
| |
| return categories; |
| } |
| |
| /** |
| * Find existing breakpoint container in the container array the given category. |
| * |
| * @param containers the container array |
| * @param category the category |
| * @return the breakpoint container, can be <code>null</code>. |
| */ |
| private static BreakpointContainer findExistingContainer(List containers, IAdaptable category) { |
| BreakpointContainer container = null; |
| |
| Iterator containerIt = containers.iterator(); |
| while (containerIt.hasNext()) { |
| container = (BreakpointContainer) containerIt.next(); |
| IAdaptable containerCategory = container.getCategory(); |
| |
| if (category.equals(containerCategory)) |
| break; |
| |
| container = null; |
| } |
| |
| return container; |
| } |
| |
| // TODO [pchuong]: can be remove if BreakpointsContentProvider no longer uses this class |
| void addBreakpoint(IBreakpoint breakpoint) { |
| addBreakpoint(breakpoint, new ModelDelta(null, IModelDelta.NO_CHANGE)); |
| } |
| |
| /** |
| * Add a breakpoint to the container, additional delta will be added to the root delta. |
| * |
| * @param breakpoint the breakpoint to added |
| * @param rootDelta the root delta of this container |
| * @see removeBreakpoint |
| */ |
| public void addBreakpoint(IBreakpoint breakpoint, ModelDelta rootDelta) { |
| final int bpIndex = insertBreakpoint(breakpoint); |
| if (bpIndex < 0) return; |
| |
| if (hasNesting()) { |
| IBreakpointOrganizer organizer = fNesting[0]; |
| |
| // get the breakpoint categories from the organizer |
| IAdaptable[] categories = getCategories(breakpoint, organizer); |
| |
| for (int i = 0; i < categories.length; ++i) { |
| ModelDelta childDelta = null; |
| IAdaptable category = categories[i]; |
| BreakpointContainer container = findExistingContainer(fChildContainers, category); |
| |
| // create a new container if it doesn't exist |
| if (container == null) { |
| IBreakpointOrganizer[] nesting = null; |
| if (fNesting.length > 1) { |
| nesting = new IBreakpointOrganizer[fNesting.length - 1]; |
| System.arraycopy(fNesting, 1, nesting, 0, nesting.length); |
| } |
| container = new BreakpointContainer(this, category, organizer, fComparator, nesting); |
| insertChildContainer(container); |
| childDelta = rootDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.INSERTED|IModelDelta.INSTALL, -1); |
| |
| } else { |
| childDelta = rootDelta.addNode(container, fChildContainers.indexOf(container), IModelDelta.STATE, -1); |
| } |
| |
| container.addBreakpoint(breakpoint, childDelta); |
| childDelta.setChildCount(container.getChildren().length); |
| } |
| |
| } else { |
| // TODO [pchuong]: There seems to be some kind of problem when the INSERTED flag is used, |
| // there is a additional checkbox added to the end of the tree. |
| // Also the tree seems to have a strange visual affect when using the INSERTED |
| // flag for the child node instead of ADDED flag. Note: all breakpoint delta |
| // is using the ADDED flag in this class. |
| rootDelta.addNode(breakpoint, bpIndex, IModelDelta.ADDED|IModelDelta.INSTALL, 0); |
| // rootDelta.addNode(breakpoint, bpIndex, IModelDelta.INSERTED|IModelDelta.INSTALL, 0); |
| |
| rootDelta.setFlags(rootDelta.getFlags() | IModelDelta.EXPAND); |
| } |
| } |
| |
| /** |
| * Remove a breakpoint from the container, additional delta will be added to the root delta. |
| * |
| * @param breakpoint the breakpoint to remove |
| * @param delta the root delta of this container |
| * @see addBreakpoint |
| */ |
| public boolean removeBreakpoint(IBreakpoint breakpoint, ModelDelta rootDelta) { |
| boolean removed = fBreakpoints.remove(breakpoint); |
| |
| if (removed) { |
| boolean addRemoveBpDelta = getContainers().length == 0; |
| |
| Iterator it = fChildContainers.iterator(); |
| while (it.hasNext()) { |
| BreakpointContainer container = (BreakpointContainer) it.next(); |
| |
| // if the breakpoint contains in the container and it is the only breakpoint, |
| // than remove the container from the collection |
| if (container.contains(breakpoint)) { |
| ModelDelta childDelta = null; |
| if ((!container.isDefaultContainer()) && (container.getBreakpoints().length <= 1)) { |
| it.remove(); |
| childDelta = rootDelta.addNode(container, IModelDelta.REMOVED|IModelDelta.UNINSTALL); |
| |
| } else { |
| childDelta = rootDelta.addNode(container, IModelDelta.STATE); |
| } |
| |
| // remove the breakpoint from the nested containers |
| container.removeBreakpoint(breakpoint, childDelta); |
| } |
| } |
| |
| if (addRemoveBpDelta) { |
| rootDelta.addNode(breakpoint, IModelDelta.REMOVED|IModelDelta.UNINSTALL); |
| } |
| } |
| return removed; |
| } |
| |
| /** |
| * A helper method to copy the organizers between two containers. |
| * |
| * @param destContainer the destination container |
| * @param sourceContainer the source container |
| */ |
| public static void copyOrganizers(BreakpointContainer destContainer, BreakpointContainer sourceContainer) { |
| destContainer.fNesting = sourceContainer.fNesting; |
| destContainer.fOrganizer = sourceContainer.fOrganizer; |
| destContainer.fCategory = sourceContainer.fCategory; |
| } |
| |
| /** |
| * A helper method to update the breakpoint cache of the container and it's ancestors. |
| * |
| * @param container the breakpoint container |
| * @param breakpoints the breakpoint to update |
| * @param add true if breakpoint should be added to the cache, otherwise remove the breakpoint from the cache |
| */ |
| private static void updateSelfAndAncestorsBreakpointCache(BreakpointContainer container, List breakpoints, boolean add) { |
| if (container != null) { |
| container.fBreakpoints.removeAll(breakpoints); |
| if (add) |
| container.fBreakpoints.addAll(breakpoints); |
| updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, add); |
| } |
| } |
| |
| /** |
| * A helper method to add a breakpoint to an existing container. |
| * |
| * @param destContainer the destination container |
| * @param breakpoint the breakpoint to add |
| * @param destContainerDelta the destination container delta, additional delta will be added to this delta |
| */ |
| static public void addBreakpoint(BreakpointContainer destContainer, IBreakpoint breakpoint, ModelDelta destContainerDelta) { |
| int index = destContainer.insertBreakpoint(breakpoint); |
| Assert.isTrue(index >= 0); |
| |
| List breakpoints = destContainer.fBreakpoints; |
| destContainerDelta.addNode(breakpoint, index/*breakpoints.indexOf(breakpoint)*/, IModelDelta.ADDED|IModelDelta.INSTALL, 0); |
| destContainerDelta.setFlags(destContainerDelta.getFlags() | IModelDelta.EXPAND); |
| |
| // add the breakpoints to the parent containers. |
| updateSelfAndAncestorsBreakpointCache(destContainer.getParent(), breakpoints, true); |
| } |
| |
| /** |
| * A helper method to add a child container to an existing container. |
| * |
| * @param destContainer the destination container |
| * @param sourceContainer the source container |
| * @param destContainerDelta the delta of the destination container, additional delta will be added to this delta |
| */ |
| static public void addChildContainer(BreakpointContainer destContainer, BreakpointContainer sourceContainer, ModelDelta destContainerDelta) { |
| destContainer.insertChildContainer(sourceContainer); |
| sourceContainer.fParent = destContainer; |
| |
| // add the breakpoints to the parent containers. |
| List breakpoints = Arrays.asList(sourceContainer.getBreakpoints()); |
| updateSelfAndAncestorsBreakpointCache(destContainer, breakpoints, true); |
| |
| int index = destContainer.fChildContainers.indexOf(sourceContainer); |
| int size = sourceContainer.getChildren().length; |
| ModelDelta childDelta = destContainerDelta.addNode(sourceContainer, index, IModelDelta.INSERTED|IModelDelta.INSTALL|IModelDelta.EXPAND, size); |
| |
| appendContainerDelta(sourceContainer, childDelta); |
| } |
| |
| /** |
| * A helper method to append delta to the breakpoint container. This method is used by addContainer only. |
| * |
| * @param container the container to append child delta |
| * @param containerDelta the delta of the breakpoint container, additional delta will be added to this delta |
| * @see BreakpointContainer.addContainer |
| */ |
| static private void appendContainerDelta(BreakpointContainer container, ModelDelta containerDelta) { |
| Object[] children = container.getChildren(); |
| for (int i = 0; i < children.length; ++i) { |
| boolean isBreakpoint = children[0] instanceof IBreakpoint; |
| int numChild = isBreakpoint ? 0 : children.length; |
| int flag = isBreakpoint ? IModelDelta.ADDED|IModelDelta.INSTALL |
| : IModelDelta.INSERTED|IModelDelta.INSTALL|IModelDelta.EXPAND; |
| ModelDelta childDelta = containerDelta.addNode(children[i], i, flag, numChild); |
| |
| if (children[i] instanceof BreakpointContainer) { |
| BreakpointContainer childContainer = (BreakpointContainer) children[i]; |
| appendContainerDelta(childContainer, childDelta); |
| } |
| } |
| } |
| |
| /** |
| * A helper method to remove the breakpoint from the container. |
| * |
| * @param container the container to remove the breakpoint |
| * @param breakpoint the breakpoint to remove |
| * @param containerDelta the delta of the breakpoint container, additional delta will be added to this delta |
| */ |
| static public void removeBreakpoint(BreakpointContainer container, IBreakpoint breakpoint, ModelDelta containerDelta) { |
| container.removeBreakpoint(breakpoint, containerDelta); |
| List breakpoints = new ArrayList(); |
| breakpoints.add(breakpoint); |
| updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, false); |
| } |
| |
| /** |
| * Remove all child elements including the given container itself. |
| * |
| * @param container the breakpoint container |
| * @param parentDelta the parent delta |
| */ |
| static public void removeAll(BreakpointContainer container, ModelDelta delta) { |
| BreakpointContainer parent = container.getParent(); |
| if (parent != null) { |
| parent.fChildContainers.remove(container); |
| delta = delta.addNode(container, IModelDelta.UNINSTALL|IModelDelta.REMOVED); |
| } |
| |
| if (container.fChildContainers.size() == 0) { |
| List breakpoints = new ArrayList(); |
| |
| Iterator iterator = container.fBreakpoints.iterator(); |
| while (iterator.hasNext()) { |
| Object obj = iterator.next(); |
| breakpoints.add(obj); |
| delta.addNode(obj, IModelDelta.UNINSTALL|IModelDelta.REMOVED); |
| iterator.remove(); |
| } |
| |
| // remove the breakpoints from the parent containers. |
| updateSelfAndAncestorsBreakpointCache(container.getParent(), breakpoints, false); |
| return; |
| } |
| |
| Iterator iterator = container.fChildContainers.iterator(); |
| while (iterator.hasNext()) { |
| BreakpointContainer childContainer = (BreakpointContainer) iterator.next(); |
| ModelDelta childDelta = delta.addNode(childContainer, IModelDelta.REMOVED|IModelDelta.UNINSTALL); |
| iterator.remove(); |
| |
| removeAll(childContainer, childDelta); |
| } |
| } |
| |
| /** |
| * Returns whether this is the default container. |
| * |
| * @return true if it is a default container |
| */ |
| boolean isDefaultContainer() { |
| return fDefaultContainer; |
| } |
| |
| /** |
| * Returns the breakpoints in this container |
| * |
| * @return the breakpoints in this container |
| */ |
| public IBreakpoint[] getBreakpoints() { |
| return (IBreakpoint[]) fBreakpoints.toArray(new IBreakpoint[fBreakpoints.size()]); |
| } |
| |
| /** |
| * Returns this container's category. |
| * |
| * @return container category |
| */ |
| public IAdaptable getCategory() { |
| return fCategory; |
| } |
| |
| /** |
| * Returns children as breakpoints or nested containers. |
| * |
| * @return children as breakpoints or nested containers |
| */ |
| public Object[] getChildren() { |
| if (fChildContainers.isEmpty()) { |
| return getBreakpoints(); |
| } |
| return getContainers(); |
| } |
| |
| /** |
| * Returns the index of the given child element (breakpoint or container. |
| * |
| * @param child Child to calculate index of. |
| * @return index of child |
| */ |
| public int getChildIndex(Object child) { |
| if (fChildContainers.isEmpty()) { |
| return fBreakpoints.indexOf(child); |
| } |
| return fChildContainers.indexOf(child); |
| } |
| |
| /** |
| * Returns the containers nested in this container, possibly empty. |
| * |
| * @return the containers nested in this container, can be empty. |
| */ |
| public BreakpointContainer[] getContainers() { |
| return (BreakpointContainer[]) fChildContainers.toArray(new BreakpointContainer[fChildContainers.size()]); |
| } |
| |
| /** |
| * Returns this container's organizer. |
| * |
| * @return this container's organizer |
| */ |
| public IBreakpointOrganizer getOrganizer() { |
| return fOrganizer; |
| } |
| |
| /** |
| * Returns whether this container contains the given breakpoint. |
| * |
| * @param breakpoint |
| * @return true if this container contains the given breakpoint |
| */ |
| public boolean contains(IBreakpoint breakpoint) { |
| return fBreakpoints.contains(breakpoint); |
| } |
| |
| /** |
| * Returns the child containers for the given breakpoint. |
| * |
| * @param breakpoint |
| * @return child containers |
| */ |
| public BreakpointContainer[] getContainers(IBreakpoint breakpoint) { |
| if (contains(breakpoint)) { |
| BreakpointContainer[] containers = getContainers(); |
| if (containers.length == 0) { |
| return new BreakpointContainer[]{this}; |
| } |
| ArrayList list = new ArrayList(); |
| for (int i = 0; i < containers.length; i++) { |
| BreakpointContainer container = containers[i]; |
| BreakpointContainer[] subcontainers = container.getContainers(breakpoint); |
| if (subcontainers != null) { |
| for (int j = 0; j < subcontainers.length; j++) { |
| list.add(subcontainers[j]); |
| } |
| } |
| } |
| return (BreakpointContainer[]) list.toArray(new BreakpointContainer[list.size()]); |
| } |
| return new BreakpointContainer[0]; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| public boolean equals(Object obj) { |
| if (obj instanceof BreakpointContainer) { |
| |
| BreakpointContainer container = (BreakpointContainer) obj; |
| |
| // With Group by "Advanced" the same category can contain a different subset of breakpoints, |
| // therefore to have the same category is not enough to be equal. |
| if (! (fParent != null && container.fParent != null && fParent.equals(container.fParent) || |
| fParent == null && container.fParent == null) ) { |
| return false; |
| } |
| |
| if (getCategory() != null && container.getCategory() != null) { |
| return getCategory().equals(container.getCategory()); |
| } else { |
| return true; |
| } |
| } |
| return super.equals(obj); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#getChildCount(java.lang.Object, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate) |
| */ |
| protected int getChildCount(Object element, IPresentationContext context, IViewerUpdate monitor) throws CoreException { |
| return getChildren().length; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#getChildren(java.lang.Object, int, int, org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext, org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate) |
| */ |
| protected Object[] getChildren(Object parent, int index, int length, IPresentationContext context, IViewerUpdate monitor) throws CoreException { |
| return getElements(getChildren(), index, length); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.debug.internal.ui.model.elements.ElementContentProvider#supportsContextId(java.lang.String) |
| */ |
| protected boolean supportsContextId(String id) { |
| return id.equals(IDebugUIConstants.ID_BREAKPOINT_VIEW); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) |
| */ |
| public Object getAdapter(Class adapter) { |
| return Platform.getAdapterManager().getAdapter(this, adapter); |
| } |
| } |