com.openMap1.mapper.views.ClassModelView.java Source code

Java tutorial

Introduction

Here is the source code for com.openMap1.mapper.views.ClassModelView.java

Source

package com.openMap1.mapper.views;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import java.io.IOException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.graphics.Image;

import org.eclipse.core.runtime.IPath;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;

import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.dialogs.SaveAsDialog;

import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EReference;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

import org.eclipse.emf.edit.ui.util.EditUIUtil;

import org.eclipse.xsd.XSDNamedComponent;

import com.openMap1.mapper.presentation.MapperEditor;
import com.openMap1.mapper.presentation.QueryEditor;
import com.openMap1.mapper.presentation.MapperActionBarContributor;

import com.openMap1.mapper.util.EclipseFileUtil;
import com.openMap1.mapper.util.FileUtil;
import com.openMap1.mapper.util.GenUtil;
import com.openMap1.mapper.util.ModelUtil;
import com.openMap1.mapper.util.Timer;

import com.openMap1.mapper.mapping.MDLBase;
import com.openMap1.mapper.mapping.objectMapping;
import com.openMap1.mapper.util.messageChannel;
import com.openMap1.mapper.util.SystemMessageChannel;

import com.openMap1.mapper.actions.CopySimplificationsAction;
import com.openMap1.mapper.actions.ImportSimplificationsAction;
import com.openMap1.mapper.actions.MakeEcoreMappingsAction;
import com.openMap1.mapper.actions.MergeModelsAction;
import com.openMap1.mapper.actions.PasteSimplificationsAction;
import com.openMap1.mapper.actions.RemoveSimplificationsAction;

import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.ParameterClass;

/**
 * Shows a tree-structured view of the mapped class model, and the mappings.
 * 
 * Usually the tree structure is the class hierarchy.
 * For HL7 RMIM applications, it is the RMIM tree.
 * 
 * @author robert
 *
 */

public class ClassModelView extends ViewPart implements ISelectionChangedListener {

    private boolean tracing = false;

    private boolean writeMappingsColumn = true;

    private Timer timer;

    private TreeViewer viewer;

    private ArrayList<EObject> modelRoot = new ArrayList<EObject>();

    private ArrayList<LabelledEClass> rmimRoot = new ArrayList<LabelledEClass>();

    /* record the class tree children of each named class in the model, including 'Object' 
     * These are the classes which have that class as 'main' superclass - only for purposes
     * of tree display. Multiple inheritance of attributes and associations is not ignored. */
    private Hashtable<String, ArrayList<EClass>> childVanillaClasses;

    /** the package which is the root of the model */
    public EPackage ecoreRoot() {
        return ecoreRoot;
    }

    private EPackage ecoreRoot;

    private URI classModelURI = null;

    /** the URI address of the stored class model */
    public URI classModelURI() {
        return classModelURI;
    }

    private URI mappingSetURI = null;

    /** the URI address of the stored mapping set, if the view is currently linked to one */
    public URI mappingSetURI() {
        return mappingSetURI;
    }

    public void setMappingSetURI(URI uri) {
        mappingSetURI = uri;
        queryURI = null;
    }

    private URI queryURI = null;

    /** the URI address of the stored query, if the view is currently linked to one */
    public URI queryURI() {
        return queryURI;
    }

    public void setQueryURI(URI uri) {
        queryURI = uri;
        mappingSetURI = null;
    }

    private MapperEditor mapperEditor = null;

    public MapperEditor mapperEditor() {
        return mapperEditor;
    }

    private MDLBase mdlBase = null;

    public LabelledEClass topLabelledEClass() {
        return topLabelledEClass;
    }

    private LabelledEClass topLabelledEClass;

    private Hashtable<String, Vector<String>> parentTable;

    //---------------------------------------------------------------------------------------------
    //                           constructor and initialisation
    //---------------------------------------------------------------------------------------------

    public ClassModelView() {
    }

    /**
     * Callback to create the viewer and initialize it.
     */
    public void createPartControl(Composite parent) {
        trace("creating part control");
        viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);

        // this column shows the tree , and gets its label provider from the viewer
        TreeViewerColumn tv1 = new TreeViewerColumn(viewer, SWT.LEFT);
        tv1.getColumn().setWidth(300);
        tv1.getColumn().setText("Class");

        TreeViewerColumn tv2 = null;
        if (writeMappingsColumn) {
            tv2 = new TreeViewerColumn(viewer, SWT.LEFT);
            tv2.getColumn().setWidth(300);
            tv2.getColumn().setText("Mappings/Templates");
            tv2.setLabelProvider(new MappingLabelProvider());
        }

        // set up this part to be a selection provider for editors and other views
        getSite().setSelectionProvider(viewer);
        //make this view listen to its own selections, to remember what was selected
        viewer.addPostSelectionChangedListener(this);

        if (classModelURI != null)
            try {

                // set up the class model and connect the viewer to it
                EObject root = FileUtil.getEMFModelRoot(classModelURI);
                if (!(root instanceof EPackage))
                    throw new MapperException("Class model root is not an EPackage");
                ecoreRoot = (EPackage) root;

                // set up the appropriate content provider and label provider for the class model
                setupViewer((EPackage) root, classModelURI);

                /* if at the previous shutdown, the class model view was connected to a mapper editor,
                 * re-connect it to the same one. */
                if (mappingSetURI != null) {
                    MapperEditor me = WorkBenchUtil.getMapperEditor(mappingSetURI.toString());
                    if ((me != null) && (root != null))
                        viewer.addPostSelectionChangedListener(
                                (MapperActionBarContributor) me.getActionBarContributor());
                }

                /* if at the previous shutdown, the class model view was connected to a query editor,
                 * re-connect it to the same one. */
                if (queryURI != null) {
                    QueryEditor qe = WorkBenchUtil.getQueryEditor(queryURI.toString());
                    if ((qe != null) && (root != null))
                        connectToQueryEditor(qe);
                }

            } catch (Exception ex) {
                System.out.println("Failed to open class model for class model view, at  location '"
                        + classModelURI.toString() + "'");
            }

        // make the menu actions
        makeActions();
        contributeToActionBars();

    }

    /* connect to the Attribute and Association Views, if you can; 
     * 'false' means do not force creation; Eclipse objects to the potential recursion */
    private void connectToFeatureViews() {
        AttributeView atv = WorkBenchUtil.getAttributeView(false);
        if (atv != null)
            connectToAttributeView(atv);
        AssociationView asv = WorkBenchUtil.getAssociationView(false);
        if (asv != null)
            connectToAssociationView(asv);
    }

    private void setupViewer(EPackage ecoreRoot, URI uri) {
        trace("Set up viewer");

        // viewing a class model as an RMIM tree
        if (isRMIMRoot(ecoreRoot)) {
            setRMIMClassModel(ecoreRoot, uri);
            setupRMIMViewer(viewer);
        }

        // viewing a class model grouped by packages
        else if (groupInPackages(ecoreRoot)) {
            setPackagedClassModel(ecoreRoot, uri);
            setupPackagedViewer(viewer);

        }

        // viewing a class model as an inheritance hierarchy
        else {
            setVanillaClassModel(ecoreRoot, uri);
            setupVanillaViewer(viewer);
        }

        // connect to the Associations and Attributes views
        connectToFeatureViews();
    }

    /**
     * Set up the TreeViewer with the appropriate content provider and label provider,
     * for a vanilla class model; the tree structure follows the inheritance structure
     * @param viewer
     */
    private void setupVanillaViewer(TreeViewer viewer) {
        trace("set up vanilla viewer");
        viewer.setContentProvider(new ClassModelViewContentProvider());
        viewer.setLabelProvider(new ClassModelViewLabelProvider());
        viewer.getTree().setHeaderVisible(true);
        viewer.getTree().setLinesVisible(true);
    }

    /**
     * Set up the TreeViewer with the appropriate content provider and label provider,
     * for a class model to be viewed by package
     * @param viewer
     */
    private void setupPackagedViewer(TreeViewer viewer) {
        trace("set up packaged viewer");
        viewer.setContentProvider(new PackagedViewContentProvider());
        viewer.setLabelProvider(new PackagedViewLabelProvider());
        viewer.getTree().setHeaderVisible(true);
        viewer.getTree().setLinesVisible(true);
    }

    /**
     * Set up the TreeViewer with the appropriate content provider and label provider,
     * for an RMIM class model; the tree structure follows the RMIM associations
     * @param viewer
     */
    private void setupRMIMViewer(TreeViewer viewer) {
        trace("set up RMIM viewer");
        viewer.setContentProvider(new RMIMViewContentProvider());
        viewer.setLabelProvider(new RMIMViewLabelProvider());
        viewer.getTree().setHeaderVisible(true);
        viewer.getTree().setLinesVisible(true);
    }

    /**
     * 
     * @param root
     * @return true if the class model is to be shown in an RMIM hierarchy view
     */
    public static boolean isRMIMRoot(EObject root) {
        if ((root != null) && (root instanceof EPackage))
            return (ModelUtil.getMIFAnnotation((EPackage) root, "RMIM") != null);
        return false;
    }

    /* replaced by calls to ModelUtil.getMIFAnnotation
    private String getAnnotation(EModelElement eo, String key)
    {
       String mifNamespaceURI = "urn:hl7-org:v3/mif2";
       String value = null;
       EAnnotation ann = eo.getEAnnotation(mifNamespaceURI);
       if (ann != null) value = ann.getDetails().get(key);
       return value;
    }
    */

    /**
     * 
     * @param eo an EMF model object - root of the package tree
     * @return true if the classes are to be viewed grouped by packages,
     * rather than by an inheritacne hierarchy
     */
    private Boolean groupInPackages(EModelElement eo) {
        boolean group = false;
        EAnnotation ann = eo.getEAnnotation("urn:com.openMap1.mapper");
        String value = null;
        if (ann != null)
            value = ann.getDetails().get("viewByPackages");
        if (value != null)
            group = (value.equals("true"));
        return group;
    }

    /**
     * do the startup activities when this view is associated with a mapping set
     * and its active mapper editor
     * @param MapperEditor me the active mapper editor
     * @param EObject umlRoot the root of the UML model being shown in this view
     * @param String path the path to the UML model file, for use in the view title
     */
    public void initiateForMapperEditor(MapperEditor me, EObject root, String classModelURIString) {
        trace("initiate for mapper editor");
        if ((root != null) && (root instanceof EPackage)) {
            ecoreRoot = (EPackage) root;
            URI uri = URI.createURI(classModelURIString);

            setupViewer(ecoreRoot, uri);

            setContentDescriptionFromURL(classModelURIString);
            setMappingSetURI(EditUIUtil.getURI(me.getEditorInput()));

            // hook up the mapper editor so it can listen to selections in this class model view
            viewer.addPostSelectionChangedListener((MapperActionBarContributor) me.getActionBarContributor());

            // record the editor 
            mapperEditor = me;
            mdlBase = null;

            viewer.refresh();
        } else if (root == null) {
            //System.out.println("Class Model Root is null in ClassModelView.initiateForMapperEditor"   );      
        } else if (!(root instanceof EPackage)) {
            System.out.println("Class Model Root is not an EPackage, but a " + root.eClass().getName());
            return;
        }
    }

    /**
     * do the startup activities when this view is associated with a query
     * and its active query editor
     * @param QueryEditor qe the active query editor
     * @param EObject umlRoot the root of the UML model being shown in this view
     * @param String path the path to the query file, for use in the view title
     */
    public void initiateForQueryEditor(QueryEditor qe, EObject root, String classModelURIString) {
        trace("initiate for query editor");
        if (timer == null)
            timer = new Timer("Class model view");
        timer.start(Timer.COMPILE);
        if ((root != null) && (root instanceof EPackage)) {
            this.ecoreRoot = (EPackage) root;
            URI uri = URI.createURI(classModelURIString);
            setupViewer(ecoreRoot, uri);
            setContentDescriptionFromURL(classModelURIString);
            setQueryURI(EditUIUtil.getURI(qe.getEditorInput()));
            connectToQueryEditor(qe);
            mapperEditor = null; // sever any previous link to a mapping set
            mdlBase = null;
            viewer.refresh();
        } else if (root == null) {
            System.out.println("Class Model Root is null in ClassModelView.initiateForQueryEditor");
        } else if (!(root instanceof EPackage)) {
            System.out.println("Ecore Class Model Root is not an EPackage, but a " + root.eClass().getName());
            return;
        }
    }

    /**
     * hook up the query editor so it can listen to selections in this class model view
     * @param qe
     */
    public void connectToQueryEditor(QueryEditor qe) {
        viewer.addPostSelectionChangedListener(qe);
        mapperEditor = null; // sever any previous link to a mapping set
    }

    /**
     * hook up the Attribute View so it can listen to selections in this class model view
     * @param qe
     */
    public void connectToAttributeView(AttributeView av) {
        viewer.addPostSelectionChangedListener(av);
    }

    /**
     * hook up the Association View so it can listen to selections in this class model view
     * @param qe
     */
    public void connectToAssociationView(AssociationView av) {
        viewer.addPostSelectionChangedListener(av);
    }

    /**
     * Strip out a file name from a URL, and use it as the description of the view
     */
    public void setContentDescriptionFromURL(String url) {
        super.setContentDescription(FileUtil.getFileName(url));
    }

    //---------------------------------------------------------------------------------------------
    //           Setting up a class model to be viewed in packages for the content provider and label provider                   
    //---------------------------------------------------------------------------------------------

    /**
     * record a UML class model in the form used by the ViewContentProvider -
     * in the Hashtable of 'main' subclasses.
     * This method assumes the class model is in a single package
     * 
     * @param root the root object of the UML model, assumed to be an EPackage
     */
    public void setPackagedClassModel(EPackage root, URI classModelURI) {
        trace("Set packaged class model");
        this.classModelURI = classModelURI;
        modelRoot = new ArrayList<EObject>();
        modelRoot.add(root);

        /* ensure the viewer is expecting the right kind of content provider and label provider, 
         * and give it the root of the tree. */
        setupPackagedViewer(viewer);
        viewer.setInput(modelRoot);
    }

    //---------------------------------------------------------------------------------------------
    //           content provider and label provider for a class model to be viewed by package
    //---------------------------------------------------------------------------------------------

    /*
     * The provider (= adapter) which adapts the model (in this case an EMF model)
     * to the viewer (in this case a TreeViewer). 
     * There are two parts - a content provider which provides the tree structure
     * and a label provider which provides the icons and text
     */
    class PackagedViewContentProvider implements IStructuredContentProvider, ITreeContentProvider {

        // called by viewer.setInput(Object) but need do nothing
        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        }

        public void dispose() {
            trace("packaged view dispose");
        }

        public Object[] getElements(Object parent) {
            // the only child of modelroot is the EPackage object 
            if (parent.equals(modelRoot)) {
                return arrayOf(modelRoot);
            }
            // for all other objects (including the package) use getChildren
            return getChildren(parent);
        }

        /**
         * child EObjects:
         * For a package, the first children are its sub-packages; next children are the classes in it
         * For any class, the there are no children
         * For any other EObject, get the EObject child nodes
         */
        public Object[] getChildren(Object parent) {
            ArrayList<EObject> result = new ArrayList<EObject>();
            if (parent instanceof EPackage) {
                EPackage pack = (EPackage) parent;
                // sub-packages are the first children
                for (Iterator<EPackage> ip = pack.getESubpackages().iterator(); ip.hasNext();)
                    result.add(ip.next());
                // next follow classes in the package
                for (Iterator<EClassifier> ic = pack.getEClassifiers().iterator(); ic.hasNext();) {
                    EClassifier ec = ic.next();
                    if (ec instanceof EClass)
                        result.add(ec);
                }
            }
            return arrayOf(result);
        }

        /**
         * get the parent object of any object in the tree. For EClasses, this is the 
         * its containing package. for packages, the parent is its superpackage
         */
        public Object getParent(Object child) {
            EObject parent = null;
            if (child instanceof EClass) {
                parent = ((EClass) child).getEPackage();
            } else if (child instanceof EPackage) {
                parent = ((EPackage) child).getESuperPackage();
                if (parent == null)
                    parent = ecoreRoot;
            }
            return parent;
        }

        public boolean hasChildren(Object parent) {
            return (getChildren(parent).length > 0);
        }
    } // end of class PackagedViewContentProvider

    class PackagedViewLabelProvider extends LabelProvider implements ITableLabelProvider {

        public String getText(Object obj) {
            if (obj instanceof EObject)
                return getTextLabel((EObject) obj);
            return "not an EObject";
        }

        public String getColumnText(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getText(obj);
            case 1: {
                if (obj instanceof EClass)
                    return getObjectMappingText((EClass) obj);
            }
            }
            return ("");
        }

        public Image getColumnImage(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getImage(obj);
            case 1:
                return null;
            }
            return null;
        }

        public Image getImage(Object obj) {
            Image im = null;
            // default 'Folder' image if type is not recognised (eg packages)
            String imageKey = ISharedImages.IMG_OBJ_FOLDER;
            im = PlatformUI.getWorkbench().getSharedImages().getImage(imageKey);

            // images for Ecore classes
            if (obj instanceof EClass) {
                im = FileUtil.getImage("Class");
            }
            return im;
        }

        private String getTextLabel(EObject eo) {
            if (eo instanceof ENamedElement) // for named parts of Ecore models: classes and packages
            {
                return ((ENamedElement) eo).getName();
            } else
                return eo.eClass().getName();
        }

    }

    //---------------------------------------------------------------------------------------------
    //           Setting up a  vanilla class model for the content provider and label provider                   
    //---------------------------------------------------------------------------------------------

    /**
     * record a UML class model in the form used by the ViewContentProvider -
     * in the Hashtable of 'main' subclasses.
     * This method assumes the class model is in a single package
     * 
     * @param root the root object of the UML model, assumed to be an EPackage
     */
    public void setVanillaClassModel(EPackage root, URI classModelURI) {
        trace("Set vanilla class model");
        this.classModelURI = classModelURI;
        childVanillaClasses = new Hashtable<String, ArrayList<EClass>>();
        modelRoot = new ArrayList<EObject>();
        modelRoot.add(root);

        addClasses(root);

        /* ensure the viewer is expecting the right kind of content provider and label provider, 
         * and give it the root of the tree. */
        setupVanillaViewer(viewer);
        viewer.setInput(modelRoot);
    }

    /**
     * add the classes in a package to the model, and recursively add
     * the classes in all its sub-packages
     * @param aPackage
     */
    private void addClasses(EPackage aPackage) {
        // add the classes in this package
        for (Iterator<EObject> it = aPackage.eContents().iterator(); it.hasNext();) {
            EObject child = it.next();

            // record each Class under its superclasses, or under class Object
            if (child instanceof EClass)
                recordVanillaClass((EClass) child);

            // ignore other things in the package - imports etc.
            else {
            }
        }

        //do the same recursively for its sub-packages
        for (Iterator<EPackage> it = aPackage.getESubpackages().iterator(); it.hasNext();)
            addClasses(it.next());
    }

    /**
     * record each class under its first superclass, or under class 'Object' if it has none
     * @param c
     */
    private void recordVanillaClass(EClass c) {
        if (c.getESuperTypes().size() == 0) {
            addVanillaClassToSubClasses("Object", c);
        }
        // if the class has more than one superclass, choose the main superclass that it inherits most from
        else
            addVanillaClassToSubClasses(getMainSuperClass(c).getName(), c);
    }

    /**
     * When an EClass has more than one superclass, the 'main' superclass,
     * which will be its parent in the tree diagram of the mapped class model, is the 
     * superclass with the largest number of features (i.e from which which it inherits most features). 
     * When there is a tie for number of features, the class whose name is lexically first wins.
     * @param c the EClass whose main superclass is sought
     * @return the main EClass, or null if there are no superclasses
     */
    private EClass getMainSuperClass(EClass c) {
        EClass best = null;
        for (Iterator<EClass> it = c.getESuperTypes().iterator(); it.hasNext();) {
            EClass current = it.next();
            if (best == null) {
                best = current;
            } else if (current.getFeatureCount() > best.getFeatureCount()) {
                best = current;
            } else if ((current.getFeatureCount() == best.getFeatureCount())
                    && (current.getName().compareTo(best.getName()) < 0)) {
                best = current;
            }
            // compareTo: The result is a negative integer if this String object lexicographically precedes the argument string
        }
        return best;
    }

    /**
     * record a class under one of its superclasses
     * @param superClass
     * @param c
     */
    private void addVanillaClassToSubClasses(String superClass, EClass c) {
        ArrayList<EClass> subs = childVanillaClasses.get(superClass);
        if (subs == null)
            subs = new ArrayList<EClass>();
        ArrayList<EClass> newSubs = addAClass(subs, c);
        childVanillaClasses.put(superClass, newSubs);
    }

    /**
     * @param subs existing list of subclasses
     * @param c new subclass
     * @return enlarged list of subclasses, preserving alphabetical order of class names
     */
    private ArrayList<EClass> addAClass(ArrayList<EClass> subs, EClass c) {
        ArrayList<EClass> newSubs = new ArrayList<EClass>();
        String cName = c.getName();
        boolean placed = false; // new class has not yet been added
        for (Iterator<EClass> it = subs.iterator(); it.hasNext();) {
            EClass next = it.next();
            // if the new class comes before the current class and has not been added, add it once
            if ((next.getName().compareTo(cName) > 0) && (!placed)) {
                newSubs.add(c);
                placed = true;
            }
            // add the current class in any case
            newSubs.add(next);
        }
        // if the new class is last, add it
        if (!placed)
            newSubs.add(c);
        return newSubs;
    }

    //---------------------------------------------------------------------------------------------
    //                content provider and label provider for a vanilla class model
    //---------------------------------------------------------------------------------------------

    /*
     * The provider (= adapter) which adapts the model (in this case an EMF model)
     * to the viewer (in this case a TreeViewer). 
     * There are two parts - a content provider which provides the tree structure
     * and a label provider which provides the icons and text
     */
    class ClassModelViewContentProvider implements IStructuredContentProvider, ITreeContentProvider {

        // called by viewer.setInput(Object) but need do nothing
        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        }

        public void dispose() {
            trace("vanilla dispose");
        }

        public Object[] getElements(Object parent) {
            // the only child of modelroot is the EPackage object 
            if (parent.equals(modelRoot)) {
                return arrayOf(modelRoot);
            }
            // for all other objects (including the package) use getChildren
            return getChildren(parent);
        }

        /**
         * child EObjects:
         * For the package, the children are the classes with no superclass
         * For any class, the children are (a) the subclasses, (b) the association markers (c) the properties
         * For any other EObject, get the EObject child nodes
         */
        public Object[] getChildren(Object parent) {
            ArrayList<EObject> result = new ArrayList<EObject>();

            String className = "NoClass"; // there will be no entry in subClasses map for this class name
            // direct children of the package are the subclasses of 'Object' = classes with no superclass
            if (parent instanceof EPackage) {
                className = "Object";
            } else if (parent instanceof EClass) {
                className = ((EClass) parent).getName();
            }
            if (childVanillaClasses.get(className) != null)
                for (Iterator<EClass> it = childVanillaClasses.get(className).iterator(); it.hasNext();) {
                    result.add(it.next());
                }
            return arrayOf(result);
        }

        /**
         * get the parent object of any object in the tree. For EClasses, this is the 
         * 'main' superclass, or the package if there are no superclasses
         */
        public Object getParent(Object child) {
            EObject parent = null;
            if (child instanceof EClass) {
                parent = getMainSuperClass((EClass) child);
                if (parent == null)
                    parent = ecoreRoot;
            }
            return parent;
        }

        public boolean hasChildren(Object parent) {
            return (getChildren(parent).length > 0);
        }
    } // end of class ViewContentProvider

    private EObject[] arrayOf(ArrayList<EObject> aList) {
        return (EObject[]) aList.toArray(new EObject[aList.size()]);
    }

    class ClassModelViewLabelProvider extends LabelProvider implements ITableLabelProvider {

        public String getText(Object obj) {
            if (obj instanceof EObject)
                return getTextLabel((EObject) obj);
            return "not an EObject";
        }

        public String getColumnText(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getText(obj);
            case 1: {
                if (obj instanceof EClass)
                    return getObjectMappingText((EClass) obj);
            }
            }
            return ("");
        }

        public Image getColumnImage(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getImage(obj);
            case 1:
                return null;
            }
            return null;
        }

        public Image getImage(Object obj) {
            Image im = null;
            // default 'Folder' image if type is not recognised (eg packages)
            String imageKey = ISharedImages.IMG_OBJ_FOLDER;
            im = PlatformUI.getWorkbench().getSharedImages().getImage(imageKey);

            // images for Ecore classes
            if (obj instanceof EClass) {
                im = FileUtil.getImage("Class");
            }
            return im;
        }

        private String getTextLabel(EObject eo) {
            if (eo instanceof ENamedElement) // for named parts of Ecore models: classes and packages
            {
                return ((ENamedElement) eo).getName();
            } else if (eo instanceof XSDNamedComponent) // for XML schemas
            {
                return eo.eClass().getName() + ": " + ((XSDNamedComponent) eo).getName();
            } else
                return eo.eClass().getName();
        }

    }

    //---------------------------------------------------------------------------------------------
    //                             setup for an RMIM  class model
    //---------------------------------------------------------------------------------------------

    /**
     * find the LabelledEClass which is the root of the class tree of the RMIM
     */
    public static LabelledEClass getRootLabelledEClass(EPackage topPackage) {
        LabelledEClass rootLabelledEClass = null;

        /* find the one EClass marked as the entry class; 
         * iterate over all nested packages to find it. */
        for (Iterator<EPackage> ip = topPackage.getESubpackages().iterator(); ip.hasNext();) {
            EPackage rmimPackage = ip.next();
            // Usually there is on package inside the top package. Iterate over classes in any packages found
            for (Iterator<EClassifier> ic = rmimPackage.getEClassifiers().iterator(); ic.hasNext();) {
                EClassifier ec = ic.next();
                if ((ec instanceof EClass) && (ModelUtil.getMIFAnnotation(ec, "entry") != null)) {
                    rootLabelledEClass = new LabelledEClass((EClass) ec, null, null);
                }
            }
        }

        /* if the class is not found in any packages below the top package  (possibly because there are no such packages)
         * look for the class directly in the top package. */
        if (rootLabelledEClass == null) {
            // iterate over classes in the top package
            for (Iterator<EClassifier> ic = topPackage.getEClassifiers().iterator(); ic.hasNext();) {
                EClassifier ec = ic.next();
                if ((ec instanceof EClass) && (ModelUtil.getMIFAnnotation(ec, "entry") != null)) {
                    rootLabelledEClass = new LabelledEClass((EClass) ec, null, null);
                }
            }

        }

        return rootLabelledEClass;
    }

    /**    
     * @param topPackage the root package of the RMIM class model,
     * which has a single layer of sub-packages below it
     */
    public void setRMIMClassModel(EPackage topPackage, URI classModelURI) {
        timer = new Timer("Class model view");
        this.classModelURI = classModelURI;
        rmimRoot = new ArrayList<LabelledEClass>();
        trace("Setting RMIM " + classModelURI);
        parentTable = ModelUtil.parentTable(topPackage);

        topLabelledEClass = getRootLabelledEClass(topPackage);
        rmimRoot.add(topLabelledEClass);

        /* ensure the viewer is expecting the right kind of content provider and label provider, 
         * and give it the root of the tree. */
        setupRMIMViewer(viewer);
        viewer.setInput(rmimRoot);

        /*
        Vector<String> noDups = new Vector<String>();
        int maximum = 100000000;
        int dataTypeDepth = 0;
        int start = 0;
        int leafCount = countLeafNodes(start,dataTypeDepth,maximum,topLabelledEClass.eClass(),noDups);
        System.out.println("Leaf nodes in class model at '" + classModelURI.toString() + "': " +  leafCount);
        */
    }

    /**
     * count leaf nodes in the RMIM class tree, truncating any recursion, 
     * ignoring text content of elements, and ignoring occurrences of data type ANY
     * @param start the count up to this point
     * @param maximum a maximum it will not count beyond, to avoid memory overflow
     * @param theClass class whose subtree is being added to the count
     * @param noDups to truncate recursion whenever the same class name is encountered twice
     * @return
     */
    @SuppressWarnings("unused")
    private int countLeafNodes(int start, int dataTypeDepth, int maximum, EClass theClass, Vector<String> noDups) {
        // control depth into data type classes
        int MAX_DATATYPE_DEPTH = 1;
        int nextDTDepth = dataTypeDepth;
        if (theClass.getEPackage().getName().equals("datatypes"))
            nextDTDepth++;
        if (nextDTDepth > MAX_DATATYPE_DEPTH)
            return start;

        // control recursion of RMIM classes
        Vector<String> nextNoDups = new Vector<String>();
        for (Iterator<String> it = noDups.iterator(); it.hasNext();)
            nextNoDups.add(it.next());
        nextNoDups.add(theClass.getName());

        // attribute leaf nodes of this class
        int count = start + theClass.getEAttributes().size();

        // leaf nodes of classes in subtrees
        for (Iterator<EReference> ir = theClass.getEAllReferences().iterator(); ir.hasNext();) {
            EReference ref = ir.next();
            EClassifier ec = ref.getEType();
            if ((ec instanceof EClass) && (ref.isContainment()) && (count < maximum)) {
                EClass next = (EClass) ec;
                String name = next.getName();
                if ((!GenUtil.inVector(name, nextNoDups)) && (!name.equals("ANY")))
                    count = countLeafNodes(count, nextDTDepth, maximum, next, nextNoDups);
            }
        }
        return count;
    }

    //---------------------------------------------------------------------------------------------
    //                content provider and label provider for an RMIM class model
    //---------------------------------------------------------------------------------------------

    /*
     * The provider (= adapter) which adapts the model (in this case an EMF model)
     * to the viewer (in this case a TreeViewer). 
     * There are two parts - a content provider which provides the tree structure
     * and a label provider which provides the icons and text
     */
    class RMIMViewContentProvider implements IStructuredContentProvider, ITreeContentProvider {

        // called by viewer.setInput(Object) but need do nothing
        public void inputChanged(Viewer v, Object oldInput, Object newInput) {
        }

        public void dispose() {
            trace("RMIM dispose");
        }

        public Object[] getElements(Object parent) {
            // the only child of rmimRoot is the EPackage object 
            if (parent.equals(rmimRoot)) {
                return array2Of(rmimRoot);
            }
            // for all other objects (including the package) use getChildren
            return getChildren(parent);
        }

        /**
         * child LabelledEClass objects:
         * For the package, the children are the RMIM root class
         * For any other LabelledEClass, get the LabelledEClass nodes for the classes linked by RMIM associations
         */
        public Object[] getChildren(Object parent) {
            ArrayList<LabelledEClass> children = new ArrayList<LabelledEClass>();
            if (parent instanceof LabelledEClass)
                children = ((LabelledEClass) parent).getChildren();
            return array2Of(children);
        }

        private LabelledEClass[] array2Of(ArrayList<LabelledEClass> aList) {
            return (LabelledEClass[]) aList.toArray(new LabelledEClass[aList.size()]);
        }

        /**
         * get the parent object of any object in the tree. 
         */
        public Object getParent(Object child) {
            LabelledEClass parent = null;
            if (child instanceof LabelledEClass) {
                parent = ((LabelledEClass) child).parent();
                if (parent == null) {
                    trace("null parent");
                    // parent = (LabelledEClass)child; // ??
                }
            }
            return parent;
        }

        public boolean hasChildren(Object parent) {
            return (getChildren(parent).length > 0);
        }

    } // end of class RMIMViewContentProvider

    class RMIMViewLabelProvider extends LabelProvider implements ITableLabelProvider {

        public String getColumnText(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getText(obj);
            case 1: {
                if (obj instanceof LabelledEClass)
                    return getObjectMappingText((LabelledEClass) obj);
            }
            }
            return ("");
        }

        public Image getColumnImage(Object obj, int columnIndex) {
            switch (columnIndex) {
            case 0:
                return getImage(obj);
            case 1:
                return null;
            }
            return null;
        }

        // label an RMIM class  by the association leading to it, followed by the class name
        public String getText(Object obj) {
            if (obj instanceof LabelledEClass) {
                LabelledEClass lc = (LabelledEClass) obj;
                String name = lc.eClass().getName();
                if (lc.associationName() != null)
                    name = lc.associationName() + "." + name;
                EPackage CMETPackage = lc.eClass().getEPackage();
                String CMETName = ModelUtil.getEAnnotationDetail(CMETPackage, "name");
                if ((CMETName != null) && (!CMETName.equals("")))
                    name = name + " (" + CMETName + ")";
                return name;
            }
            return "not a LabelledEClass";
        }

        public Image getImage(Object obj) {
            Image im = null;
            // default 'Folder' image if type is not recognised
            String imageKey = ISharedImages.IMG_OBJ_FOLDER;
            im = PlatformUI.getWorkbench().getSharedImages().getImage(imageKey);

            // images for RMIM classes and data type classes
            if (obj instanceof LabelledEClass) {
                EClass theClass = ((LabelledEClass) obj).eClass();
                String RIMClass = ModelUtil.getMIFAnnotation(theClass, "RIM Class");
                String imageName = "Class" + getColour(RIMClass); // "ClassGreen", "ClassRed", etc.

                String packageName = theClass.getEPackage().getName();
                if (packageName.equals("datatypes"))
                    imageName = "DataType";
                im = FileUtil.getImage(imageName);
            }
            return im;
        }

        /**
         * @param RIMClass
         * @return a string for the colour which clones of that class have in an RMIM diagram
         */
        private String getColour(String RIMClass) {
            String colour = "";
            if (RIMClass != null) {
                if (GenUtil.inArray(RIMClass, redClasses))
                    colour = "Red";
                if (GenUtil.inArray(RIMClass, pinkClasses))
                    colour = "Pink";
                if (GenUtil.inArray(RIMClass, blueClasses))
                    colour = "Blue";
                if (GenUtil.inArray(RIMClass, yellowClasses))
                    colour = "Yellow";
                if (GenUtil.inArray(RIMClass, greenClasses))
                    colour = "Green";
            }
            return colour;
        }

    }

    // HL7 colouring of RIM classes
    static String[] redClasses = { "Act", "ControlAct", "Observation", "DiagnosticImage", "PublicHealthCase",
            "Supply", "Diet", "DeviceTask", "FinancialContract", "InvoiceElement", "FinancialTransaction",
            "Account", "PatientEncounter", "SubstanceAdministration", "WorkingList", "Exposure", "Procedure" };
    static String[] pinkClasses = { "ActRelationship" };
    static String[] blueClasses = { "Participation", "ManagedParticipation" };
    static String[] yellowClasses = { "Role", "RoleLink", "Patient", "LicensedEntity", "QualifiedEntity", "Access",
            "Employee" };
    static String[] greenClasses = { "Entity", "Place", "Person", "LivingSubject", "NonPersonLivingSubject",
            "Organization", "Material", "Device", "ManufacturedMaterial", "Container" };

    //----------------------------------------------------------------------------------------
    //                  Label Provider for mapping columns 
    //----------------------------------------------------------------------------------------

    /**
     * @return MDLBase the mappings in the mapping set being edited, 
     * with fast retrieval methods
     */
    public MDLBase mdlBase() {
        // if there is no current Mapper editor, return no MDLBase
        if (mapperEditor == null)
            return null;
        if (mdlBase == null)
            refreshMDLBase();
        else if (mdlBase != null) {
            // if the mappings have been altered since the MDBase was last refreshed, refresh it now
            if (!WorkBenchUtil.mappingRoot(mapperEditor).classModelViewIsRefreshed())
                refreshMDLBase();
        }
        return mdlBase;
    }

    private void refreshMDLBase() {
        messageChannel mc = new SystemMessageChannel();
        try {
            mdlBase = new MDLBase(WorkBenchUtil.mappingRoot(mapperEditor), mc);
            // record that the MDLBase has been refreshed
            WorkBenchUtil.mappingRoot(mapperEditor).setClassModelViewIsRrefreshed(true);
        } catch (MapperException ex) {
            GenUtil.surprise(ex, "ClassModelView.mdlBase");
        }
    }

    /**
     * @param ec a class
     * @return a text description of the object mappings to the class and its
     * subclasses, of the form (S)paths where S is the number of object mappings to 
     * its proper subclasses, and 'paths' is the concatenated paths of the mappings to the class itself.
     */
    private String getObjectMappingText(EClass ec) {
        String end = ";   ";
        String text = "";
        if (mdlBase() != null)
            try {
                String className = ModelUtil.getQualifiedClassName(ec);
                for (Iterator<objectMapping> it = mdlBase().objectMappings(className).iterator(); it.hasNext();) {
                    objectMapping om = it.next();
                    String path = om.nodePath().stringForm();
                    if (om.hasSubset())
                        path = path + "(" + om.subset() + ")";
                    text = text + path + end;
                }
                int mappingsToSubclasses = 0;
                for (Iterator<EClass> it = mdlBase().ms().getAllSubClasses(ec).iterator(); it.hasNext();) {
                    EClass ed = it.next();
                    if ((ec.isSuperTypeOf(ed)) && (ec != ed)) {
                        String subclassName = ModelUtil.getQualifiedClassName(ed);
                        mappingsToSubclasses = mappingsToSubclasses + mdlBase().objectMappings(subclassName).size();
                    }
                }
                if (mappingsToSubclasses > 0)
                    text = "[" + mappingsToSubclasses + "] " + text;
            } catch (MapperException ex) {
            }
        if (text.endsWith(end))
            text = text.substring(0, text.length() - end.length());
        return text;
    }

    /**
     * @param lec a labelled EClass
     * @return a text description of the object mappings to this occurrence of the class
     * at this point of the RMIM tree;
     * or its template id if it has one
     */
    private String getObjectMappingText(LabelledEClass lec) {
        String path = "";
        EClass ec = lec.eClass();
        LabelledEClass parent = lec.parent();
        String className = ModelUtil.getQualifiedClassName(ec);

        // modification to show template ids in stead of a mapping, if there are any
        String templateId = ModelUtil.getMIFAnnotation(ec, "template");
        if (templateId != null) {
            boolean found = true;
            int index = 1;
            // find all annotations with keys 'template_1','template_2', etc.
            while (found) {
                String nextId = ModelUtil.getMIFAnnotation(ec, "template_" + new Integer(index).toString());
                if (nextId != null)
                    templateId = templateId + ";  " + nextId;
                else
                    found = false;
                index++;
            }
            return templateId;
        }

        /* a class may only show some mapping text if (a) it is the parameter class 
         * of the mapping set or (b) it is the root class or (c) its parent has 
         * a mapping (already determined, as the parent node is visible)*/
        boolean eligible = ((isParameterClass(lec)) || (parent == null)
                || ((parent != null) && (parent.isMapped())));

        if ((mdlBase() != null) && eligible)
            try {
                // don't carry on looking after you have found the right object mapping
                boolean isLinkedToParent = false;
                // there may be many object mappings to this class, most of which are elsewhere in the tree
                for (Iterator<objectMapping> it = mdlBase().objectMappings(className).iterator(); it.hasNext();) {
                    objectMapping om = it.next();
                    if (!isLinkedToParent) {

                        // top of the RMIM tree, or top (parameter) class of an imported mapping set
                        if ((isParameterClass(lec)) | (parent == null))
                            path = setTheObjectMappingText(lec, om);

                        /* for any  class not at the top of the RMIM tree, its parent class must be 
                         * mapped, and the named association to this class must also be mapped 
                         * with the correct subsets.  */
                        else {
                            String parentClass = ModelUtil.getQualifiedClassName(parent.eClass());
                            String parentSubset = parent.getMappedSubset();
                            isLinkedToParent = (mdlBase().representsAssociationRoleLocally(parentClass,
                                    parentSubset, lec.associationName(), className, om.subset()));
                            if (isLinkedToParent)
                                path = setTheObjectMappingText(lec, om);
                        }
                    }
                }
            } catch (Exception ex) {
            } // odd null pointers can occur when mappings have been deleted
        return path;
    }

    private String setTheObjectMappingText(LabelledEClass lec, objectMapping om) {
        String path = om.nodePath().stringForm();
        if (om.hasSubset())
            path = path + "(" + om.subset() + ")";
        lec.setObjectMappingText(path);
        lec.setMappedSubset(om.subset());
        return path;
    }

    /**    * 
     * @param lec
     * @return true if the EClass is the parameter class of the mapping set
     */
    private boolean isParameterClass(LabelledEClass lec) {
        boolean isParamClass = false;
        if (mapperEditor != null) {
            MappedStructure ms = WorkBenchUtil.mappingRoot(mapperEditor);
            if (ms.getParameterClasses().size() > 0) {
                ParameterClass pc = ms.getParameterClasses().get(0);
                String paramClassName = pc.getQualifiedClassName();
                String mappedClassName = ModelUtil.getQualifiedClassName(lec.eClass());
                isParamClass = (mappedClassName.equals(paramClassName));
            }
        }
        return isParamClass;
    }

    /**
     * 
     * @return if the mapping set has a  parameter class, return the 
     * qualified parameter class name; otherwise return null
     */
    private String parameterClassName() {
        String paramClassName = null;
        if (mapperEditor != null) {
            MappedStructure ms = WorkBenchUtil.mappingRoot(mapperEditor);
            if (ms.getParameterClasses().size() > 0) {
                ParameterClass pc = ms.getParameterClasses().get(0);
                paramClassName = pc.getQualifiedClassName();
            }
        }
        return paramClassName;
    }

    /**
     * label provider for the mappings column of the class model or RMIM model
     * @author robert
     *
     */
    class MappingLabelProvider extends ColumnLabelProvider {

        public MappingLabelProvider() {
        }

        public String getText(Object element) {
            String text = "";
            System.out.println("getting text from label provider");

            // view of a vanilla class model
            if ((element instanceof EClass) && (mdlBase() != null)) {
                EClass ec = (EClass) element;
                text = getObjectMappingText(ec);
            }
            // view of an RMIM class model
            else if ((element instanceof LabelledEClass) && (mdlBase() != null)) {
                EClass ec = ((LabelledEClass) element).eClass();
                text = getObjectMappingText(ec);
            }
            return text;
        }
    }

    //----------------------------------------------------------------------------------------
    //                  Actions for the pull-down menu
    //----------------------------------------------------------------------------------------

    private Action saveClassModelChangesAction;
    private Action saveClassModelAsAction;
    private Action showLastSelectedClassAction;
    private MakeEcoreMappingsAction makeEcoreMappingsAction;
    private ImportSimplificationsAction importSimplificationsAction;
    private CopySimplificationsAction copySimplificationsAction;
    private PasteSimplificationsAction pasteSimplificationsAction;
    private RemoveSimplificationsAction removeSimplificationsAction;
    private MergeModelsAction mergeModelsAction;

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        fillLocalPullDown(bars.getMenuManager());
    }

    private void fillLocalPullDown(IMenuManager manager) {
        manager.add(importSimplificationsAction);
        manager.add(copySimplificationsAction);
        manager.add(pasteSimplificationsAction);
        manager.add(removeSimplificationsAction);
        manager.add(mergeModelsAction);
        manager.add(saveClassModelChangesAction);
        manager.add(saveClassModelAsAction);
        manager.add(showLastSelectedClassAction);
        manager.add(makeEcoreMappingsAction);
    }

    /**
     * make the actions for the pulldown menu in the Mapped Class Model view
     */
    private void makeActions() {

        saveClassModelChangesAction = new Action() {
            public void run() {
                FileUtil.saveResource(ecoreRoot().eResource());
            }
        };
        saveClassModelChangesAction.setText("Save Changes in Class Model");
        saveClassModelChangesAction.setToolTipText("Save simplification annotation changes in class model");
        saveClassModelChangesAction.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));

        saveClassModelAsAction = new Action() {
            public void run() {
                doWriteClassModel();
            }
        };
        saveClassModelAsAction.setText("Save Class Model as..");
        saveClassModelAsAction.setToolTipText("Write out a class model to be used to generate Java classes");
        saveClassModelAsAction.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));

        showLastSelectedClassAction = new Action() {
            public void run() {
                String path = EclipseFileUtil.getSelectedClassPath(classModelURI.toString());
                TreePath pathToLastClass = getLabelledTreePath(path);
                if (pathToLastClass != null) {
                    viewer.setSelection(new TreeSelection(pathToLastClass), true); // make it visible               
                }
            }
        };
        showLastSelectedClassAction.setText("Show Last Selected Class");
        showLastSelectedClassAction
                .setToolTipText("Expand the tree to show the class model last selected by the user");
        showLastSelectedClassAction.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_OBJS_INFO_TSK));

        makeEcoreMappingsAction = new MakeEcoreMappingsAction(this);

        importSimplificationsAction = new ImportSimplificationsAction();
        copySimplificationsAction = new CopySimplificationsAction();
        pasteSimplificationsAction = new PasteSimplificationsAction();
        removeSimplificationsAction = new RemoveSimplificationsAction();
        mergeModelsAction = new MergeModelsAction();
    }

    /**
     * run the action to write the class model as an ecore file, for later code generation
     */
    private void doWriteClassModel() {
        SaveAsDialog saveAsDialog = new SaveAsDialog(getSite().getShell());
        saveAsDialog.open();
        IPath path = saveAsDialog.getResult();
        if (path != null) {
            // use the platform URI, not the absolute one, and Eclipse will see the resource
            URI resURI = URI.createPlatformResourceURI(path.toString(), true);
            saveAtURI(ecoreRoot, resURI, "ecore");
        }
    }

    /**
     * Save an Ecore model at a given URI
     * @param model an Ecore model
     * @param resURI the URI it is to be saved at
     */
    public void saveAtURI(EPackage model, URI resURI, String extension) {
        ResourceSet resourceSet = new ResourceSetImpl();

        resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(extension,
                new XMIResourceFactoryImpl());

        Resource resource = resourceSet.createResource(resURI);
        resource.getContents().add(model);
        try {
            resource.save(null);
        } catch (IOException ex) {
            System.out.println("Exception saving ecore resource at " + resURI.toString() + ": " + ex.getMessage());
        }
    }

    /**
     * Passing the focus request to the viewer's control.
     */
    public void setFocus() {
        viewer.getControl().setFocus();
    }

    //------------------------------------------------------------------------------------------
    //                        saving and restoring the state of the view
    //------------------------------------------------------------------------------------------

    private static final String TAG_MAPPING_SET_URI = "mappingSetURI";
    private static final String TAG_CLASS_MODEL_URI = "instanceURI";
    private static final String TAG_QUERY_URI = "queryURI";

    public void saveState(IMemento memento) {
        super.saveState(memento);
        if (classModelURI() != null)
            memento.putString(TAG_CLASS_MODEL_URI, classModelURI().toString());

        // at most one of mappingSetURI and queryURI will be non-null
        if (mappingSetURI() != null)
            memento.putString(TAG_MAPPING_SET_URI, mappingSetURI().toString());
        if (queryURI() != null)
            memento.putString(TAG_QUERY_URI, queryURI().toString());
    }

    /**
     * re-initialise the model from its state at last closedown
     * Note the viewer does not exist when this runs.
     */
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);

        if (memento != null) {
            String cmURI = memento.getString(TAG_CLASS_MODEL_URI);
            if (cmURI != null)
                classModelURI = URI.createURI(cmURI);

            // at most one of mappingSetURI and queryURI will be non-null
            String msURI = memento.getString(TAG_MAPPING_SET_URI);
            if (msURI != null)
                setMappingSetURI(URI.createURI(msURI));

            String quURI = memento.getString(TAG_QUERY_URI);
            if (quURI != null)
                setQueryURI(URI.createURI(quURI));
        }
    }

    //----------------------------------------------------------------------------------------
    //                  Expanding the tree view to show a mapped class
    //----------------------------------------------------------------------------------------

    /**
     * @param qualifiedClassName the package name and class name of the class whose mapping has been selected
     */
    public void showMappedClass(String qualifiedClassName, String subset) {
        // RMIM case; find the named LabelledEClass and expand to it
        if ((isRMIMRoot(ecoreRoot)) && (subset != null)) {
            TreePath startPath = TreePath.EMPTY.createChildPath(topLabelledEClass);
            if (parameterClassName() != null)
                startPath = extendToParameterClass(startPath, parameterClassName());
            TreePath namedPath = findLabelledPath(qualifiedClassName, subset, startPath);
            if (namedPath != null) {
                viewer.setSelection(new TreeSelection(namedPath), true); // make it visible
            } else {
                System.out.println("Null tree path");
            }
        }

        // non-RMIM case; find the named EClass and expand to it
        if (!(isRMIMRoot(ecoreRoot))) {
            EClass namedClass = ModelUtil.getNamedClass(ecoreRoot, qualifiedClassName);
            if (namedClass != null) {
                viewer.expandToLevel(namedClass, 0);
                viewer.setSelection(new StructuredSelection(namedClass));
            }
        }
    }

    /**
     * recursive descent of a tree of mapped LabelledEClasses,
     * looking for a class with the required class name and subset
     * @param className
     * @param subset 
     * @param path
     */
    private TreePath findLabelledPath(String className, String subset, TreePath path) {
        if (path == null)
            return null;
        LabelledEClass lc = (LabelledEClass) path.getLastSegment();
        // find out if the class is mapped, and if so, set its subset
        getObjectMappingText(lc);
        /* only try mapped classes and their mapped descendants 
         * (usually any mapped class has all mapped ancestors) */
        if (lc.getMappedSubset() != null) {
            String cName = ModelUtil.getQualifiedClassName(lc.eClass());
            if ((subset.equals(lc.getMappedSubset())) && (className.equals(cName)))
                return path;
            else
                for (Iterator<LabelledEClass> it = lc.getChildren().iterator(); it.hasNext();) {
                    TreePath childPath = path.createChildPath(it.next());
                    TreePath targetPath = findLabelledPath(className, subset, childPath);
                    if (targetPath != null)
                        return targetPath;
                }
        }
        return null;
    }

    /**
     * @param startPath a TreePath with one step - the entry class of the RMIM
     * @param parameterClassName  name of the parameter class of the mapping set
     * @return a TreePath to one example of the parameter class (there may be several)
     * found by a tree search cut off at repeated class names
     */
    private TreePath extendToParameterClass(TreePath startPath, String parameterClassName) {
        TreePath currentPath = startPath;

        // from parents of each class, find a path of parent classes to the top class
        Vector<String> parentsToRoot = new Vector<String>();
        parentsToRoot.add(parameterClassName);
        String topClassName = ModelUtil.getQualifiedClassName(topLabelledEClass.eClass());
        parentsToRoot = extendToAncestorClass(parentsToRoot, topClassName);

        // use these to construct a TreePath of LabelledEClass objects
        if (parentsToRoot != null) {
            for (int i = 1; i < parentsToRoot.size(); i++) {
                LabelledEClass current = (LabelledEClass) currentPath.getLastSegment();
                boolean found = false;
                for (Iterator<LabelledEClass> ic = current.getChildren().iterator(); ic.hasNext();) {
                    LabelledEClass child = ic.next();
                    if (ModelUtil.getQualifiedClassName(child.eClass()).equals(parentsToRoot.get(i))) {
                        found = true;
                        currentPath = currentPath.createChildPath(child);
                    }
                }
                if (!found)
                    return null;
            }
            return currentPath;
        }
        return null;
    }

    private Vector<String> extendToAncestorClass(Vector<String> parentsToRoot, String className) {
        if (parentsToRoot.get(0).equals(className))
            return parentsToRoot;
        else {
            Vector<String> parents = parentTable.get(parentsToRoot.get(0));
            for (Iterator<String> ip = parents.iterator(); ip.hasNext();) {
                String parent = ip.next();
                if (!GenUtil.inVector(parent, parentsToRoot)) {
                    Vector<String> newTrail = new Vector<String>();
                    newTrail.add(parent);
                    for (Iterator<String> is = parentsToRoot.iterator(); is.hasNext();)
                        newTrail.add(is.next());
                    Vector<String> result = extendToAncestorClass(newTrail, className);
                    if (result != null)
                        return result;
                }
            }
        }
        return null;
    }

    /**
     * 
     * @param path
     * @return the treePath of LabelledEClasses got by following a string path,
     * or null if it cannot be followed
     */
    private TreePath getLabelledTreePath(String path) {
        TreePath result = null;
        if (path != null) {
            StringTokenizer steps = new StringTokenizer(path, "/");
            String topName = steps.nextToken(); // check the first step, which should be the name of the top class
            if (topName.equals(topLabelledEClass.eClass().getName())) {
                result = TreePath.EMPTY.createChildPath(topLabelledEClass);
                LabelledEClass currentClass = topLabelledEClass;
                while ((steps.hasMoreTokens()) && (currentClass != null)) {
                    String step = steps.nextToken();
                    currentClass = currentClass.getNamedAssocChild(step);
                    if (currentClass != null)
                        result = result.createChildPath(currentClass);
                    else
                        result = null;
                }
            }
        }
        return result;
    }

    //-----------------------------------------------------------------------------------------------------------------
    //     Listening for the selection of a new class in this View, for copy and paste of simplification annotations,
    //     and also to reopen the class model at the last selected class
    //-----------------------------------------------------------------------------------------------------------------

    private LabelledEClass selectedLabelledEClass = null;

    /** get the node that has been selected, for copy and paste of simplification annotations */
    public LabelledEClass getSelectedLabelledEClass() {
        return selectedLabelledEClass;
    }

    private LabelledEClass copiedLabelledEClass = null;

    /** set the node that has been copied, for later paste of simplification annotations */
    public void setCopiedLabelledEClass(LabelledEClass copied) {
        copiedLabelledEClass = copied;
    }

    /** get the node that has been copied, for later paste of simplification annotations */
    public LabelledEClass getCopiedLabelledEClass() {
        return copiedLabelledEClass;
    }

    /**
     * remember the node selected in this view, as a local variable
     * and remember the path to it as a local variable and in an XML file in the workspace
     */
    public void selectionChanged(SelectionChangedEvent event) {
        ISelection selection = event.getSelection();
        if (selection instanceof IStructuredSelection && ((IStructuredSelection) selection).size() == 1) {
            Object object = ((IStructuredSelection) selection).getFirstElement();

            // RMIM class model view
            if (object instanceof LabelledEClass) {
                selectedLabelledEClass = (LabelledEClass) object;
                String path = selectedLabelledEClass.getPath();
                try {
                    EclipseFileUtil.saveClassPath(classModelURI().toString(), path);
                } catch (MapperException ex) {
                    trace("Exception saving path to selected class:" + ex.getMessage());
                }
            }
        }
    }

    //----------------------------------------------------------------------------------------
    //                          Trivia
    //----------------------------------------------------------------------------------------

    private void trace(String s) {
        if (tracing)
            System.out.println(s);
    }

}