org.kalypso.ui.editor.gmleditor.part.GMLContentProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.kalypso.ui.editor.gmleditor.part.GMLContentProvider.java

Source

/*
 * --------------- Kalypso-Header --------------------------------------------------------------------
 *
 * This file is part of kalypso. Copyright (C) 2004, 2005 by:
 *
 * Technical University Hamburg-Harburg (TUHH) Institute of River and coastal engineering Denickestr. 22 21073 Hamburg,
 * Germany http://www.tuhh.de/wb
 *
 * and
 *
 * Bjoernsen Consulting Engineers (BCE) Maria Trost 3 56070 Koblenz, Germany http://www.bjoernsen.de
 *
 * This library 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 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library 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 this library; if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Contact:
 *
 * E-Mail: belger@bjoernsen.de schlienger@bjoernsen.de v.doemming@tuhh.de
 *
 * ---------------------------------------------------------------------------------------------------
 */
package org.kalypso.ui.editor.gmleditor.part;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.namespace.QName;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.progress.UIJob;
import org.kalypso.commons.java.lang.Objects;
import org.kalypso.contribs.eclipse.core.runtime.StatusUtilities;
import org.kalypso.gmlschema.property.IPropertyType;
import org.kalypso.gmlschema.property.relation.IRelationType;
import org.kalypso.ui.KalypsoGisPlugin;
import org.kalypso.ui.catalogs.FeatureTypePropertiesCatalog;
import org.kalypso.ui.catalogs.IFeatureTypePropertiesConstants;
import org.kalypso.ui.internal.i18n.Messages;
import org.kalypsodeegree.model.feature.Feature;
import org.kalypsodeegree.model.feature.FeatureList;
import org.kalypsodeegree.model.feature.GMLWorkspace;
import org.kalypsodeegree.model.feature.IXLinkedFeature;
import org.kalypsodeegree.model.feature.event.FeatureStructureChangeModellEvent;
import org.kalypsodeegree.model.feature.event.FeaturesChangedModellEvent;
import org.kalypsodeegree.model.feature.event.ModellEvent;
import org.kalypsodeegree.model.feature.event.ModellEventListener;
import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPath;
import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPathException;
import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPathSegment;
import org.kalypsodeegree_impl.model.feature.gmlxpath.GMLXPathUtilities;

/**
 * @author Christoph Kpferle
 * @author Gernot Belger
 */
public class GMLContentProvider implements ITreeContentProvider {
    private static final GMLXPath EMPTY_ROOT_PATH = new GMLXPath("", null);//$NON-NLS-1$

    private final ModellEventListener m_workspaceListener = new ModellEventListener() {
        @Override
        public void onModellChange(final ModellEvent modellEvent) {
            handleModelChanged(modellEvent);
        }
    };

    private final Map<QName, Boolean> m_showChildrenOverrides = new HashMap<>();

    private TreeViewer m_viewer;

    private GMLWorkspace m_workspace;

    /**
     * The x-path to the currently (not visible) root element. Its children are the root elements of the tree.
     * <p>
     * If null, the root-feature is the root element of the tree.
     */
    private GMLXPath m_rootPath = EMPTY_ROOT_PATH; //$NON-NLS-1$

    private boolean m_showAssociations;

    private final boolean m_handleModelEvents;

    public GMLContentProvider(final boolean showAssociations) {
        this(showAssociations, true);
    }

    /**
     * Likely to be replaced by {@link #GMLContentProvider(boolean)} in the future.<br>
     * 
     * @param handleModelEvents
     *          Only for backwards compability. Should always be set to <code>true</code>.
     */
    public GMLContentProvider(final boolean showAssociations, final boolean handleModelEvents) {
        m_showAssociations = showAssociations;
        m_handleModelEvents = handleModelEvents;
    }

    /**
     * Gets the children and updates the parent-hash.
     * 
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    @Override
    public Object[] getChildren(final Object parentElement) {
        if (m_workspace == null)
            return new Object[] {};

        // Test first if we should show children
        final QName qname = getQName(parentElement);
        final boolean showChildren = isShowChildren(qname);
        if (!showChildren)
            return new Object[] {};

        return getChildrenInternal(parentElement);
    }

    private boolean isShowChildren(final QName qname) {
        if (qname == null)
            return true;

        if (m_showChildrenOverrides.containsKey(qname))
            return m_showChildrenOverrides.get(qname);

        return FeatureTypePropertiesCatalog.isPropertyOn(qname, m_workspace.getContext(),
                IFeatureTypePropertiesConstants.GMLTREE_SHOW_CHILDREN);
    }

    public static QName getQName(final Object element) {
        if (element instanceof Feature)
            return ((Feature) element).getQualifiedName();

        if (element instanceof FeatureAssociationTypeElement)
            return ((FeatureAssociationTypeElement) element).getPropertyType().getQName();

        return null;
    }

    private Object[] getChildrenInternal(final Object parentElement) {
        if (parentElement instanceof GMLWorkspace)
            return new Object[] { ((GMLWorkspace) parentElement).getRootFeature() };

        final List<Object> result = new ArrayList<>();
        if (parentElement instanceof Feature)
            collectFeatureChildren(parentElement, result);
        else if (parentElement instanceof FeatureAssociationTypeElement)
            collectAssociationChildren((FeatureAssociationTypeElement) parentElement, result);
        else if (parentElement instanceof FeatureList)
            return ((FeatureList) parentElement).toArray(); // can happen in !showAssociation's mode

        return result.toArray();
    }

    private void collectFeatureChildren(final Object parentElement, final List<Object> result) {
        final Feature parentFE = (Feature) parentElement;
        final IPropertyType[] properties = parentFE.getFeatureType().getProperties();

        for (final IPropertyType property : properties) {
            if (property instanceof IRelationType) {
                final FeatureAssociationTypeElement fate = new FeatureAssociationTypeElement(
                        (Feature) parentElement, (IRelationType) property);

                /* suppress explicitely hidden children */
                final QName propertyName = property.getQName();
                final boolean childHiddenInTree = FeatureTypePropertiesCatalog.getInstance()
                        .isChildHiddenInTree(parentFE.getQualifiedName(), propertyName, m_workspace.getContext());
                if (childHiddenInTree)
                    continue;

                if (m_showAssociations)
                    result.add(fate);
                else {
                    final Object[] children = getChildren(fate);
                    result.addAll(Arrays.asList(children));
                }
            }
            // FIXME: showing the geometry does not make much sense (because we have no actions on it) and
            // is ugly as well, as we have no nice label provider for it. So this is suppressed for now.
            // else if( GeometryUtilities.isGeometry( property ) )
            // {
            // final Object value = parentFE.getProperty( property );
            // if( value != null )
            // result.add( value );
            // }
        }
    }

    private void collectAssociationChildren(final FeatureAssociationTypeElement fate, final List<Object> result) {
        final Feature parentFeature = fate.getOwner();
        final IRelationType ftp = fate.getPropertyType();

        final Feature[] features = m_workspace.resolveLinks(parentFeature, ftp);
        for (int i = 0; i < features.length; i++) {
            final Feature feature = features[i];
            if (feature != null) {
                if (isLink(parentFeature, ftp, i))
                    result.add(new LinkedFeatureElement(fate, feature));
                else
                    result.add(feature);
            }
        }
    }

    /**
     * FIXME: this method does not use any members of this class and so does not depends on this specific implementation.
     * Move it into a utility class.
     */
    private boolean isLink(final Feature parent, final IRelationType linkProp, final int pos) {
        final Object value = parent.getProperty(linkProp);

        if (linkProp.isList()) {
            final List<?> list = (List<?>) value;
            if (pos < 0 || pos >= list.size())
                return false;

            final Object object = list.get(pos);
            return isLinkObject(object);
        }

        return isLinkObject(value);
    }

    private boolean isLinkObject(final Object object) {
        if (object instanceof IXLinkedFeature)
            return true;

        if (object instanceof String)
            return true;

        return false;
    }

    @Override
    public Object getParent(final Object element) {
        /* search is orderd from fast to slow */

        /* If its an association we know the parent */
        if (element instanceof FeatureAssociationTypeElement) {
            final FeatureAssociationTypeElement fate = (FeatureAssociationTypeElement) element;
            return fate.getOwner();
        }

        /* Is it one of the root elements? */
        final Object[] elements = getElements(m_workspace);
        for (final Object object : elements)
            if (object == element)
                return null;

        // TODO: there are also GeometryProperty-Elements

        if (element instanceof Feature) {
            final Feature feature = (Feature) element;
            final Feature parent = feature.getOwner();

            if (!m_showAssociations)
                return parent;

            if (parent != null) {
                final Object[] parentChildren = getChildren(parent);
                for (final Object object : parentChildren)
                    if (object instanceof FeatureAssociationTypeElement) {
                        final FeatureAssociationTypeElement fate = (FeatureAssociationTypeElement) object;
                        final IRelationType associationTypeProperty = fate.getPropertyType();
                        final Object property = parent.getProperty(associationTypeProperty);
                        if (property == feature)
                            return fate;
                        else if (property instanceof List<?>) {
                            if (((List<?>) property).contains(feature))
                                return fate;
                        }
                    }
            }
        }

        /* May happen if we have gone-into the tree. */
        return null;
    }

    @Override
    public boolean hasChildren(final Object element) {
        if (element == null)
            return false;

        final int childCount = getChildren(element).length;
        return childCount > 0;
    }

    @Override
    public Object[] getElements(final Object inputElement) {
        if (m_workspace == null)
            return new Object[] {};

        final Object[] rootFeatureObjects = new Object[] { m_workspace.getRootFeature() };

        try {
            final Object object = findObjectForPath();
            if (object == m_workspace)
                return rootFeatureObjects;

            return getChildren(object);
        } catch (final GMLXPathException e) {
            final IStatus status = StatusUtilities.statusFromThrowable(e);
            KalypsoGisPlugin.getDefault().getLog().log(status);

            return rootFeatureObjects;
        }
    }

    private Object findObjectForPath() throws GMLXPathException {
        if (m_rootPath == null || m_rootPath.getSegmentSize() == 0)
            return m_workspace;

        final GMLXPath parentPath = m_rootPath.getParentPath();
        final Object parent = GMLXPathUtilities.query(parentPath, m_workspace);
        if (parent instanceof Feature) {
            final GMLXPathSegment segment = m_rootPath.getSegment(m_rootPath.getSegmentSize() - 1);

            final Object valueFromSegment = GMLXPathUtilities.getValueFromSegment(segment, parent, false);

            final Object[] children = getChildren(parent);
            for (final Object object : children)
                if (object instanceof FeatureAssociationTypeElement) {
                    final FeatureAssociationTypeElement fate = (FeatureAssociationTypeElement) object;

                    final Object property = ((Feature) parent).getProperty(fate.getPropertyType());
                    if (property == valueFromSegment)
                        return object;
                }
        }

        return GMLXPathUtilities.query(m_rootPath, m_workspace);
    }

    @Override
    public void dispose() {
        if (m_workspace != null)
            m_workspace.removeModellListener(m_workspaceListener);

        m_viewer = null;
        m_workspace = null;
    }

    @Override
    public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) {
        m_viewer = (TreeViewer) viewer;

        if (oldInput != newInput) {
            if (m_workspace != null) {
                m_workspace.removeModellListener(m_workspaceListener);
                m_workspace = null;
            }

            if (newInput instanceof GMLWorkspace) {
                m_workspace = (GMLWorkspace) newInput;
                m_workspace.addModellListener(m_workspaceListener);
            } else
                m_workspace = null;
        }
    }

    /** Expand the element and all of its parents */
    public void expandElement(final Object element) {
        if (element == null)
            return;

        expandElement(getParent(element));

        m_viewer.setExpandedState(element, true);
    }

    public void goInto(final Object object) {
        m_viewer.getSelection();
        final Object[] expandedElements = m_viewer.getExpandedElements();

        // prepare for exception
        m_rootPath = EMPTY_ROOT_PATH; //$NON-NLS-1$

        try {
            if (object instanceof Feature) {
                final Feature feature = (Feature) object;
                m_rootPath = new GMLXPath(feature);
            } else if (object instanceof FeatureAssociationTypeElement) {
                final FeatureAssociationTypeElement fate = (FeatureAssociationTypeElement) object;
                final Feature parentFeature = fate.getOwner();
                final GMLXPath path = new GMLXPath(parentFeature);
                m_rootPath = new GMLXPath(path, fate.getPropertyType().getQName());
            }
        } catch (final GMLXPathException e) {
            final IStatus status = StatusUtilities.statusFromThrowable(e);
            KalypsoGisPlugin.getDefault().getLog().log(status);
        } finally {
            m_viewer.refresh();
            m_viewer.setExpandedElements(expandedElements);
        }
    }

    public void goUp() {
        final Object[] expandedElements = m_viewer.getExpandedElements();

        try {
            final Object object = findObjectForPath();

            final Object parent = getParent(object);
            if (parent != null) {
                goInto(parent);
                return; // do not refresh, we already did so
            }
        } catch (final GMLXPathException e) {
            final IStatus status = StatusUtilities.statusFromThrowable(e);
            KalypsoGisPlugin.getDefault().getLog().log(status);
        }

        m_rootPath = EMPTY_ROOT_PATH; //$NON-NLS-1$

        m_viewer.setExpandedElements(expandedElements);
        m_viewer.refresh();
    }

    public boolean canGoUp() {
        return m_rootPath.getSegmentSize() > 0;
    }

    public GMLXPath getRootPath() {
        return m_rootPath;
    }

    /**
     * Sets the root path and refreshes the tree.
     * <p>
     * Tries to keep the current expansion state.
     * </p>
     */
    public void setRootPath(final GMLXPath rootPath) {
        final Object[] expandedElements = m_viewer == null ? null : m_viewer.getExpandedElements();

        if (rootPath == null)
            m_rootPath = EMPTY_ROOT_PATH;
        else
            m_rootPath = rootPath;

        // FIXME: dangerous: should be called in SWT thread...
        if (m_viewer != null) {
            m_viewer.setExpandedElements(expandedElements);
            m_viewer.refresh();
        }
    }

    private final Set<Feature> m_featureChangeStack = new LinkedHashSet<>();

    private UIJob m_featureChangeJob;

    private UIJob m_featureStructureChangedJob;

    protected void handleModelChanged(final ModellEvent modellEvent) {
        if (!m_handleModelEvents)
            return;

        final TreeViewer treeViewer = m_viewer;
        if (treeViewer == null)
            return;

        final Control control = treeViewer.getControl();

        if (modellEvent instanceof FeatureStructureChangeModellEvent) {
            if (m_featureStructureChangedJob != null)
                m_featureStructureChangedJob.cancel();

            m_featureStructureChangedJob = new UIJob(Messages.getString("GMLContentProvider_0")) //$NON-NLS-1$
            {
                @Override
                public IStatus runInUIThread(final IProgressMonitor monitor) {
                    if (monitor.isCanceled())
                        return Status.CANCEL_STATUS;
                    if (Objects.isNull(control) || control.isDisposed())
                        return Status.CANCEL_STATUS;

                    final Object[] expandedElements = treeViewer.getExpandedElements();

                    treeViewer.refresh();

                    treeViewer.setExpandedElements(expandedElements);

                    return Status.OK_STATUS;
                }
            };

            m_featureStructureChangedJob.setSystem(true);
            m_featureStructureChangedJob.setUser(false);

            m_featureStructureChangedJob.schedule(50);

        } else if (modellEvent instanceof FeaturesChangedModellEvent) {
            final FeaturesChangedModellEvent fcme = (FeaturesChangedModellEvent) modellEvent;

            // TODO: ugly abstraction, mover into separate class!
            synchronized (m_featureChangeStack) {
                Collections.addAll(m_featureChangeStack, fcme.getFeatures());

                if (m_featureChangeJob != null) {
                    m_featureChangeJob.cancel();
                    m_featureChangeJob = null;
                }

                // FIXME; why recreate the job at all?
                final UIJob featureChangeJob = new UIJob(Messages.getString("GMLContentProvider_1")) //$NON-NLS-1$
                {
                    @Override
                    public IStatus runInUIThread(final IProgressMonitor monitor) {
                        if (monitor.isCanceled())
                            return Status.CANCEL_STATUS;

                        if (Objects.isNull(control) || control.isDisposed())
                            return Status.CANCEL_STATUS;

                        final Feature[] features = popChangedFeatures();

                        for (final Feature feature : features) {
                            // FIXME: handle special case: also refresh elements of tree that reference this feature...

                            treeViewer.refresh(feature, true);
                        }

                        return Status.OK_STATUS;
                    }
                };

                featureChangeJob.setSystem(true);
                featureChangeJob.setUser(false);
                featureChangeJob.schedule(50);

                m_featureChangeJob = featureChangeJob;
            }
        }
    }

    Feature[] popChangedFeatures() {
        synchronized (m_featureChangeStack) {
            final Feature[] popped = m_featureChangeStack.toArray(new Feature[] {});
            m_featureChangeStack.clear();
            return popped;
        }
    }

    public void setShowAssociations(final boolean showAssociations) {
        m_showAssociations = showAssociations;
        if (m_viewer != null)
            m_viewer.refresh();
    }

    /**
     * Allows to override default behavior regarding visibility of children, defined by the catalog-properties.<br/>
     * 
     * @param override
     *          Value that overrides the catalog behavior. Set to <code>null</code>, to use the default behavior.
     */
    public void setShowChildrenOverride(final QName element, final Boolean override) {
        if (override == null)
            m_showChildrenOverrides.remove(element);
        else
            m_showChildrenOverrides.put(element, override);
    }
}