Java tutorial
/* 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; } } }