
/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2010, Dawid Weiss, Stanisław Osiński.
 * All rights reserved.
 *
 * Refer to the full license file "carrot2.LICENSE"
 * in the root folder of the repository checkout or at:
 * http://www.carrot2.org/carrot2.LICENSE
 */

package org.carrot2.workbench.core.ui;

import java.util.*;

import org.carrot2.core.Cluster;
import org.carrot2.core.ProcessingResult;
import org.carrot2.workbench.core.helpers.Utils;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.widgets.Display;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * Search editor serves as a {@link ISelectionProvider}, providing the selection
 * events (a set of selected clusters or the entire search result).
 */
public final class SearchEditorSelectionProvider implements ISelectionProvider
{
    /**
     * An array of selection listeners.
     */
    private final ArrayList<ISelectionChangedListener> listeners = Lists.newArrayList();
    
    /**
     * Selected clusters by their ID.
     */
    private BitSet selectedClusters = new BitSet();

    /**
     * Mapping from clusters identifiers to cluster instances.
     */
    private final Map<Integer, Cluster> clustersById = Maps.newHashMap();

    /**
     * Mapping from cluster identifiers to {@link TreePath} with
     * {@link Cluster} objects for the tree model.
     */
    private final Map<Integer, TreePath> clusterTreePathsById = Maps.newHashMap();

    /**
     * Custom selection for a set of clusters.
     */
    @SuppressWarnings("unchecked")
    public final class ClusterSelection implements IStructuredSelection
    {
        private final ArrayList<Cluster> selected;
        public final TreeSelection treeSelection;

        ClusterSelection(SearchEditorSelectionProvider provider)
        {
            selected = Lists.newArrayListWithCapacity(
                provider.selectedClusters.cardinality());

            for(int i = provider.selectedClusters.nextSetBit(0); 
                i >= 0; i = provider.selectedClusters.nextSetBit(i + 1))
            {
                selected.add(provider.clustersById.get(i));
            }

            treeSelection = createTreeSelection();
        }

        public Object getFirstElement()
        {
            return selected.get(0);
        }

        public Iterator iterator()
        {
            return selected.iterator();
        }

        public int size()
        {
            return selected.size();
        }

        public Object [] toArray()
        {
            return selected.toArray(new Cluster [selected.size()]);
        }

        public List toList()
        {
            return Lists.newArrayList(selected);
        }

        public boolean isEmpty()
        {
            return selected.isEmpty();
        }
        
        /**
         * Convert a structured selection to a tree selection.  
         */
        private TreeSelection createTreeSelection()
        {
            final TreePath [] paths = new TreePath [selected.size()];
            
            for (int i = 0; i < selected.size(); i++)
            {
                paths[i] = clusterTreePathsById.get(selected.get(i).getId());
            }

            return new TreeSelection(paths);
        }

        @Override
        public String toString()
        {
            return treeSelection.toString();
        }
    }

    /* */
    SearchEditorSelectionProvider(SearchEditor editor)
    {
        /* Build the representation of selection for each cluster. */
        if (editor.getSearchResult().getProcessingResult() != null)
        {
            buildRepresentation(editor.getSearchResult().getProcessingResult());
        }
        else
        {
            editor.getSearchResult().addListener(new SearchResultListenerAdapter() {
                public void processingResultUpdated(ProcessingResult result)
                {
                    buildRepresentation(result);
                }
            });
        }
    }

    /* */
    public void addSelectionChangedListener(ISelectionChangedListener listener)
    {
        assert Display.getCurrent() != null;
        listeners.add(listener);
    }

    /* */
    public void removeSelectionChangedListener(ISelectionChangedListener listener)
    {
        assert Display.getCurrent() != null;
        listeners.remove(listener);
    }

    /* */
    public ISelection getSelection()
    {
        return new ClusterSelection(this);
    }

    /* */
    public void setSelection(ISelection selection, ISelectionChangedListener... skipListeners)
    {
        if (selection.isEmpty())
        {
            if (!selectedClusters.isEmpty())
            {
                selectedClusters.clear();
                fireSelectionChanged();
            }
            return;
        }

        if (selection instanceof IStructuredSelection)
        {
            final BitSet newSelection = getClustersFromSelection(
                (IStructuredSelection) selection);

            // Check if the selection has changed.
            final BitSet changes = (BitSet) selectedClusters.clone();
            changes.xor(newSelection);
            if (changes.cardinality() > 0)
            {
                this.selectedClusters = newSelection;
                fireSelectionChanged(skipListeners);
            }
            return;
        }

        // Do nothing (ignore selection we don't understand).
        Utils.logError("Unexpected selection passed to search editor: "
            + selection.getClass(), false);
    }

    /**
     * Get clusters contained in the selection. 
     */
    static BitSet getClustersFromSelection(IStructuredSelection selection)
    {
        final IStructuredSelection ss = (IStructuredSelection) selection;
        final IAdapterManager mgr = Platform.getAdapterManager();

        final BitSet newSelection = new BitSet();
        for (Object selected : ss.toArray())
        {
            final Cluster c = (Cluster) mgr.getAdapter(selected, Cluster.class);             
            if (c == null) continue;
            newSelection.set(c.getId());
        }
        
        return newSelection;
    }

    /* */
    public void setSelection(ISelection selection)
    {
        setSelection(selection, new ISelectionChangedListener [0]);
    }

    /**
     * Toggle cluster's <code>groupId</code> selection to <code>selected</code>. 
     */
    public void toggleSelected(
        int groupId, boolean selected, ISelectionChangedListener skipListeners)
    {
        assert Display.getCurrent() != null;

        if (this.selectedClusters.get(groupId) == selected)
            return;
        
        this.selectedClusters.set(groupId, selected);
        fireSelectionChanged(skipListeners);
    }

    /**
     * Fire selection changed event.
     */
    private void fireSelectionChanged(ISelectionChangedListener... skipListeners)
    {
        assert Display.getCurrent() != null;

        final SelectionChangedEvent event = 
            new SelectionChangedEvent(this, getSelection());

        ArrayList<ISelectionChangedListener> notifyList = Lists.newArrayList(listeners);
        notifyList.removeAll(Arrays.asList(skipListeners));

        for (ISelectionChangedListener listener : notifyList)
        {
            listener.selectionChanged(event);
        }
    }

    /**
     * Build internal data structures required for selections. 
     */
    private void buildRepresentation(ProcessingResult processingResult)
    {
        this.selectedClusters.clear();
        this.clustersById.clear();
        this.clusterTreePathsById.clear();

        // Build mappings. We use Stack to keep 1.5 compatibility.
        final ArrayList<Cluster> path = new ArrayList<Cluster>();
        descend(processingResult.getClusters(), path);
    }

    /** Recursive descend on the list of clusters. */
    private void descend(List<Cluster> clusters, List<Cluster> path)
    {
        for (Cluster c : clusters)
        {
            path.add(c);
            this.clusterTreePathsById.put(c.getId(), 
                new TreePath(path.toArray()));
            this.clustersById.put(c.getId(), c);
            
            descend(c.getSubclusters(), path);
            path.remove(path.size() - 1);
        }
    }
}

@@ -7755,48 +7755,8 @@
 ngs.
- We use Stack to keep 1.5 compatibility.
 %0A   

Comment removed.