/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.core.workspace; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.teiid.core.designer.selection.TreeSelection; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.designer.core.ModelerCore; /** * ModelWorkspaceSelections * * @since 8.0 */ public class ModelWorkspaceSelections implements TreeSelection { /** * A reusable filter that shows everything in the view. */ public static final ModelWorkspaceSelectionFilter ALL_SELECTABLE_FILTER = new ModelWorkspaceSelectionFilter() { @Override public boolean isSelectable(final Object element) { return true; } }; /*==================================================================== * Constants defining the selection modes: *====================================================================*/ /** * Selection constant (value 0) indicating an object is <i>not</i> selected, nor are any objects * (directly or indirectly) below it. */ public static final int UNSELECTED = 0; /** * Selection constant (value 1) indicating an object <i>is</i> selected as are all of the objects * (directly or indirectly) below it. */ public static final int SELECTED = 1; /** * Selection constant (value 2) indicating an object is <i>not</i> selected, while some of the * objects (directly or indirectly) below it are selected and some are not selected. */ public static final int PARTIALLY_SELECTED = 2; /** * A possible return value from {@link #getSelectionMode(IPath)} when the selection mode * cannot be determined. */ public static final int UNKNOWN_SELECTION = -1; private final Set selecteds; private final Set unselecteds; private final Set partiallySelected; private final List selectableFilters; private ModelWorkspaceView modelWorkspaceView; /** * Construct an instance of ModelWorkspaceSelections. * */ public ModelWorkspaceSelections() { super(); this.selecteds = new HashSet(); this.unselecteds = new HashSet(); this.partiallySelected = new HashSet(); this.selectableFilters = new LinkedList(); } /** * Return the ModelWorkspaceView that this selections object is currently using. * This class uses the view when {@link #setSelected(Object, int) setting the selection mode} * of various objects so that mode of the ancestors of the affected object are set * correctly. Having no view means tha some of the ancestors modes may be set * to something less correct (e.g., {@link #PARTIALLY_SELECTED}). * @return the view, or null if there is no view. */ public ModelWorkspaceView getModelWorkspaceView() { return modelWorkspaceView; } /** * Set the ModelWorkspaceView that this selections object is to use. * This class uses the view when {@link #setSelected(Object, int) setting the selection mode} * of various objects so that mode of the ancestors of the affected object are set * correctly. Having no view means tha some of the ancestors modes may be set * to something less correct (e.g., {@link #PARTIALLY_SELECTED}). * @param view the view to be used, or null if this object should not use a view. */ public void setModelWorkspaceView(ModelWorkspaceView view) { modelWorkspaceView = view; } /** * Return the list of {@link ModelWorkspaceSelectionFilter} instances used by * this object. The returned list should be modified to add or remove * filters. However, this object does not make any special effort when * accessing the filters, so care must be taken to not modify * this list at the same time this object is using the filters. * @return the List of {@link ModelWorkspaceSelectionFilter} instances; never null */ public List getModelWorkspaceSelectionFilters() { return this.selectableFilters; } protected boolean isSelectable( final Object object ) { final Iterator iter = this.selectableFilters.iterator(); while (iter.hasNext()) { final ModelWorkspaceSelectionFilter filter = (ModelWorkspaceSelectionFilter)iter.next(); if ( filter.isSelectable(object) ) { return true; } } // no filter said the object was selectable return false; } /** * Return whether there are any paths that are known to be selected, unselected or partially selected. * @return true if there are at least some paths known to be selected, unselected or partially selected, * or false if there no known selection modes for any path */ public boolean hasSelectionModes() { return this.selecteds.size() != 0 || this.partiallySelected.size() != 0 || this.unselecteds.size() != 0; } /** * Get the current selection mode for the supplied path for a model object. * @param path the path of the object for which the selection mode is to be determined; * may not be null * @return the selection mode; one of {@link #SELECTED}, {@link #UNSELECTED}, * {@link #PARTIALLY_SELECTED} or {@link #UNKNOWN_SELECTION}. */ public int getSelectionMode( final IPath path ) { CoreArgCheck.isNotNull(path); if ( this.selecteds.contains(path) ) { return SELECTED; } if ( this.unselecteds.contains(path) ) { return UNSELECTED; } if ( this.partiallySelected.contains(path) ) { return PARTIALLY_SELECTED; } // ------------------------------------------------------------------- // The path is not known. However, the "default" can be determined by // looking at the ancestors. // ------------------------------------------------------------------- // If this is the JdbcDatabase path ... if ( path.segmentCount() == 0 ) { // The default for the JdbcDatabase path should be to be PARTIALLY_SELECTED // so that children are figured out this.unselecteds.add(path); return UNSELECTED; } // If this path is for a root object ... if ( path.segmentCount() == 1 ) { // so the default should be to be UNSELECTED this.unselecteds.add(path); return UNSELECTED; } // Get the parent path and see what it's selection mode is ... final IPath parentPath = path.removeLastSegments(1); final int parentMode = getSelectionMode(parentPath); // recursive!!! if ( parentMode == SELECTED ) { // The parent is fully selected, so should this node ... this.selecteds.add(path); return SELECTED; } // if ( parentMode == PARTIALLY_SELECTED ) { // // The parent is partially selected, so we don't know what to assume // return UNKNOWN_SELECTION; // } // Parent is unselected, so this node should be as well ... return UNSELECTED; } /** * Get the current selection mode for the supplied model object. * @param modelObject the model object for which the selection mode is to be determined; * may not be null * @return the selection mode; one of {@link #SELECTED}, {@link #UNSELECTED}, * {@link #PARTIALLY_SELECTED} or {@link #UNKNOWN_SELECTION}. */ @Override public int getSelectionMode( final Object modelObject ) { CoreArgCheck.isNotNull(modelObject); assertNonNullView(); final IPath path = this.modelWorkspaceView.getPath(modelObject); if ( path != null ) { return getSelectionMode(path); } return UNSELECTED; } protected void assertNonNullView() { if ( this.modelWorkspaceView == null ) { final String msg = ModelerCore.Util.getString("ModelWorkspaceSelections.NoViewReference"); //$NON-NLS-1$ throw new IllegalStateException(msg); } } /** * Set the selection of the supplied model object. * @param modelObject * @param selectionMode */ public void setSelected( final Object modelObject, final boolean selected ) throws ModelWorkspaceException { CoreArgCheck.isNotNull(modelObject); final int mode = selected ? SELECTED : UNSELECTED; setSelected(modelObject,mode); } /** * Method that actually sets the selection of the supplied model object. * @param modelObject * @param selectionMode */ protected void setSelected( final Object modelObject, final int selectionMode ) throws ModelWorkspaceException { if ( isSelectable(modelObject) ) { assertNonNullView(); final IPath path = this.modelWorkspaceView.getPath(modelObject); if ( path != null ) { setSelected(path,selectionMode); updateParentSelection(modelObject,path,selectionMode); } } } /** * Set the selection of the supplied model object. * @param modelObject * @param selectionMode */ protected void setSelected( final IPath path, final int selectionMode ) { CoreArgCheck.isNotNull(path); if ( selectionMode == SELECTED ) { this.selecteds.add(path); this.unselecteds.remove(path); this.partiallySelected.remove(path); removeSubpaths(this.unselecteds,path); removeSubpaths(this.partiallySelected,path); } else if ( selectionMode == UNSELECTED ) { this.selecteds.remove(path); this.unselecteds.add(path); this.partiallySelected.remove(path); removeSubpaths(this.selecteds,path); removeSubpaths(this.partiallySelected,path); } else if ( selectionMode == PARTIALLY_SELECTED ) { this.selecteds.remove(path); this.unselecteds.remove(path); this.partiallySelected.add(path); } } /** * Utility method to remove from the supplied set all paths to which the supplied * path is considered a prefix. * @param paths * @param path */ protected void removeSubpaths( final Set paths, final IPath path ) { final Iterator iter = paths.iterator(); while (iter.hasNext()) { final IPath existingPath = (IPath)iter.next(); if ( path.isPrefixOf(existingPath) ) { iter.remove(); } } } protected void updateParentSelection( final Object child, final IPath childPath, final int childSelectionMode ) throws ModelWorkspaceException { // If the parent is already set to the selection mode of the child, do nothing ... final IPath parentPath = childPath.removeLastSegments(1); if ( parentPath.segmentCount() == 0 ) { return; } final int currentParentMode = this.getSelectionMode(parentPath); if ( currentParentMode == childSelectionMode ) { return; } if ( this.modelWorkspaceView != null ) { // Get the parent ... final Object parent = this.modelWorkspaceView.getParent(child); if ( parent != null ) { // Get the children of the parent ... final Object[] children = this.modelWorkspaceView.getChildren(parent); // Go through the children ... boolean foundFullySelected = false; boolean foundUnselected = false; for (int i = 0; i < children.length; ++i) { Object childObj = children[i]; if ( isSelectable(childObj) ) { final IPath childObjPath = this.modelWorkspaceView.getPath(childObj); final int mode = this.getSelectionMode(childObjPath); if ( mode == SELECTED ) { foundFullySelected = true; } else if ( mode == UNSELECTED ) { foundUnselected = true; } if ( mode == PARTIALLY_SELECTED || (foundFullySelected && foundUnselected) ) { this.setSelected(parent,PARTIALLY_SELECTED); return; } } } // At this point, all the children are either all unselected or all selected if ( foundFullySelected ) { this.setSelected(parent,SELECTED); } else { this.setSelected(parent,UNSELECTED); } return; } } else { // We have to make some assumptions about what the parent mode is ... // If the parent is selected and the child is being unselected or partially selected ... if ( currentParentMode == SELECTED && childSelectionMode == UNSELECTED || childSelectionMode == PARTIALLY_SELECTED ) { // Assume the parent should be partially selected ... this.setSelected(parentPath,PARTIALLY_SELECTED); } } } /** * Return the paths that have been explicitly selected. These paths may not include paths to all * objects in a fully-selected branch. * <p> * The resulting list contains the IPath objects in their natural order. * </p> * @return the {@link IPath} instances specifying those objects that have been explicitly * selected (not necessarily including any paths to objects contained by fully-selected objects); * never null */ public List getSelectedPaths() { return createSortedPaths(this.selecteds); } /** * Return the paths that are considered partially selected. Partially selected objects * are those that contain (directly or indirectly) both selected and unselected objects. * <p> * The resulting list contains the IPath objects in their natural order. * </p> * @return the {@link IPath} instances specifying those objects that are partially * selected; never null */ public List getPartiallySelectedPaths() { return createSortedPaths(this.partiallySelected); } /** * Return the paths that are considered unselected. These paths may not include paths to all * objects in a fully-unselected branch. * <p> * The resulting list contains the IPath objects in their natural order. * </p> * @return the {@link IPath} instances specifying those objects that are * unselected (not necessarily including any paths to objects contained by unselected objects); * never null */ public List getUnselectedPaths() { return createSortedPaths(this.unselecteds); } /** * Return the list of selected or partially-selected ModelResource instances. * @return the list of {@link ModelResource} instances that are either fully-selected * or partially selected. * @throws IllegalStateException if there is no * {@link #setModelWorkspaceView(ModelWorkspaceView) ModelWorkspaceView} available * (since one is required to actually determine the objects that are selected). * @throws ModelWorkspaceException if there is an error in the ModelWorkspace */ public List getSelectedOrPartiallySelectedModelResources() throws ModelWorkspaceException { assertNonNullView(); final List result = new LinkedList(); // Go through the selections and add the model resources ... for (int i = 0; i < 2; ++i) { final List paths = (i==0) ? this.getSelectedPaths() : this.getPartiallySelectedPaths(); final Iterator iter = paths.iterator(); while (iter.hasNext()) { final IPath path = (IPath)iter.next(); final ModelResource modelResource = this.modelWorkspaceView.findModelResourceForObject(path); if ( modelResource != null ) { // A ModelResource (or an object in a ModelResource) was specified by the path .. if ( !result.contains(modelResource)) { result.add(modelResource); } } else { // See if the object is a ModelWorkspaceItem ... final Object obj = this.modelWorkspaceView.findObject(path); if ( obj instanceof ModelWorkspaceItem ) { addAllModelResources((ModelWorkspaceItem)obj,result); } } } } return result; } /** * @param item * @param result */ private void addAllModelResources( final ModelWorkspaceItem item, final List result) throws ModelWorkspaceException { final int mode = getSelectionMode(item); if ( mode != SELECTED && mode != PARTIALLY_SELECTED ) { // The item is not selected or partially selected, so simply return without doing anything ... return; } // At this point, 'item' is either SELECTED or PARTIALLY_SELECTED. if ( item instanceof ModelResource ) { if ( !result.contains(item)) { // Add to the list only if selected or partially selected result.add(item); } return; } // It's not a ModelResource, so get all the children and process them ... final ModelWorkspaceItem[] children = item.getChildren(); for (int i = 0; i < children.length; ++i) { final ModelWorkspaceItem child = children[i]; if(this.modelWorkspaceView.isVisible(item,child)) { addAllModelResources(child,result); } } } /** * Return the list of selected or partially-selected IResources that are not ModelResource instances. * @return the list of {@link IResource} instances that are either fully-selected * or partially selected. * @throws IllegalStateException if there is no * {@link #setModelWorkspaceView(ModelWorkspaceView) ModelWorkspaceView} available * (since one is required to actually determine the objects that are selected). * @throws ModelWorkspaceException if there is an error in the ModelWorkspace */ public List getSelectedOrPartiallySelectedNonModelResources() throws ModelWorkspaceException { assertNonNullView(); final List result = new LinkedList(); // Go through the selections and add the model resources ... for (int i = 0; i < 2; ++i) { final List paths = (i==0) ? this.getSelectedPaths() : this.getPartiallySelectedPaths(); final Iterator iter = paths.iterator(); while (iter.hasNext()) { final IPath path = (IPath)iter.next(); final IResource resource = ModelerCore.getWorkspace().getRoot().findMember(path); if ( resource != null ) { // A ModelResource (or an object in a ModelResource) was specified by the path .. if ( !result.contains(resource)) { result.add(resource); } } else { // See if the object is a ModelWorkspaceItem ... final Object obj = this.modelWorkspaceView.findObject(path); if ( obj instanceof ModelWorkspaceItem ) { addAllNonModelResources((ModelWorkspaceItem)obj,result); } } } } return result; } /** * @param item * @param result */ private void addAllNonModelResources( final ModelWorkspaceItem item, final List result) throws ModelWorkspaceException { final int mode = getSelectionMode(item); if ( mode != SELECTED && mode != PARTIALLY_SELECTED ) { // The item is not selected or partially selected, so simply return without doing anything ... return; } // At this point, 'item' is either SELECTED or PARTIALLY_SELECTED. final IResource iresource = item.getCorrespondingResource(); if ( item instanceof ModelResource ) { if ( !result.contains(iresource)) { // Add to the list only if selected or partially selected result.add(iresource); } return; } // It's not a ModelResource, so get all the children and process them ... final ModelWorkspaceItem[] children = item.getChildren(); for (int i = 0; i < children.length; ++i) { final ModelWorkspaceItem child = children[i]; addAllNonModelResources(child,result); } } @Override public String toString() { final StringBuffer sb = new StringBuffer(); toString(" Selected Paths",this.selecteds,sb); //$NON-NLS-1$ toString("\n Partially-Selected Paths",this.partiallySelected,sb); //$NON-NLS-1$ toString("\n Unselected Paths",this.unselecteds,sb); //$NON-NLS-1$ return sb.toString(); } protected void toString( final String desc, final Set paths, final StringBuffer sb ) { sb.append(desc + " (" + paths.size() + "):"); //$NON-NLS-1$ //$NON-NLS-2$ final List sortedPaths = createSortedPaths(paths); final Iterator iter = sortedPaths.iterator(); while (iter.hasNext()) { final Object path = iter.next(); sb.append("\n " + path); //$NON-NLS-1$ } } protected List createSortedPaths( final Set paths ) { final List sortedPaths = new LinkedList(); sortedPaths.addAll(paths); final Comparator comparator = new Comparator() { @Override public int compare(Object o1, Object o2) { return ((IPath)o1).toString().compareTo(((IPath)o2).toString()); } @Override public boolean equals(Object obj) { return false; } }; Collections.sort(sortedPaths,comparator); return sortedPaths; } }