/******************************************************************************* * Copyright (c) 2011, 2015 Wind River Systems 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: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.debug.internal.ui.viewers.model.provisional; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import org.eclipse.core.runtime.Assert; import org.eclipse.debug.internal.core.IInternalDebugCoreConstants; /** * Virtual item, which is analogous to the SWT's tree item. This class is used * by the {@link VirtualTreeModelViewer}. * * @see VirtualTreeModelViewer * @since 3.8 */ public class VirtualItem { // Data keys for display attributes of an item. public static String LABEL_KEY = "LABEL_KEY"; //$NON-NLS-1$ public static String IMAGE_KEY = "IMAGE_KEY"; //$NON-NLS-1$ public static String FONT_KEY = "FONT_KEY"; //$NON-NLS-1$ public static String FOREGROUND_KEY = "FOREGROUND_KEY"; //$NON-NLS-1$ public static String BACKGROUND_KEY = "BACKGROUND_KEY"; //$NON-NLS-1$ public static String ELEMENT_DATA_KEY = "element"; //$NON-NLS-1$ /** * Index object of a tree item. It allows the indexes to be modified * as items are inserted and removed. */ public static class Index implements Comparable<Object> { private Integer fIndexValue; public Index(int index) { fIndexValue = Integer.valueOf(index); } @Override public boolean equals(Object obj) { return obj instanceof Index && ((Index)obj).fIndexValue.equals(fIndexValue); } @Override public int hashCode() { return fIndexValue.hashCode(); } public void increment() { fIndexValue = Integer.valueOf(fIndexValue.intValue() + 1); } public void decrement() { fIndexValue = Integer.valueOf(fIndexValue.intValue() - 1); } public int intValue() { return fIndexValue.intValue(); } @Override public int compareTo(Object obj) { return obj instanceof Index ? fIndexValue.compareTo(((Index)obj).fIndexValue) : 0; } @Override public String toString() { return fIndexValue.toString(); } } /** * Parent items of this item. */ final private VirtualItem fParent; /** * The index of this item. */ final private Index fIndex; /** * Map of child items. The key to the map is the item's index, which * must be the same object instance as the index in the item. The tree map * keeps the items sorted while allowing indexes (keys) to be modified as * child items are inserted and removed. */ private Map<Index, VirtualItem> fItems = new TreeMap<Index, VirtualItem>(); /** * Flag indicating whether this item has child items. */ private boolean fHasItems = false; /** * Indicates that this item has been expanded. It should only * be set to <code>true</code> if fHasItems is <code>true</code>. */ private boolean fExpanded = false; /** * The count of child items. <code>-1</code> indicates that the count * is not known. */ private int fItemCount = -1; /** * The data held by this item. It includes the element as well as the item * display attributes. */ private Map<String, Object> fData = new HashMap<String, Object>(1); /** * Flag indicating that the item needs to have it's label updated. */ private boolean fNeedsLabelUpdate = true; /** * Flag indicating that the item's count needs to be updated. */ private boolean fNeedsCountUpdate = true; /** * Flag indicating that the item's element needs to be updated. */ private boolean fNeedsDataUpdate = true; /** * Indicates that this item has been disposed. */ private boolean fDisposed = false; /** * Virtual item constructor. * @param parent parent virtual item * @param index index of the item in the parent */ public VirtualItem(VirtualItem parent, Index index) { fParent = parent; fIndex = index; } /** * Clears the child item at the given index. * @param index index of item to clear. */ public void clear(Index index) { VirtualItem item = fItems.remove(index); if (item != null) { item.dispose(); } } /** * Clears all child items. * * @since 3.9 */ public void clearAll() { fData.clear(); for (VirtualItem item : fItems.values()) { item.dispose(); } fItems.clear(); } /** * Returns the parent item. * @return parent item. */ public VirtualItem getParent() { return fParent; } /** * @return Returns the index of this item. */ public Index getIndex() { return fIndex; } /** * Finds the given item in the child items of this element. * @param element Data object of the item to be found. * @return Item if found, <code>null</code> if not. */ public VirtualItem findItem(Object element) { for (VirtualItem item : fItems.values()) { Object nextData = item.getData(); if ( (element != null && element.equals(nextData)) || (element == null && nextData == null) ) { return item; } } return null; } /** * @return Returns whether the data element of this item is stale. */ public boolean needsDataUpdate() { return fNeedsDataUpdate; } /** * Marks the item as having a stale data item. */ public void setNeedsDataUpdate() { fNeedsDataUpdate = true; } /** * Clears the stale status of the item's data element. */ public void clearNeedsDataUpdate() { fNeedsDataUpdate = false; } /** * @return Returns whether the item has stale item count. */ public boolean needsCountUpdate() { return fNeedsCountUpdate; } /** * Marks the item as having a stale child count. */ public void setNeedsCountUpdate() { fNeedsCountUpdate = true; fItemCount = -1; } /** * Clears the stale status of the item's child count. */ public void clearNeedsCountUpdate() { fNeedsCountUpdate = false; } /** * @return Returns whether the item has stale label. */ public boolean needsLabelUpdate() { return fNeedsLabelUpdate; } /** * Marks the item as having a stale label data. */ public void setNeedsLabelUpdate() { fNeedsLabelUpdate = true; } /** * Clears the stale status of the item's label. */ public void clearNeedsLabelUpdate() { fNeedsLabelUpdate = false; } /** * @return Returns whether the item has been disposed. */ public boolean isDisposed() { return fDisposed; } /** * Disposes the item. */ public void dispose() { clearAll(); fDisposed = true; findTree().fireItemDisposed(this); } /** * @param key Key to retrieve data for. * @return Returns item data corresponding to given key. */ public Object getData (String key) { return fData.get(key); } /** * Sets given data element for given key. * @param key Key for data. * @param data Data value. */ public void setData(String key, Object data) { fData.put(key, data); } /** * Sets the item's data element. * @param data Item's new element. */ public void setData(Object data) { fData.put(ELEMENT_DATA_KEY, data); } /** * @return Returns item's data element. */ public Object getData () { return fData.get(ELEMENT_DATA_KEY); } /** * Marks the given item as expanded or collapsed. * @param expanded If true, item will be marked as expanded. */ public void setExpanded(boolean expanded) { if (fExpanded == expanded) { return; } fExpanded = expanded; if (fExpanded && getItemCount() == -1) { setNeedsCountUpdate(); } Assert.isTrue(!fExpanded || hasItems()); // If collapsed, make sure that all the children are collapsed as well. if (!fExpanded) { for (VirtualItem item : fItems.values()) { item.setExpanded(expanded); } } } /** * @return Returns item's expanded state. */ public boolean getExpanded() { return fExpanded; } /** * Sets the flag indicating whether item has child items. * @param hasChildren Set to true if child has items. */ public void setHasItems(boolean hasChildren) { fHasItems = hasChildren; if (!fHasItems) { if (getItemCount() != 0) { setItemCount(0); } } else if (getItemCount() == 0) { setItemCount(-1); } } /** * @return Returns true if item has child items. */ public boolean hasItems() { return fHasItems; } /** * Sets the item's child count. * @param count Child count. */ public void setItemCount(int count) { fItemCount = count; for (Iterator<Entry<Index, VirtualItem>> itr = fItems.entrySet().iterator(); itr.hasNext();) { Entry<Index, VirtualItem> entry = itr.next(); int index = entry.getKey().intValue(); if (index >= count) { VirtualItem item = entry.getValue(); item.dispose(); itr.remove(); } } if (fItemCount == 0) { if (hasItems()) { setHasItems(false); } if (getExpanded()) { setExpanded(false); } } else { if (!hasItems()) { setHasItems(true); } } } /** * @return Returns item's child count. */ public int getItemCount() { return fItemCount; } /** * Returns the child item at given index. Child item is created if needed. * * @param index Index of the child item. * @return Child item. */ public VirtualItem getItem(Index index) { ensureItems(); VirtualItem item = fItems.get(index); if (item == null) { item = new VirtualItem(this, index); fItems.put(index, item); } return item; } /** * @return Returns true if any of the child items need a data update. */ public boolean childrenNeedDataUpdate() { if (getItemCount() == 0) { return false; } if (fItems == null || fItems.size() != fItemCount) { return true; } for (VirtualItem child : fItems.values()) { if (child.needsDataUpdate()) { return true; } } return false; } /** * Returns an array of current child items. The returned array contains * only the items that have been created. It may not contain as many items as the * item count. * * @return Child items array. */ public VirtualItem[] getItems() { return fItems.values().toArray(new VirtualItem[fItems.size()]); } /** * Adds a child item at the given index position. * @param position The index position to inser the new item at. * @return Returns the added item. */ public VirtualItem addItem(int position) { if (!fHasItems) { fHasItems = true; } if (fItemCount < 0) { fItemCount = 0; } // Increment all items with an index higher than the given position. fItemCount++; ensureItems(); for (Index childIndex : fItems.keySet()) { if (childIndex.intValue() >= position) { childIndex.increment(); } } // Note: the same index object used to create the item has to // be used as the key into the map. Index childIndex = new Index(position); VirtualItem newChild = new VirtualItem(this, childIndex); fItems.put(childIndex, newChild); return newChild; } /** * Removes the item at the given index. * @param position Index of the item to remove. */ public void remove(Index position) { fItemCount--; if (fItemCount < 0) { fHasItems = false; } ensureItems(); VirtualItem removedItem = null; for (Iterator<Entry<Index, VirtualItem>> itr = fItems.entrySet().iterator(); itr.hasNext();) { Entry<Index, VirtualItem> entry = itr.next(); Index childIndex = entry.getKey(); if (childIndex.intValue() > position.intValue()) { childIndex.decrement(); } else if (childIndex.intValue() == position.intValue()) { removedItem = entry.getValue(); removedItem.dispose(); itr.remove(); } } } private void ensureItems() { if (fItems == null) { fItems = new HashMap<Index, VirtualItem>(Math.max(1, Math.min(fItemCount, 16))); } } private VirtualTree findTree() { VirtualItem item = this; while (!(item instanceof VirtualTree)) { item = item.fParent; } return (VirtualTree)item; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); toStringItem(buffer, IInternalDebugCoreConstants.EMPTY_STRING); return buffer.toString(); } void toStringItem(StringBuffer buffer, String indent) { buffer.append(indent); buffer.append(toStringElement()); buffer.append("\n"); //$NON-NLS-1$ indent = indent + " "; //$NON-NLS-1$ for (int i = 0; i < fItemCount; i++) { VirtualItem item = fItems.get(new Index(i)); if (item != null) { item.toStringItem(buffer, indent); } else { buffer.append("<no item>\n"); //$NON-NLS-1$ } } } private String toStringElement() { String[] label = (String[])fData.get(LABEL_KEY); if (label != null && label.length != 0) { return label[0]; } Object data = fData.get(ELEMENT_DATA_KEY); if (data != null) { return data.toString(); } return "<no data>"; //$NON-NLS-1$ } }