com.crispico.flower.mp.doc.DocumentationController.java Source code

Java tutorial

Introduction

Here is the source code for com.crispico.flower.mp.doc.DocumentationController.java

Source

/* license-start
 * 
 * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 3.
 * 
 * This program 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 General Public License for more details, at <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *   Crispico - Initial API and implementation
 *
 * license-end
 */
package com.crispico.flower.mp.doc;

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.provider.INotifyChangedListener;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.UMLPackage;

import com.crispico.flower.mp.FlowerEditingDomain;
import com.crispico.flower.mp.notation.Note;
import com.crispico.flower.mp.notation.View;

/**
 * This is an abstract documentation controller capable of :
 * <ul>
 *    <li> interpreting the changes of the selection, {@link #setSelection()}, which unregisters automatically the old
 *       selection and registers the listeners for the new selection if documentable.
 *    <li> listening to documentation changes about the documentable element and automatically processing the content
 *       to be dispatched to the client;
 *    <li> listening to the element which provides the label provider and automatically process the label and the image 
 *       to be dispatched to the client;
 *    <li> saving the received new documentation for an element, process it for storing it into the element.
 * </ul>
 * 
 * <p/> The changeDocumentationDetailsOnClient method is abstract and need to be implemented in the following way : 
 * <ul>
 *    <li> if the documentationContentChanged parameter = true - the client must make actions for update the graphical component
 *          that shows the content of the documentation
 *    <li> if the hasDocumentationChanged parameter = true - the client must take care for switching the pages of documentation
 *          available or if the documentation is not available
 *    <li> if the documentationTitleChanged parameter = true - the client must update the graphical component
 *          responsible for showing the title and the image of the documentation.  
 * </ul>
 * 
 * <p/> Implements the following interfaces :
 * <ul>
 *    <li> {@link Adapter} - needed for this abstract class to be an adapter so it could be attached to an element
 *       and also for extending classes to have the ability to override existing mechanisms regarding the notifications 
 *       about changes;
 *    <li> {@link INotifyChangedListener} - needed for abstract class to be a notifier, so it could be attached to
 *       label item providers given by the element which provides it, and also implemented for the extending classes
 *       to have the ability to override also mechanism regarding notifications.
 *  <li> Because this abstract class implements both {@link Adapter} and {@link INotifyChangedListener} and
 *     because both have {@link #notifyChanged(Notification)} method, mean that the common behavior must implemented
 *     in this method. The separation can be done by checking the type of the notification parameter if it is a documentation
 *     or otherwise.
 * </ul>
 * 
 * @author Sorin
 * @author Daniela
 * @flowerModelElementId _kUlEkIm4Ed-IjqfeDOxYSw
 */
public abstract class DocumentationController implements Adapter, INotifyChangedListener {

    /**
     * The initial selected element must be kept because when switching the refElement
     * we must re-obtain the element which provides for the label provider. This also
     * need to be kept for the web navigator in order to send the id of the object in order
     * to recognize it later.
     */
    protected Element selectedElement;

    /**
     * This represents the element which provides for an {@link IDocumentationProvider} 
     * needed for obtaining or updating the documentation.
     */
    protected Element selectedDocumentableElement;

    /**
     * This represents the element which provides the label provider. It is stored
     * to be compared with a new element which provides a label provider, 
     * when switching the refElement.
     */
    protected Element selectedLabeledElement;

    /**
     * This is the objects which returns the label of the selected labeled element to
     * be shown by the client.
     */
    protected ItemProviderAdapter selectedLabelProvider;

    /**
     * Needed in order not to send the same documentation multiple times.
     * For sending to the client the same documentation {@link #processDocumentationContent(boolean)} method
     * exists and <code> true</code> value can be passed.
     */
    protected String clientDocumentationContent;

    /**
     * Sometimes we want the controller to send the new doc details to client without making optimizations.
     * For example if a new client connects for the first time to this controller.
     * @author Dana
     */
    protected Boolean avoidUsingLastSelectionForOptimization = false;

    /**
     * {@link #clientSelectionImage} and {@link #clientSelectionTitle} are kept in order not to
     * send the same information to the client. 
     */
    protected String clientSelectionTitle;

    protected Object clientSelectionImage;

    protected boolean clientHasDocumentation = false;

    public String getClientDocumentationContent() {
        return clientDocumentationContent;
    }

    public String getClientSelectionTitle() {
        return clientSelectionTitle;
    }

    public Object getClientSelectionImage() {
        return clientSelectionImage;
    }

    public boolean isClientHasDocumentation() {
        return clientHasDocumentation;
    }

    /**
     * This implementation of this abstract method should make specific changes on the client
     * that implements it. This changes can be made having in account the tree received parameters, i.e.:
     *  
     * If hasDocumentationChanged is true: 
     *   The implementation of this abstract method is expected to switch the 2 pages 
     *   for showing documentation and showing no documentation available message, more particularly
     *   for Flex Documentation View, to stack command for updating the showed page
     *   
     * If documentationTitleChanged is true:
     *     The implementation of this abstract method is expected to update the title of the Eclipse
      *   documentation View or to stack commands for updating the Flex Documentation View. 
     * 
     * If documentationContentChanged is true:
     *   Implementation should update the content of the Eclipse Documentation View or should stack 
     *   commands for updating the content for the Flex Documentation View.
        
     * @param hasDocumentationChanged
     * @param documentationTitleChanged
     * @param documentationContentChanged
     */
    protected abstract void changeDocumentationDetailsOnClient(boolean hasDocumentationChanged,
            boolean documentationTitleChanged, boolean documentationContentChanged);

    /**
     * This method must be called when the documentation controller must take it's information from a new element.
     * 
     * <p/> This controller will provide documentation content only if finds a single element in the selection,
     * that single element provides for a documentable element and labeled element which provide {@link IDocumentationProvider}
     * respectively an label provider. 
     * 
     * <p/> The behavior is to first remove the listeners on the old selection by calling {@link #clearSelection()}.
     * After it obtains the documentable element from {@link DocumentationUtils#getDocumentableElement(Object)}
     * and after it obtains the labeled element which is capable or obtaining the label provider. Listeners are added
     * if the selection is valid, and after {@link #processDocumentationContent()} is called for processing
     * and sending to the client the documentation content and {@link #processSelectionTitle()} is called for
     * processing and sending the label and the image to the client. In final {@link #processHasDocumentation()} 
     * method will be called for telling the client that the pages must be switched.
     * @flowerModelElementId _kWZpgom4Ed-IjqfeDOxYSw
     */
    public void setSelection(ISelection selection) {

        clearSelection();

        // Take the first element of a structured selection and make sure that only 1 object was selected.
        Object selectedObject = null;
        if (selection instanceof StructuredSelection) {
            StructuredSelection structuredSelection = (StructuredSelection) selection;
            if (structuredSelection.size() == 1) {
                selectedObject = structuredSelection.getFirstElement();
            }
        }

        // For the selected object we see if we can obtain a documentable element it means it provides for documentation 
        Element documentableElement = DocumentationUtils.getDocumentableElement(selectedObject);
        if (documentableElement != null) {
            this.selectedElement = (Element) selectedObject;
            this.selectedDocumentableElement = documentableElement;
            // for the selected object we find the element which can give us a ItemProviderAdapter.
            this.selectedLabeledElement = (Element) findSelectedLabeledObject(selectedObject);
            // obtain an itemProviderAdapter to be used for text and image 
            this.selectedLabelProvider = (ItemProviderAdapter) FlowerEditingDomain
                    .getEditingDomainItemProviderFor(selectedLabeledElement);

            // an label item provider could not be found and we are sure that the selection id documentable so throw exception
            if (selectedLabelProvider == null)
                throw new IllegalStateException("Could not obtain a label item provider for selected element");
        }

        if (selectedDocumentableElement != null) {
            //processDocumentationDetails(false, false);

            selectedDocumentableElement.eAdapters().add(this);
            selectedLabelProvider.addListener(this);
        }
        // we need to call the processing of the switching of the pages after then information has been set to the client
        // because otherwise, when changing from no doc to with doc, we would first the see the old documentation flickering
        // to the new documentation.
        processDocumentationDetails(true, true);
    }

    /**
     * This method unregisters the listeners from the documentable element and from the label provider
     * if they exists.
     * 
     * <p/> We do not client the values showed by the client because we are interested in keeping them
     * because the new selected element may have the same content.
     * 
     * <p/> This method should be called when we want to dispose this controller or when
     * we want to change the selection, for which we first must remove the listening on the old selection.
     * @flowerModelElementId _kWaQk4m4Ed-IjqfeDOxYSw
     */
    public void clearSelection() {
        if (selectedDocumentableElement != null) {
            selectedDocumentableElement.eAdapters().remove(this);
            selectedDocumentableElement = null;
        }
        if (selectedLabelProvider != null) {
            selectedLabelProvider.removeListener(this);
            selectedLabelProvider = null;
            selectedLabeledElement = null;
        }
        selectedElement = null;
    }

    /**
     * Because the interfaces {@link Adapter} and {@link INotifyChangedListener} have both this method
     * it means that for both of them, notification about documentation changed or notification about selected labeled element changed
     * are handled here.
     * 
     * <p/> This method has been implemented directly into the {@link DocumentationController} in order to permit for extending classes
     * to add their own behavior after a notification has been handled.
     * 
     * <p> This method in the first part verifies that the notification received is of type documentation changed. If so
     * the re processing of the {@link #selectedDocumentableElement} is commanded in order to set the new documentation to the client.
     * After the reprocessing of the {@link #selectedDocumentableElement} it is verified if the {@link #selectedLabeledElement} is
     * the same after the documentation change notification. This is done because after changing the refElement in the case of note
     * means that we will need also to update the listened element which provides the label provider.
     * 
     * <p/> This method in the second part if it is not about a documentation change then it means that the notification arrived from
     * changing a feature in element which provides the label, so we need to re process the label of the selection and set it to the
     * client.
     * @flowerModelElementId _kWa3oYm4Ed-IjqfeDOxYSw
     */
    public void notifyChanged(Notification notification) {
        int eventType = notification.getEventType();

        // Story Sorin : The before release M3 alpha, a bug arrived from here because it seamed that when an element was deleted from tree,
        // normally the clear Selection removes also an adapter from the element, which dispatches a notification about removing adapter.
        // In this case the old mechanism was trying to process the title and also the image, but the editing domain could not be recovered because the element
        // was not in the resource anymore. So the following Removing notifications where fastly added in order to fix the problem.

        // TODO Sorin : the following fast solution must be revised in order to ensure that the solution is ok, if endeed this case has been sliped away at the moment of implementation.
        // It may be possible that this did not happened before, and it may be possible that this happened only after Cristi's work on web/group/ids etc. 
        if (eventType == Notification.REMOVING_ADAPTER)
            return;

        // after a doc change, even if the selection remains the same,
        // the corresponding labeled selection of it could be changed so find the new one
        if (eventType == IDocumentationProvider.DOCUMENTATION_CHANGED) {
            Object newSelectedLabeledElement = findSelectedLabeledObject(selectedElement);
            if (newSelectedLabeledElement != selectedLabeledElement) {
                // unlisten the old label provider
                if (selectedLabelProvider != null) {
                    selectedLabelProvider.removeListener(this);
                }
                // store the new labeled element
                selectedLabeledElement = (Element) newSelectedLabeledElement;
                // obtain the new label provider for the labeled element
                selectedLabelProvider = (ItemProviderAdapter) FlowerEditingDomain
                        .getEditingDomainItemProviderFor(selectedLabeledElement);

                // an label item provider could not be found and we are sure that the selection id documentable so throw exception
                if (selectedLabelProvider == null)
                    throw new IllegalStateException("Could not obtain a label item provider for selected element");
                // listen the new label provider
                selectedLabelProvider.addListener(this);
            }
        }
        // some documentation details could be modified so process the new ones
        if (eventType == IDocumentationProvider.DOCUMENTATION_CHANGED) {
            processDocumentationDetails(true, true);
        } else {
            if (FlowerEditingDomain.getFlowerEditingDomainFor((EObject) selectedLabeledElement) == null) { // object removed from model
                return;
            }
            // if it gets here it is shore that the notification has arrived from the element which provides label for selection
            // so label it could be changed. Need to process only the label
            processDocumentationDetails(true, false);
        }
    }

    /**
     * Given an object it will return the object capable of providing a label provider for the use of the selection title.
     * The logic is the following :
     * 
     * If it is a:
     * <ul>
     *    <li> note unlinked then this is the labeled Object;
     *    <li> note linked then the labeled object is the ref element of the View refElement;
     *    <li> view then the labeled object is the ref element;
     * </ul>
     * @flowerModelElementId _kWfJE4m4Ed-IjqfeDOxYSw
     */
    private Object findSelectedLabeledObject(Object selectedObject) {
        Object selectedLabeledObject = null;
        if (selectedObject instanceof Note) {
            Note selectedNote = (Note) selectedObject;
            if (selectedNote.getRefElement() == null) {
                selectedLabeledObject = selectedNote;
            } else {
                selectedLabeledObject = ((View) selectedNote.getRefElement()).getRefElement();
            }
        } else if (selectedObject instanceof View) {
            View selectedView = (View) selectedObject;
            selectedLabeledObject = selectedView.getRefElement();
        } else if (selectedObject instanceof Element) {
            selectedLabeledObject = selectedObject;
        }
        return selectedLabeledObject;
    }

    public void processDocumentationDetails(boolean needToProcessDocumentationTitle,
            boolean needToProcessDocumetationContent) {
        boolean hasDocumentationChanged = false;
        boolean documentationTitleChanged = false;
        boolean documentationContentChanged = false;

        //process hasDocumenation
        boolean newHasDocumentation = selectedDocumentableElement != null;
        if (avoidUsingLastSelectionForOptimization || newHasDocumentation != clientHasDocumentation) {
            clientHasDocumentation = newHasDocumentation;
            hasDocumentationChanged = true;
        }

        //process title details if needed it and if it can be obtained
        TitleDetails newTitleDetails = getTitleDetails();
        if (newTitleDetails != null && needToProcessDocumentationTitle) {
            if (avoidUsingLastSelectionForOptimization || titleChanged(newTitleDetails)) {
                this.clientSelectionTitle = newTitleDetails.text;
                this.clientSelectionImage = newTitleDetails.image;
                documentationTitleChanged = true;
            }
        }

        //process content if needed it and if it can be obtained
        IDocumentationProvider documentationProvider = clientHasDocumentation
                ? (IDocumentationProvider) FlowerEditingDomain
                        .getEditingDomainItemProviderFor(selectedDocumentableElement)
                : null;
        if (documentationProvider != null && needToProcessDocumetationContent) {
            String newDocumentationContent = documentationProvider.getDocumentation(selectedDocumentableElement);
            if (avoidUsingLastSelectionForOptimization || contentChanged(newDocumentationContent)) {
                this.clientDocumentationContent = newDocumentationContent;
                documentationContentChanged = true;
            }
        }

        avoidUsingLastSelectionForOptimization = false;

        if (hasDocumentationChanged || documentationTitleChanged || documentationContentChanged)
            changeDocumentationDetailsOnClient(hasDocumentationChanged, documentationTitleChanged,
                    documentationContentChanged);
    }

    private boolean titleChanged(TitleDetails newTitleDetails) {
        if (newTitleDetails.text != null && !newTitleDetails.text.equals(clientSelectionTitle)
                || newTitleDetails.text == null && clientSelectionTitle != null
                || newTitleDetails.image != null && !newTitleDetails.image.equals(clientSelectionImage)
                || newTitleDetails.image == null && newTitleDetails.image != null)
            return true;
        else
            return false;
    }

    private boolean contentChanged(String newDocumentationContent) {
        if (newDocumentationContent != null && !newDocumentationContent.equals(clientDocumentationContent)
                || newDocumentationContent == null && clientDocumentationContent != null)
            return true;
        else
            return false;
    }

    protected TitleDetails getTitleDetails() {
        if (selectedLabelProvider != null)
            return new TitleDetails(selectedLabelProvider.getText(selectedLabeledElement),
                    selectedLabelProvider.getImage(selectedLabeledElement));
        else
            return null;
    }

    /**
     * Given a the new documentation content and a documentable element it obtains it's documentation item provider
     * and sets the documentation.
     * @flowerModelElementId _kWdT4om4Ed-IjqfeDOxYSw
     */
    public void updateDocumentationContent(String text, Element documentableElement) {
        IDocumentationProvider documentationProvider = (IDocumentationProvider) FlowerEditingDomain
                .getEditingDomainItemProviderFor(documentableElement);
        if (documentationProvider != null) {
            String oldDocumentation = documentationProvider.getDocumentation(documentableElement);
            if (oldDocumentation != null && !oldDocumentation.equals(text)
                    || oldDocumentation == null && text != null) {
                documentationProvider.setDocumentation(documentableElement, text, null);
            }
        }
    }

    /**
     * @flowerModelElementId _kWeiAYm4Ed-IjqfeDOxYSw
     */
    public void updateDocumentationContent(String text) {
        updateDocumentationContent(text, this.selectedDocumentableElement);
    }

    /**
     * @flowerModelElementId _kWfJEYm4Ed-IjqfeDOxYSw
     */
    public Element getSelectedDocumentableElement() {
        return selectedDocumentableElement;
    }

    /**
     * The methods {@link #isAdapterForType()}, {@link #getTarget()} and {@link #setTarget()} we are obligated to implement
     * if we want to implement {@link Adapter}, but these methods have no purpose.
     */
    public boolean isAdapterForType(Object type) {
        return false;
    }

    public Notifier getTarget() {
        return null;
    }

    public void setTarget(Notifier newTarget) {
    }

    public static class TitleDetails {

        public String text;
        public Object image;

        public TitleDetails(String text, Object image) {
            this.text = text;
            this.image = image;
        }
    }

}