org.bbaw.pdr.ae.export.logic.PdrObjectsPreviewStructure.java Source code

Java tutorial

Introduction

Here is the source code for org.bbaw.pdr.ae.export.logic.PdrObjectsPreviewStructure.java

Source

/**
 * This file is part of Archiv-Editor.
 *
 * The software Archiv-Editor serves as a client user interface for working with
 * the Person Data Repository. See: pdr.bbaw.de
 *
 * The software Archiv-Editor was developed at the Berlin-Brandenburg Academy
 * of Sciences and Humanities, Jgerstr. 22/23, D-10117 Berlin.
 * www.bbaw.de
 *
 * Copyright (C) 2010-2013  Berlin-Brandenburg Academy
 * of Sciences and Humanities
 *
 * The software Archiv-Editor was developed by @author: Christoph Plutte.
 *
 * Archiv-Editor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Archiv-Editor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Archiv-Editor.
 * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>.
 */
package org.bbaw.pdr.ae.export.logic;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Vector;

import org.bbaw.pdr.ae.common.AEConstants;
import org.bbaw.pdr.ae.common.CommonActivator;
import org.bbaw.pdr.ae.control.comparator.AspectsByCreatorComparator;
import org.bbaw.pdr.ae.control.comparator.AspectsByCronComparator;
import org.bbaw.pdr.ae.control.comparator.AspectsByRecentChangesComparator;
import org.bbaw.pdr.ae.control.comparator.AspectsByReferenceComparator;
import org.bbaw.pdr.ae.control.comparator.AspectsBySemanticComparator;
import org.bbaw.pdr.ae.control.facade.Facade;
import org.bbaw.pdr.ae.export.pluggable.AeExportCoreProvider;
import org.bbaw.pdr.ae.metamodel.PdrId;
import org.bbaw.pdr.ae.model.Aspect;
import org.bbaw.pdr.ae.model.PdrObject;
import org.bbaw.pdr.ae.model.Person;
import org.bbaw.pdr.ae.model.ReferenceMods;
import org.bbaw.pdr.ae.model.ValidationStm;
import org.bbaw.pdr.ae.model.view.OrderingHead;
import org.bbaw.pdr.ae.view.control.PDRObjectsOrderer;
import org.bbaw.pdr.ae.view.control.PDRObjectsProvider;
import org.bbaw.pdr.ae.view.control.orderer.AspectByYearOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByMarkupOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByPersonOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByPlaceOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByReferenceOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByRelationOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsBySemanticOrderer;
import org.bbaw.pdr.ae.view.control.orderer.AspectsByUserOrderer;
import org.eclipse.core.runtime.ILog;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;

/**
 * Represents the set of {@link PdrObject}s currently available at
 * the {@link PDRObjectsProvider} instance known by 
 * {@link AeExportCoreProvider} as a tree.</br>
 * As all required methods are implemented, a wrapper can easily be 
 * written to serve as an {@link ITreeContentProvider}.
 * 
 * @author Jakob Hoeper
 *
 */
public class PdrObjectsPreviewStructure implements ITreeContentProvider, ICheckStateProvider {

    private ILog log = AEConstants.ILOGGER;

    private PDRObjectsProvider provider;
    private Facade facade;
    private Vector<StructNode> roots;
    private HashMap<Object, Vector<StructNode>> nodes;
    private String groupedBy;
    private int sortedBy;

    /**
     * <p>Set up an instance of {@link PdrObjectsPreviewStructure} fitted for a tree-ish 
     * representation of {@link PdrObject} sets and their relations.</p>
     * <p>In order to actually populate this structure, {@link #buildTree(boolean)} or
     * {@link #buildTree()} have to be called. This can be done whenever a full rebuilding is
     * desired. </p>
     */
    public PdrObjectsPreviewStructure() {
        facade = Facade.getInstanz();
        provider = AeExportCoreProvider.getInstance().getPdrObjectsProvider();
        // TODO: save somehow what to load and how to group
        roots = new Vector<StructNode>();
        nodes = new HashMap<Object, Vector<StructNode>>();
        sortedBy = 0;
        groupedBy = null;
        //System.out.println(getClass().getSimpleName()+" set up.");
    }

    public PdrObjectsPreviewStructure(TreeViewer viewer) {
        this();
        viewer.setContentProvider(this);
    }

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        if (oldInput == null || !oldInput.equals(newInput)) {
            //System.out.println(this.getClass().getSimpleName()+" input changed");
        }
    }

    /**
     * Update entire structure to match contents of central export plugin's 
     * {@link PDRObjectsProvider} instance.</br>
     * (Calls {@link PDRObjectsProvider#getArrangedAspects()} or 
     * {@link PDRObjectsProvider#getArrangedAspectsByObjects()}, depending on the top 
     * argument being false or true).
     * @param top if true, the {@link PdrObject}s to which the provided aspects
     * are related as aspect_of are represented on a top-level layer. 
     * If false, they will be represented directly above the aspects, but underneath
     * the classification structure the provider is configured with. 
     */
    public void buildTree(boolean top) {
        // reset class attributes
        groupedBy = groupedBy();
        roots = new Vector<StructNode>();
        nodes = new HashMap<Object, Vector<StructNode>>();
        // translate PDR Objects dependencies into more generic tree structure 
        // retrieve classified Aspects
        //System.out.println("  Retrieve aspects as arranged by PDRObjectsProvider.");
        Vector<OrderingHead> groups = top ? provider.getArrangedAspectsByObjects() : provider.getArrangedAspects();

        if (groups != null)
            // represent persons/objects at highest level
            // this is the current default behaviour
            if (top) {
                //System.out.println("   Initialize PDR Objects preview tree layout.");
                // leave logical layout entirely to treeify(Orderinghead) method 
                for (OrderingHead personalHead : groups) {
                    //System.out.println("    Build sub tree for group "+personalHead.getLabel());
                    this.roots.add(treeify(personalHead));
                }
                // previously applied layout
            } else // represent persons/objects at lowest level of class. hierarchy 
                for (OrderingHead group : groups) {
                    StructNode refs = createNode(null, group);
                    // estimate a parent node that will comprise all objects attached to the
                    // current OrdernigHead
                    StructNode head = computeClassNode(group, null);
                    // contribute Aspects and Persons
                    HashMap<Person, StructNode> people = new HashMap<Person, StructNode>();
                    for (Aspect aspect : group.getAspects()) {
                        PdrObject pobj = getPdrObject(aspect.getOwningObjectId());
                        StructNode aspectNode = null;
                        // insert intermediate layer of Persons
                        if (pobj instanceof Person) {
                            Person p = (Person) pobj;
                            if (people.containsKey(p)) {
                                aspectNode = people.get(p).addChild(aspect);
                            } else {
                                StructNode parent = head.addChild(p);
                                people.put(p, parent);
                                aspectNode = parent.addChild(aspect);
                            }
                        } else
                            aspectNode = head.addChild(aspect);
                        // insert References
                        for (ValidationStm validation : aspect.getValidation().getValidationStms()) {
                            ReferenceMods mods = facade.getReference(validation.getReference().getSourceId());
                            if (mods != null) {
                                aspectNode.addChild(mods);
                                refs.addChild(mods);
                            }
                        }
                    }
                }
        //System.out.println(this.getClass().getSimpleName()+" input changed.");
        //System.out.println(" Number of root nodes: "+roots.size());
        //System.out.println(" Number of nodes total: "+nodes.size());
    }

    /**
     * Builds tree structure expressing the results of
     * {@link PDRObjectsProvider#getArrangedAspectsByObjects()}, meaning
     * that the classification the provider has been configured with
     * using {@link PDRObjectsProvider#setOrderer(PDRObjectsOrderer)}
     * will be nested within top level classes representing whatever
     * objects (most likely {@link Person}s) own the comprised {@link Aspect}s. 
     */
    public void buildTree() {
        this.buildTree(true);
    }

    /**
     * Build a tree representing the given {@link OrderingHead} and its underlying aspects structure.
     * <p>Note: nested layers of OrderingHeads are ignored. Only OrderingHeads directly beneath the
     * given head are handled and henceforth expected to contain only aspects as children.
     * </p>
     * <p>This method pretty much expects {@link OrderingHead} root nodes as input, preferably those 
     * whose {@link OrderingHead#getValue()} fields are set to a {@link Person}'s {@link PdrId}.
     * An input like that is provided by {@link PDRObjectsProvider#getArrangedAspectsByObjects()}.
     * </p>
     * @param head
     * @return
     */
    private StructNode treeify(OrderingHead head) {
        // try to find Person object represented by top level node
        Object rootContent = head;
        PdrId id = new PdrId(head.getValue());
        if (id.isValid())
            rootContent = Facade.getInstanz().getPerson(id);
        if (rootContent == null)
            rootContent = head;
        // encapsulate top level node in internal tree node
        StructNode root = this.createNode(null, rootContent);
        // assume exactly one intermediate layer of ordering head nodes between
        // top level and aspects level. Nodes on this layer can be expanded.
        //System.out.println("     Traverse subcategories:");
        for (OrderingHead cat : head.getSubCategories()) {
            //System.out.println("      "+cat.getLabel());
            // place tree node container for classification ordering heads 
            // according to depth indicated by '::' delimiters in ordering head value fields
            StructNode catNode = this.computeClassNode(cat, root);
            // attach aspects to ordering head nodes, references to aspect nodes
            //System.out.println("       append aspects and references");
            for (Aspect aspect : cat.getAspects()) {
                StructNode aspectNode = catNode.addChild(aspect);
                for (ValidationStm validation : aspect.getValidation().getValidationStms()) {
                    ReferenceMods mods = facade.getReference(validation.getReference().getSourceId());
                    if (mods != null)
                        aspectNode.addChild(mods);
                }
            }
        }
        // TODO: or whatever PdrObject this ordered head is representing...
        root.setType(root.getType() + "." + head.getValue().replace(' ', '_').toLowerCase());
        return root;
    }

    /**
     * <p>Identifies the {@link StructNode} representing an {@link OrderingHead} within the
     * specified subtree. A node is considered an ordering head's counterpart if
     * {@link StructNode#getType()} of the former matches the category identified by 
     * {@link OrderingHead#getValue()} of
     * the latter. An ordering head value field might list multiple categories, using
     * the delimiter '::', in which case the ordering head will be represented by
     * multiple nodes, each one standing for a single category and being a child of the previous one.</br>
     * If no matching node can be identified for an ordering head, it will be created at 
     * the appropriate position.</p>
     * <p><b>Call only for nodes representing {@link OrderingHead}s.</b></p>
     * @param group {@link OrderingHead} object that we desire to map into this
     * {@link PdrObjectsPreviewStructure}.
     * @param parent Node amongst whose descendants the given ordering head is to be
     * placed. If null, iteration starts at root level.
     * @return newly created descendant of the given node, so that
     * the subclass structure stored in the available {@link OrderingHead#getValue()}
     * is properly expressed  
     */
    private StructNode computeClassNode(OrderingHead group, StructNode parent) {
        StructNode node = parent;
        //System.out.println("______________\n"+group.getValue()+" - "+group.getLabel());
        // consider segments of ordering head value string delimited by '::' being nested aspects classes
        String[] keys = group.getValue().split("::");
        // semantic labels
        Vector<String> semPath = new Vector<String>();
        String labelProvider = null;
        // TODO: wahrscheinlich auch fuer place u.ae.
        /*if (this.getClassifier().endsWith(".semantic"))
           labelProvider = AeExportCoreProvider.PRIMARY_SEMANTIC_PROVIDER;
        else */if (this.getClassifier().endsWith(".markup"))
            labelProvider = AeExportCoreProvider.PRIMARY_TAGGING_PROVIDER;
        // determine on which level iteration starts
        Vector<StructNode> candidates = (parent != null) ? parent.getChildren() : this.roots;
        // along the path specified by the given markup configuration, stay
        // on the matching branch as long as possible before creating 
        // required sub nodes
        String type = "";
        StructNode next;
        for (String key : keys) {
            semPath.add(key);
            type += (type.length() > 0 ? "." : "") + key.toLowerCase(); //TODO replace special chars
            next = null;
            // see if we can stay on branch..
            for (StructNode cand : candidates)
                if (cand.getType().contains(type))
                    next = cand;
            // in case we lost branch:
            if (next == null) {
                // as in 'category', not related to feline mammals in any way..
                OrderingHead catHead = new OrderingHead(type);
                if (labelProvider != null)
                    catHead.setLabel(AeExportCoreProvider.getAnnotationLabel(semPath, labelProvider));
                else
                    catHead.setLabel(group.getLabel());
                if (node == null) {
                    next = createNode(null, catHead);
                    if (parent == null)
                        this.roots.add(next);
                } else
                    next = node.addChild(catHead);
            }
            // walk down tree
            node = next;
            candidates = node.getChildren();
        }
        //System.out.println(" Estimated category node: "+node.getLabel()+", "+node.getRootCategory());
        if (node == null)
            log.log(new Status(IStatus.WARNING, CommonActivator.PLUGIN_ID, "COMPUTATION OF CLASS NODE FAILED FOR "
                    + group.getLabel() + " under parent " + parent.getLabel()));
        // FIXME: make sure that when returning, the deepest nested node is the passed group instance!
        // FIXME: otherwise, making the PDRObjectsProvider keep its OH instances on comparator update
        // FIXME: is useless for us. 
        // FIXME: we need the very instance passed to here as group parameter to be able to identify known
        // FIXME: OH objects/aspects on re-sort
        return node;
    }

    /**
     * Instantiates a new {@link StructNode} instance which
     * references the StructNode passed as the parent parameter
     * as its parent node and
     * represents the passed Object.<br>
     * @param parent
     * @param content
     * @return
     */
    public StructNode createNode(StructNode parent, Object content) {
        StructNode node = new StructNode(parent, content, this);
        // register node
        // FIXME: since in the new layout, ordering heads appear multiple times
        // FIXME: at different positions, rather register them under their
        // FIXME: getRootCategory()!
        if (!nodes.containsKey(content))
            nodes.put(content, new Vector<StructNode>());
        nodes.get(content).add(node);
        return node;
    }

    public StructNode createRoot(Object content) {
        StructNode node = new StructNode(content, this);
        // register node
        // FIXME: since in the new layout, ordering heads appear multiple times
        // FIXME: at different positions, rather register them under their
        // FIXME: getRootCategory()!
        if (!nodes.containsKey(content))
            nodes.put(content, new Vector<StructNode>());
        nodes.get(content).add(node);
        roots.add(node);
        return node;
    }

    /** 
     * <p>Returns a string identifier for the condition at which
     * aspects of the export plugin's current {@link PDRObjectsProvider} 
     * are put
     * together into groups. Calls {@link #groupedBy()} to do this.
     * Might be out of date?</p>
     * <p>The returning identifier corresponds to the currently active
     * {@link PDRObjectsProvider#getOrderer()}, which might be one of
     * {@link AspectsByMarkupOrderer}, {@link AspectsByPersonOrderer},
     * {@link AspectsByPlaceOrderer}, {@link AspectsByReferenceOrderer},
     * {@link AspectsByRelationOrderer}, {@link AspectsBySemanticOrderer},
     * {@link AspectsByUserOrderer}, or any other class implementing
     * {@link PDRObjectsOrderer}. 
     * @return String identifier looking like <code>grouped.[condition]</code>
     * @see {@link #groupedBy()}, {@link AeExportCoreProvider}
     */
    public String getClassifier() {
        if (this.groupedBy == null)
            this.groupedBy = groupedBy();
        return this.groupedBy;
    }

    /**
     * Detects criteria that causes Aspects to be grouped together
     * in the way it can be seen in the return set of 
     * {@link PDRObjectsProvider#getArrangedAspects()} or
     * {@link PDRObjectsProvider#getArrangedAspectsByObjects()}. </br>
     * In other words, name the ObjectProvider's current
     * {@link PDRObjectsOrderer} implementation.
     * @return String identifier for the currently active 
     * {@link PDRObjectsOrderer} implementation in our 
     * PDRObjectsProvider. Possible
     * return values are:
     * <ul><li>grouped.person</li>
     * <li>grouped.year</li>
     * <li>grouped.markup</li>
     * <li>grouped.place</li>
     * <li>grouped.reference</li>
     * <li>grouped.relation</li>
     * <li>grouped.semantic</li>
     * <li>grouped.user</li></ul>
     */
    public String groupedBy() {
        PDRObjectsOrderer agg = provider.getOrderer();
        String res = "grouped.";
        if (agg == null)
            return res + "none";
        if (agg instanceof AspectsByPersonOrderer)
            return res + "person";
        if (agg instanceof AspectByYearOrderer)
            return res + "year";
        if (agg instanceof AspectsByMarkupOrderer)
            return res + "markup";
        if (agg instanceof AspectsByPlaceOrderer)
            return res + "place";
        if (agg instanceof AspectsByReferenceOrderer)
            return res + "reference";
        if (agg instanceof AspectsByRelationOrderer)
            return res + "relation";
        if (agg instanceof AspectsBySemanticOrderer)
            return res + "semantic";
        if (agg instanceof AspectsByUserOrderer)
            return res + "user";
        return "";
    }

    /**
     * Return root-level nodes.
     * @return
     */
    @Override
    public StructNode[] getElements(Object obj) {
        return roots.toArray(new StructNode[roots.size()]);
    }

    /**
     * Return root-level nodes.
     * @return
     */
    public Vector<StructNode> getElements() {
        return roots;
    }

    /**
     * Returns all children of the node being passed as parentElement, 
     * assuming it is a {@link StructNode} instance. 
     * @param parentElement a {@link StructNode} object
     * @return Array of StructNode
     */
    public StructNode[] getChildren(Object parentElement) {
        if (parentElement instanceof StructNode) {
            Vector<StructNode> res = ((StructNode) parentElement).getChildren();
            if (res != null)
                return res.toArray(new StructNode[res.size()]);
            return null;
        }
        return null;
    }

    /**
     * Returns the parent of a {@link StructNode}
     * @param element StructNode
     * @return StructNode  which references the passed entity as its child.
     */
    public StructNode getParent(Object element) {
        if (element instanceof StructNode)
            return ((StructNode) element).getParent();
        return null;
    }

    public boolean hasChildren(Object element) {
        if (element instanceof StructNode)
            return ((StructNode) element).hasChildren();
        return false;
    }

    /**
     * Returns a {@link Vector} with those nodes that are registered
     * as containing the given object.
     * @param content an Object to find nodes for, e.g. a {@link PdrObject} instance
     * @return Vector of nodes, or empty Vector if object is unknown
     */
    public Vector<StructNode> getNodesFor(Object obj) {
        Vector<StructNode> matches = new Vector<StructNode>();
        if (obj instanceof OrderingHead) {
            // FIXME: since in the new layout, orderingheads appear multiple times
            // FIXME: at different positions, rather register them under their
            // FIXME: getRootCategory()!         
            StructNode dummy = new StructNode(null, obj, this);
            for (Map.Entry<Object, Vector<StructNode>> chapter : nodes.entrySet())
                if (chapter.getKey() instanceof OrderingHead)
                    for (StructNode node : chapter.getValue())
                        if (node.getType().equals(dummy.getType()))
                            matches.add(node);
        }
        if (nodes.containsKey(obj))
            matches.addAll(nodes.get(obj));
        return matches;
    }

    /**
     * 
     * @param id
     * @return
     */
    public PdrObject getPdrObject(PdrId id) {
        //TODO: makes no sense. Change it into a lookup function for the pdr objects
        //stored in this structure instead.
        PdrObject obj;
        obj = Facade.getInstanz().getPdrObject(id);
        return obj;
    }

    /**
     * returns the object stored by the given node.
     */
    public Object getContent(Object node) {
        if (node instanceof StructNode)
            return ((StructNode) node).getContent();
        return null;
    }

    /**
     * Updates the sorting criteria of this structure, equips the responsible
     * {@link PDRObjectsProvider} with the corresponding {@link Aspect}-based 
     * {@link Comparator} and re-builds the internal structure tree based on
     * the updated contents of the provider.
     * @param compId
     * @return
     */
    public int sortBy(int compId) {
        //it is assumed that compId <= 5 anyway. See caller
        compId = compId & 7;
        Comparator<Aspect> comp = null;
        boolean asc;
        if ((sortedBy & 7) != compId) {
            sortedBy = compId; // overwrite sorting criterion on change
        } else // if same base comparator:
            sortedBy ^= 8; // keep sorting, but switch ascending flag
        asc = ((sortedBy >> 3) == 0); // descending order only if 8-bit is set   
        switch (sortedBy & 7) {
        case 0:
            Collections.sort(this.roots, asc ? null : Collections.reverseOrder());
            break;
        case 1:
            comp = new AspectsByCronComparator(asc);
            break;
        case 2:
            comp = new AspectsBySemanticComparator(AeExportCoreProvider.PRIMARY_SEMANTIC_PROVIDER, asc);
            break;
        case 3:
            comp = new AspectsByRecentChangesComparator(asc);
            break;
        case 4:
            comp = new AspectsByReferenceComparator(asc);
            break;
        case 5:
            comp = new AspectsByCreatorComparator(asc);
            break;
        }
        log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
                "Sorting criteria: " + sortedBy + ", ascending: " + asc));
        if (comp != null) {
            provider.setComparator(comp);
            // TODO: experiment: rather than rebuilding the entire thing, we just sort 
            // the structnodes children lists
            //buildTree(); 
            Vector<StructNode> nodes = new Vector<StructNode>();
            NodeComparator ncomp = new NodeComparator(comp);
            nodes = this.getExpandableNodes();
            log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID, "Nodes to sort: " + nodes.size()));
            for (StructNode n : nodes)
                n.sort(ncomp);

            log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
                    " > " + comp.getClass().getName() + "\n" + (asc ? "ascending" : "descending")));
        }
        return sortedBy;
    }

    /**
     * If a {@link StructNode} instance is given, its selection flag will be 
     * overwritten with the given boolean value by calling {@link StructNode#setSelected(boolean)},
     * which will take care of selection state consistency within the affected subtree.
     * <p>
     * <strike>If the node's selection flag status is actually being changed by this call, the
     * return value will be true, if not, false.</strike>
     * </p>
     * Returns a list of those {@link StructNode} instances whose selection flags are being
     * changed during the procedure. 
     * @param obj {@link StructNode} object
     * @param state
     * @return true if operation has any effect
     * @see StructNode#setSelected(boolean)
     */
    public HashSet<StructNode> setSelected(Object obj, boolean state) {
        HashSet<StructNode> res;
        if (obj instanceof StructNode) {
            StructNode node = (StructNode) obj;
            res = node.setSelected(state);
            //System.out.println(" changed check state of "+node.getLabel());
        } else
            res = new HashSet<StructNode>();
        return res;
    }

    /**
     * Sets selection flags of every single node to false.
     */
    public void deselectAll() {
        for (StructNode root : this.roots)
            root.setSelected(false);
    }

    /**
     * Populates an Array of those {@link StructNode} objects that are:
     * <ol><li>flagged as selected</li>
     * <li>descendants of selected nodes only</li>
     * <li>no ancestors of any selected nodes themselves</li>
     * </ol> 
     * If none of the root nodes are
     * selected, the result will be an empty Array.
     * @return {@link StructNode}[]
     */
    public StructNode[] getSelected() {
        Vector<StructNode> selection = new Vector<StructNode>();
        for (StructNode root : roots)
            selection.addAll(root.getSelectedDescendants());
        return selection.toArray(new StructNode[selection.size()]);
    }

    /**
     * Returns all expanded paths, represented by those nodes that are expanded
     * themselves, but don't know any further expanded ancestors.   
     * @return
     */
    public StructNode[] getExpandedNodes() {
        Vector<StructNode> expansion = new Vector<StructNode>();
        for (StructNode root : roots)
            expansion.addAll(root.getExpandedDescendants());
        return expansion.toArray(new StructNode[expansion.size()]);
    }

    public void expand(StructNode node) {
        node.setExpanded(true);
    }

    public void collapse(StructNode node) {
        node.setExpanded(false);
    }

    @Override
    public void dispose() {
        // TODO Auto-generated method stub
    }

    /**
     * Generates a copy of the structure's contents
     * that will only contain those objects that are currently marked as 
     * selected in this {@link PdrObjectsPreviewStructure}.
     * @return List of {@link OrderingHead}s that hold the ids of {@link Person} objects
     * and whose children, which are OrderingHead instances as well, represent groups of
     * {@link Aspect}s and the {@link ReferenceMods} validations involved. 
     */
    public Vector<OrderingHead> getSelectionHeads() {
        Vector<OrderingHead> res = new Vector<OrderingHead>();
        // create ordering heads for all person objects represented on root level
        log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID,
                "BUILD OrderingHead CONTAINERS FOR EXPORT CONTENTS"));
        for (StructNode root : roots) {
            Person p = (Person) root.getContent();
            OrderingHead head = new OrderingHead(p.getPdrId().toString());
            // attach lower levels to new head instance
            for (StructNode cat : root.getChildren())
                for (OrderingHead node : collapseSubCategories(cat))
                    head.addSubCategory(node);
            // return ordering head only if it is not empty
            if (head.hasSubCategories())
                res.add(head);
        }
        return res;
    }

    public Vector<StructNode> getExpandableNodes() {
        HashSet<StructNode> res = new HashSet<StructNode>();
        for (Vector<StructNode> nds : this.nodes.values())
            res.addAll(nds);
        return new Vector<StructNode>(res);
    }

    /**
     * When given a {@link StructNode} whose content is an {@link OrderingHead}, return a list
     * of OrderingHeads that represent any nested categories that might unfold under said node. 
     */
    private Vector<OrderingHead> collapseSubCategories(StructNode catNode) {
        Vector<OrderingHead> res = new Vector<OrderingHead>();
        if (!catNode.isSelected())
            return res;
        String value = catNode.getRootCategory();
        //System.out.println(" OrderingHead contains: "+value);
        OrderingHead head = new OrderingHead(value);
        head.setLabel(catNode.getLabel());
        // test for any sub categories amongst children
        for (StructNode chld : catNode.getChildren())
            if (chld.isSelected())
                if (chld.getContent() instanceof OrderingHead)
                    res.addAll(collapseSubCategories(chld));
                else if (chld.getContent() instanceof Aspect) {
                    head.addAspect((Aspect) chld.getContent());
                    for (StructNode refNode : chld.getChildren())
                        if (refNode.getContent() instanceof ReferenceMods)
                            head.addReference((ReferenceMods) refNode.getContent());
                }
        // if no sub categories are expected under this node, return node itself
        if (res.size() < 1)
            res.add(head);
        return res;
    }

    /**
     * Returns a new {@link PdrObjectsPreviewStructure} instance, containing
     * copies of those {@link StructNode} trees which represent or of which descendants
     *  represent {@link PdrObject} referenced to by the given selection.
     * @param selection an array of {@link PdrObject} instances
     * @return
     */
    public PdrObjectsPreviewStructure subSet(PdrObject[] selection) {
        PdrObjectsPreviewStructure subset = new PdrObjectsPreviewStructure();
        subset.groupedBy = this.groupedBy;
        subset.sortedBy = this.sortedBy;
        LinkedHashSet<StructNode> rootNodes = new LinkedHashSet<StructNode>();
        // process selection of pdr objects
        for (PdrObject pdrObj : selection) {
            Vector<StructNode> there = subset.getNodesFor(pdrObj);
            if (there.isEmpty()) {
                Vector<StructNode> here = getNodesFor(pdrObj);
                // find nodes containing specified pdr object
                for (StructNode repr : here) {
                    StructNode copy = repr.copyIntoStructure(subset);
                    /*if (!subset.nodes.containsKey(pdrObj))
                       subset.nodes.put(pdrObj, new Vector<StructNode>());
                    subset.nodes.get(pdrObj).add(copy);*/
                    rootNodes.add(copy.getRoot());
                }
            }
        }
        subset.roots = new Vector<StructNode>(rootNodes);
        return subset;
    }

    @Override
    public boolean isChecked(Object element) {
        if (element instanceof StructNode) {
            StructNode node = (StructNode) element;
            return node.isSelected();
        }
        return false;
    }

    @Override
    public boolean isGrayed(Object element) {
        // TODO Auto-generated method stub
        return false;
    }

}