org.openmicroscopy.shoola.agents.metadata.editor.EditorModel.java Source code

Java tutorial

Introduction

Here is the source code for org.openmicroscopy.shoola.agents.metadata.editor.EditorModel.java

Source

/*
 * org.openmicroscopy.shoola.agents.metadata.editor.EditorModel 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 University of Dundee. All rights reserved.
 *
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *  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.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.agents.metadata.editor;

//Java imports
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.swing.Icon;
import javax.swing.JFrame;

//Third-party libraries
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

//Application-internal dependencies
import omero.model.OriginalFile;
import omero.model.PlaneInfo;
import org.openmicroscopy.shoola.agents.metadata.AcquisitionDataLoader;
import org.openmicroscopy.shoola.agents.metadata.AnalysisResultsFileLoader;
import org.openmicroscopy.shoola.agents.metadata.FileAnnotationChecker;
import org.openmicroscopy.shoola.agents.metadata.AttachmentsLoader;
import org.openmicroscopy.shoola.agents.metadata.ChannelDataLoader;
import org.openmicroscopy.shoola.agents.metadata.ChannelDataSaver;
import org.openmicroscopy.shoola.agents.metadata.DiskSpaceLoader;
import org.openmicroscopy.shoola.agents.metadata.EditorLoader;
import org.openmicroscopy.shoola.agents.metadata.EnumerationLoader;
import org.openmicroscopy.shoola.agents.metadata.FileLoader;
import org.openmicroscopy.shoola.agents.metadata.FilesetLoader;
import org.openmicroscopy.shoola.agents.metadata.IconManager;
import org.openmicroscopy.shoola.agents.metadata.ImageSizeLoader;
import org.openmicroscopy.shoola.agents.metadata.InstrumentDataLoader;
import org.openmicroscopy.shoola.agents.metadata.MetadataViewerAgent;
import org.openmicroscopy.shoola.agents.metadata.OriginalMetadataLoader;
import org.openmicroscopy.shoola.agents.metadata.PasswordEditor;
import org.openmicroscopy.shoola.agents.metadata.PlaneInfoLoader;
import org.openmicroscopy.shoola.agents.metadata.ROILoader;
import org.openmicroscopy.shoola.agents.metadata.RenderingControlLoader;
import org.openmicroscopy.shoola.agents.metadata.ScriptLoader;
import org.openmicroscopy.shoola.agents.metadata.ScriptsLoader;
import org.openmicroscopy.shoola.agents.metadata.TagsLoader;
import org.openmicroscopy.shoola.agents.metadata.UserPhotoLoader;
import org.openmicroscopy.shoola.agents.metadata.UserPhotoUploader;
import org.openmicroscopy.shoola.agents.metadata.browser.Browser;
import org.openmicroscopy.shoola.agents.metadata.rnd.Renderer;
import org.openmicroscopy.shoola.agents.metadata.rnd.RendererFactory;
import org.openmicroscopy.shoola.agents.metadata.util.AnalysisResultsItem;
import org.openmicroscopy.shoola.agents.metadata.util.DataToSave;
import org.openmicroscopy.shoola.agents.metadata.view.MetadataViewer;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.agents.util.ViewerSorter;
import org.openmicroscopy.shoola.agents.util.ui.PermissionMenu;
import org.openmicroscopy.shoola.env.Environment;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.data.AdminService;
import org.openmicroscopy.shoola.env.data.OmeroMetadataService;
import org.openmicroscopy.shoola.env.data.model.AdminObject;
import org.openmicroscopy.shoola.env.data.model.AnnotationLinkData;
import org.openmicroscopy.shoola.env.data.model.DeletableObject;
import org.openmicroscopy.shoola.env.data.model.DeleteActivityParam;
import org.openmicroscopy.shoola.env.data.model.DownloadActivityParam;
import org.openmicroscopy.shoola.env.data.model.DownloadArchivedActivityParam;
import org.openmicroscopy.shoola.env.data.model.EnumerationObject;
import org.openmicroscopy.shoola.env.data.model.SaveAsParam;
import org.openmicroscopy.shoola.env.data.model.ScriptObject;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.data.util.StructuredDataResults;
import org.openmicroscopy.shoola.env.log.LogMessage;
import org.openmicroscopy.shoola.env.rnd.RenderingControl;
import org.openmicroscopy.shoola.env.ui.UserNotifier;
import org.openmicroscopy.shoola.util.file.modulo.ModuloInfo;
import org.openmicroscopy.shoola.util.file.modulo.ModuloParser;
import org.openmicroscopy.shoola.util.ui.component.ObservableComponent;
import pojos.AnnotationData;
import pojos.BooleanAnnotationData;
import pojos.ChannelAcquisitionData;
import pojos.ChannelData;
import pojos.DataObject;
import pojos.DatasetData;
import pojos.DoubleAnnotationData;
import pojos.ExperimenterData;
import pojos.FileAnnotationData;
import pojos.FileData;
import pojos.FilesetData;
import pojos.GroupData;
import pojos.ImageAcquisitionData;
import pojos.ImageData;
import pojos.InstrumentData;
import pojos.LongAnnotationData;
import pojos.MultiImageData;
import pojos.PermissionData;
import pojos.PixelsData;
import pojos.PlateAcquisitionData;
import pojos.PlateData;
import pojos.ProjectData;
import pojos.RatingAnnotationData;
import pojos.ScreenData;
import pojos.TagAnnotationData;
import pojos.TermAnnotationData;
import pojos.TextualAnnotationData;
import pojos.WellData;
import pojos.WellSampleData;
import pojos.XMLAnnotationData;

/** 
 * The Model component in the <code>EditorViewer</code> MVC triad.
 * This class knows how to store and manipulate the results. 
 * It provides a suitable data loader.
 * The {@link EditorComponent} intercepts the results of data loadings, 
 * feeds them back to this class and fires state transitions as appropriate.
 *
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since OME3.0
 */
class EditorModel {

    /** Identifies <code>all</code> the objects.*/
    static final int ALL = PermissionMenu.ALL;

    /** Identifies the objects added by current user.*/
    static final int ME = PermissionMenu.ME;

    /** Identifies the objects added by others.*/
    static final int OTHER = PermissionMenu.OTHER;

    /** The index of the default channel. */
    static final int DEFAULT_CHANNEL = 0;

    /** The file namespaces to exclude.*/
    private final static List<String> EXCLUDED_FILE_NS;

    static {
        EXCLUDED_FILE_NS = new ArrayList<String>();
        EXCLUDED_FILE_NS.add(FileAnnotationData.COMPANION_FILE_NS);
        EXCLUDED_FILE_NS.add(FileAnnotationData.FLIM_NS);
    }

    /** The parent of this editor. */
    private MetadataViewer parent;

    /** Reference to the component that embeds this model. */
    private Editor component;

    /** The object this editor is for. */
    private Object refObject;

    /** The parent of the object this editor is for. */
    private Object parentRefObject;

    /** The parent of the object this editor is for. */
    private Object gpRefObject;

    /** 
     * Map containing the annotations made by users.
     * The keys are the user, the value the collection of
     * textual annotations.
     */
    private Map<Long, List> textualAnnotationsByUsers;

    /** The annotations ordered by date. */
    private List textualAnnotationsByDate;

    /** Collection of loaders. */
    private List<EditorLoader> loaders;

    /** Collection of existing tags if any. */
    private Collection existingTags;

    /** Collection of existing attachments if any. */
    private Collection existingAttachments;

    /** The list of emission wavelengths for a given set of pixels. */
    private Map emissionsWavelengths;

    /** Used to sort the various collections. */
    private ViewerSorter sorter;

    /** Reference to the browser. */
    private Browser browser;

    /** The image acquisition data. */
    private ImageAcquisitionData imageAcquisitionData;

    /** The instrument data. */
    private InstrumentData instrumentData;

    /** The enumerations related to channel metadata. */
    private Map<String, List<EnumerationObject>> channelEnumerations;

    /** The enumerations related to image metadata. */
    private Map<String, List<EnumerationObject>> imageEnumerations;

    /** The map hosting the channels acquisition data. */
    private Map<Integer, ChannelAcquisitionData> channelAcquisitionDataMap;

    /** The map hosting the channels plane info. */
    private Map<Integer, Collection> channelPlaneInfoMap;

    /** Reference to the renderer. */
    private Renderer renderer;

    /** Collection of annotations to unlink and delete. */
    private List<AnnotationData> toDelete;

    /** Collection of uploaded scripts. */
    private Map<Long, ScriptObject> scripts;

    /** Scripts with a UI. */
    private List<ScriptObject> scriptsWithUI;

    /** The file annotation with the original metadata. */
    private FileAnnotationData originalMetadata;

    /** The collection of analysis results. */
    private Map<AnalysisResultsItem, EditorLoader> resultsLoader;

    /** The photo of the current user.*/
    private Map<Long, BufferedImage> usersPhoto;

    /** Flag indicating if the image is a big image or not.*/
    private boolean largeImage;

    /** The file set associated to the image if an image is selected.*/
    private Set<FilesetData> set;

    /** Checks if the image is a large image or not. */
    private void fireLargeImageLoading() {
        ImageData img = null;
        if (refObject instanceof ImageData) {
            img = (ImageData) refObject;
        } else if (refObject instanceof WellSampleData) {
            img = ((WellSampleData) refObject).getImage();
        }
        PixelsData data = img.getDefaultPixels();
        if (data == null) {
            largeImage = false;
            return;
        }
        ImageSizeLoader loader = new ImageSizeLoader(component, parent.getSecurityContext(), data.getId());
        loader.load();
    }

    /**
     * Downloads the files.
     * 
     * @param folder The folder to save the file into.
     * @param override Flag indicating to override the existing file if it
      * exists, <code>false</code> otherwise.
     */
    private void downloadFiles(File folder, boolean override) {
        UserNotifier un = MetadataViewerAgent.getRegistry().getUserNotifier();
        FileAnnotationData fa = (FileAnnotationData) getRefObject();
        OriginalFile f = (OriginalFile) fa.getContent();
        IconManager icons = IconManager.getInstance();

        DownloadActivityParam activity = new DownloadActivityParam(f, folder,
                icons.getIcon(IconManager.DOWNLOAD_22));
        un.notifyActivity(getSecurityContext(), activity);

        Collection l = parent.getRelatedNodes();
        if (l == null)
            return;
        Iterator i = l.iterator();
        Object o;
        while (i.hasNext()) {
            o = i.next();
            if (o instanceof FileAnnotationData) {
                fa = (FileAnnotationData) o;
                f = (OriginalFile) fa.getContent();
                if (f.isLoaded()) {
                    activity = new DownloadActivityParam(f, folder, icons.getIcon(IconManager.DOWNLOAD_22));
                } else {
                    long id = fa.getId();
                    Environment env = (Environment) MetadataViewerAgent.getRegistry().lookup(LookupNames.ENV);
                    String path = env.getOmeroFilesHome();
                    path += File.separator + fa.getFileName();
                    activity = new DownloadActivityParam(id, DownloadActivityParam.FILE_ANNOTATION, new File(path),
                            icons.getIcon(IconManager.DOWNLOAD_22));
                }
                activity.setFileName(fa.getFileName());
                un.notifyActivity(getSecurityContext(), activity);
            }
        }
    }

    /** 
     * Downloads the archived images. 
     * 
     * @param file The file where to download the content.
     * @param override Flag indicating to override the existing file if it
      * exists, <code>false</code> otherwise.
     */
    private void downloadImages(File file, boolean override) {
        List<ImageData> images = new ArrayList<ImageData>();
        List<DataObject> l = getSelectedObjects();
        if (!CollectionUtils.isEmpty(l)) {
            Iterator<DataObject> i = l.iterator();
            DataObject o;
            List<Long> filesetIds = new ArrayList<Long>();
            long id;
            ImageData image;
            while (i.hasNext()) {
                o = i.next();
                if (isArchived(o)) {
                    image = (ImageData) o;
                    id = image.getFilesetId();
                    if (id < 0)
                        images.add(image);
                    else if (!filesetIds.contains(id)) {
                        images.add(image);
                        filesetIds.add(id);
                    }
                }
            }
        }
        if (!CollectionUtils.isEmpty(images)) {
            Iterator<ImageData> i = images.iterator();
            DownloadArchivedActivityParam p;
            UserNotifier un = MetadataViewerAgent.getRegistry().getUserNotifier();
            IconManager icons = IconManager.getInstance();
            Icon icon = icons.getIcon(IconManager.DOWNLOAD_22);
            SecurityContext ctx = getSecurityContext();
            while (i.hasNext()) {
                p = new DownloadArchivedActivityParam(file, i.next(), icon);
                p.setOverride(override);
                un.notifyActivity(ctx, p);
            }
        }
    }

    /** 
     * Sorts the passed collection of annotations by date starting with the
     * most recent.
     * 
     * @param annotations   Collection of {@link AnnotationData} linked to 
     *                      the currently edited <code>DataObject</code>.
     */
    private void sortAnnotationByDate(List annotations) {
        if (annotations == null || annotations.size() == 0)
            return;
        Comparator c = new Comparator() {
            public int compare(Object o1, Object o2) {
                Timestamp t1 = ((AnnotationData) o1).getLastModified(),
                        t2 = ((AnnotationData) o2).getLastModified();
                long n1 = t1.getTime();
                long n2 = t2.getTime();
                int v = 0;
                if (n1 < n2)
                    v = -1;
                else if (n1 > n2)
                    v = 1;
                return -v;
            }
        };
        Collections.sort(annotations, c);
    }

    /** 
    * Sorts the passed nodes by row.
    * 
    * @param nodes The nodes to sort.
    * @return See above.
    */
    private List sortPlane(Collection nodes) {
        List<Object> l = new ArrayList<Object>();
        if (nodes == null)
            return l;
        Iterator i = nodes.iterator();
        while (i.hasNext()) {
            l.add(i.next());
        }
        Comparator c = new Comparator() {
            public int compare(Object o1, Object o2) {
                PlaneInfo i1 = (PlaneInfo) o1, i2 = (PlaneInfo) o2;
                int t1 = i1.getTheT().getValue();
                int t2 = i2.getTheT().getValue();
                int v = 0;
                if (t1 < t2)
                    v = -1;
                else if (t1 > t2)
                    v = 1;
                return v;
            }
        };
        Collections.sort(l, c);
        return l;
    }

    /** 
     * Sorts the passed collection of enumerations.
     * 
     * @param enumerations   Collection of {@link EnumerationObject}.
     */
    private void sortEnumerations(List<EnumerationObject> enumerations) {
        if (enumerations == null || enumerations.size() == 0)
            return;
        Comparator c = new Comparator() {
            public int compare(Object o1, Object o2) {
                String s1 = ((EnumerationObject) o1).getValue(), s2 = ((EnumerationObject) o2).getValue();
                int v = 0;
                int result = (s1.toLowerCase()).compareTo(s2.toLowerCase());
                if (result < 0)
                    v = -1;
                else if (result > 0)
                    v = 1;
                return v;
            }
        };
        Collections.sort(enumerations, c);
    }

    /**
     * Starts an asynchronous call to load the photo of the currently
     * selected user.
     */
    private void fireExperimenterPhotoLoading() {
        if (refObject instanceof ExperimenterData) {
            ExperimenterData exp = (ExperimenterData) refObject;
            if (usersPhoto == null || !usersPhoto.containsKey(exp.getId())) {
                UserPhotoLoader loader = new UserPhotoLoader(component, parent.getSecurityContext(), exp);
                loader.load();
            }
        }
    }

    /**
     * Returns the group corresponding to the specified id or <code>null</code>.
     * 
     * @param groupId The identifier of the group.
     * @return See above.
     */
    private GroupData getGroup(long groupId) {
        Collection groups = MetadataViewerAgent.getAvailableUserGroups();
        if (groups == null)
            return null;
        Iterator i = groups.iterator();
        GroupData group;
        while (i.hasNext()) {
            group = (GroupData) i.next();
            if (group.getId() == groupId)
                return group;
        }
        return null;
    }

    /**
     * Indicates to load all annotations available if the user can annotate
     * and is an administrator/group owner or to only load the user's
     * annotation.
     * 
     * @return See above
     */
    private boolean canRetrieveAll() {
        if (!canAnnotate())
            return false;
        //check the group level
        GroupData group = getGroup(getRefObjectGroupID());
        if (group == null)
            return false;
        switch (group.getPermissions().getPermissionsLevel()) {
        case GroupData.PERMISSIONS_GROUP_READ:
            if (MetadataViewerAgent.isAdministrator())
                return true;
            Set leaders = group.getLeaders();
            Iterator i = leaders.iterator();
            long userID = getCurrentUser().getId();
            ExperimenterData exp;
            while (i.hasNext()) {
                exp = (ExperimenterData) i.next();
                if (exp.getId() == userID)
                    return true;
            }
            return false;
        case GroupData.PERMISSIONS_PRIVATE:
            return false;
        }
        return true;
    }

    /**
     * Returns <code>true</code> if the object can be deleted,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @param result the object of reference.
     * @return See above.
     */
    private boolean canDeleteLink(Object data, StructuredDataResults result) {
        if (!(data instanceof DataObject))
            return false;
        DataObject d = (DataObject) data;
        if (result == null)
            return false;
        Collection<AnnotationLinkData> links = result.getAnnotationLinks();
        if (links == null)
            return false;
        Iterator<AnnotationLinkData> i = links.iterator();
        AnnotationLinkData link;

        while (i.hasNext()) {
            link = i.next();
            if (d.getId() == link.getChild().getId())
                return link.canDelete();
        }
        return false;
    }

    /**
     * Creates a new instance.
     * 
     * @param refObject   The object this editor is for.
     * @param parent   The parent of this browser.
     */
    EditorModel(Object refObject, MetadataViewer parent) {
        if (refObject == null)
            throw new IllegalArgumentException("No object set.");
        this.parent = parent;
        this.refObject = refObject;
        loaders = new ArrayList<EditorLoader>();
        sorter = new ViewerSorter();
    }

    /**
     * Adds the annotation to the collection of annotation to be deleted.
     * 
     * @param annotation The value to add.
     */
    void deleteAnnotation(AnnotationData annotation) {
        if (annotation == null)
            return;
        if (toDelete == null)
            toDelete = new ArrayList<AnnotationData>();
        toDelete.add(annotation);
    }

    /**
     * Returns the collection of annotation to delete.
     * 
     * @return See above.
     */
    List<AnnotationData> getAnnotationToDelete() {
        return toDelete;
    }

    /**
     * Returns <code>true</code> if multiple selection is on, 
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isMultiSelection() {
        return !parent.isSingleMode();
    }

    /**
     * Returns the observable.
     * 
     * @return See above.
     */
    ObservableComponent getObservable() {
        return parent;
    }

    /**
     * Sets the browser.
     * 
     * @param browser The value to set.
     */
    void setBrowser(Browser browser) {
        this.browser = browser;
    }

    /**
     * Returns the browser.
     * 
     * @return See above.
     */
    Browser getBrowser() {
        return browser;
    }

    /**
     * Called by the <code>Editor</code> after creation to allow this
     * object to store a back reference to the embedding component.
     * 
     * @param component The embedding component.
     */
    void initialize(Editor component) {
        this.component = component;
    }

    /**
      * Returns <code>true</code> if the permissions can be shown,
      * <code>false</code> otherwise.
      * 
      * @return See above.
      */
    boolean isPermissionsShowable() {
        Object ref = getRefObject();
        if (ref == null)
            return false;
        return ((ref instanceof ProjectData) || (ref instanceof DatasetData));
    }

    /**
      * Returns the permission of the currently edited object. 
      * Returns <code>null</code> if no permission associated. (This should never
      * happens).
      * 
      * @return See above.
      */
    PermissionData getRefObjectPermissions() {
        Object ref = getRefObject();
        if (ref instanceof DataObject)
            return ((DataObject) ref).getPermissions();
        return null;
    }

    /**
     * Returns the name of the object if any.
     * 
     * @return See above.
     */
    String getRefObjectName() {
        return getObjectName(getPrimarySelect());
    }

    /**
     * Returns the name of the specified object.
     * 
     * @param ref The object to handle.
     * @return See above.
     */
    String getObjectName(Object ref) {
        String name = "";
        if (ref instanceof ImageData)
            name = ((ImageData) ref).getName();
        else if (ref instanceof DatasetData)
            name = ((DatasetData) ref).getName();
        else if (ref instanceof ProjectData)
            name = ((ProjectData) ref).getName();
        else if (ref instanceof TagAnnotationData || ref instanceof TermAnnotationData)
            name = ((AnnotationData) ref).getContentAsString();
        else if (ref instanceof ScreenData)
            name = ((ScreenData) ref).getName();
        else if (ref instanceof PlateData)
            name = ((PlateData) ref).getName();
        else if (ref instanceof PlateAcquisitionData)
            name = ((PlateAcquisitionData) ref).getLabel();
        else if (ref instanceof FileAnnotationData)
            name = ((FileAnnotationData) ref).getFileName();
        else if (ref instanceof WellSampleData) {
            WellSampleData ws = (WellSampleData) ref;
            ImageData img = ws.getImage();
            if (img != null && img.getId() >= 0)
                name = img.getName();
        } else if (ref instanceof FileData)
            name = ((FileData) ref).getName();
        else if (ref instanceof MultiImageData)
            name = ((MultiImageData) ref).getName();
        if (name == null)
            return "";
        return name.trim();
    }

    /**
     * Returns the name associated to the specified object.
     * 
     * @param ref The object to handle.
     * @return See above.
     */
    String getObjectTypeAsString(Object ref) {
        if (ref instanceof ImageData)
            return "Image";
        else if (ref instanceof DatasetData)
            return "Dataset";
        else if (ref instanceof ProjectData)
            return "Project";
        else if (ref instanceof ScreenData)
            return "Screen";
        else if (ref instanceof PlateData)
            return "Plate";
        else if (refObject instanceof PlateAcquisitionData)
            return "Plate Run";
        else if (refObject instanceof FileAnnotationData) {
            FileAnnotationData fa = (FileAnnotationData) refObject;
            String ns = fa.getNameSpace();
            if (FileAnnotationData.EDITOR_EXPERIMENT_NS.equals(ns))
                return "Experiment";
            else if (FileAnnotationData.EDITOR_PROTOCOL_NS.equals(ns))
                return "Protocol";
            return "File";
        } else if (refObject instanceof WellSampleData)
            return "Field";
        else if (refObject instanceof TagAnnotationData) {
            TagAnnotationData tag = (TagAnnotationData) refObject;
            if (TagAnnotationData.INSIGHT_TAGSET_NS.equals(tag.getNameSpace()))
                return "Tag Set";
            else
                return "Tag";
        } else if (refObject instanceof FileData) {
            FileData f = (FileData) refObject;
            if (f.isDirectory())
                return "Folder";
            return "File";
        } else if (refObject instanceof TermAnnotationData) {
            return "Term";
        } else if (refObject instanceof XMLAnnotationData) {
            return "XML file";
        } else if (refObject instanceof LongAnnotationData || refObject instanceof DoubleAnnotationData) {
            return "Numerical value";
        }
        return "";
    }

    /**
     * Returns the description of the object if any.
     * 
     * @return See above.
     */
    String getRefObjectDescription() {
        String description = "";
        Object ref = getPrimarySelect();
        if (ref instanceof ImageData)
            description = ((ImageData) ref).getDescription();
        else if (ref instanceof DatasetData)
            description = ((DatasetData) ref).getDescription();
        else if (ref instanceof ProjectData)
            description = ((ProjectData) ref).getDescription();
        else if (ref instanceof ScreenData)
            description = ((ScreenData) ref).getDescription();
        else if (ref instanceof PlateData)
            description = ((PlateData) ref).getDescription();
        else if (ref instanceof PlateAcquisitionData)
            description = ((PlateAcquisitionData) ref).getDescription();
        else if (ref instanceof TagAnnotationData) {
            description = ((TagAnnotationData) ref).getTagDescription();
        } else if (ref instanceof WellSampleData) {
            if (parentRefObject instanceof WellData) {
                WellData ws = (WellData) parentRefObject;
                description = ws.getWellType();
            }
            ImageData img = ((WellSampleData) ref).getImage();
            if (!StringUtils.isEmpty(img.getDescription()))
                description = img.getDescription();
        } else if (ref instanceof FileData)
            description = null;//((FileData) ref).getDescription();
        if (description == null)
            return "";
        return description.trim();
    }

    /**
     * Returns the id of the reference object if it is an instance of 
     * <code>DataObject</code> or <code>-1</code> otherwise.
     * 
     * @return See above.
     */
    long getRefObjectID() {
        Object ref = getRefObject();
        if (ref instanceof DataObject)
            return ((DataObject) ref).getId();
        return -1;
    }

    /**
     * Returns the group's id of the reference object if it is an instance of 
     * <code>DataObject</code> or <code>-1</code> otherwise.
     * 
     * @return See above.
     */
    long getRefObjectGroupID() {
        Object ref = getRefObject();
        if (ref instanceof DataObject)
            return ((DataObject) ref).getGroupId();
        return -1;
    }

    /**
     * Returns <code>true</code> if the user currently logged in is
     * the one currently edited, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isSelf() {
        Object ref = getRefObject();
        if (!(ref instanceof ExperimenterData))
            return false;
        ExperimenterData exp = getCurrentUser();
        return exp.getId() == getRefObjectID();
    }

    /**
     * Returns the <code>DataObject</code> this editor is for.
     * 
     * @return See above.
     */
    Object getRefObject() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return refObject;
        return refObject;
    }

    /**
     * Returns the primary select object.
     * 
     * @return See above.
     */
    Object getPrimarySelect() {
        if (!isMultiSelection())
            return getRefObject();
        List list = parent.getRelatedNodes();
        if (CollectionUtils.isEmpty(list))
            return getRefObject();
        return list.get(0);
    }

    /**
     * Returns <code>true</code> if the object can be edited,
     * <code>false</code> otherwise.
     *
     * @return See above.
     */
    boolean canEdit() {
        return canEdit(refObject);
    }

    /**
     * Returns <code>true</code> if the object can be edited,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean canEdit(Object data) {
        if (!(data instanceof DataObject))
            return false;
        DataObject d = (DataObject) data;
        return d.canEdit();
    }

    /**
     * Returns <code>true</code> if the object can be linked,
     * <code>false</code> otherwise.
     *
     * @return See above.
     */
    boolean canLink() {
        return canLink(refObject);
    }

    /**
     * Returns <code>true</code> if the object can be linked, e.g. 
     * image added to dataset, <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean canLink(Object data) {
        switch (getDisplayMode()) {
        case LookupNames.GROUP_DISPLAY:
            if (data instanceof DataObject)
                return ((DataObject) data).canLink();
            return false;
        case LookupNames.EXPERIMENTER_DISPLAY:
        default:
            return EditorUtil.isUserOwner(data, getUserID());
        }
    }

    /**
     * Returns <code>true</code> if the object can be annotated,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean canAnnotate() {
        return canAnnotate(refObject);
    }

    /**
     * Returns <code>true</code> if the object can be annotated,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean canAnnotate(Object data) {
        if (!(data instanceof DataObject))
            return false;
        DataObject d = (DataObject) data;
        return d.canAnnotate();
    }

    /**
     * Returns <code>true</code> if the object can be deleted,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean canDelete() {
        return canDelete(refObject);
    }

    /**
     * Returns <code>true</code> if the object can be deleted,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean canDelete(Object data) {
        if (!(data instanceof DataObject))
            return false;
        DataObject d = (DataObject) data;
        return d.canDelete();
    }

    /**
     * Returns <code>true</code> if the object can be deleted,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean canDeleteLink(Object data) {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return false;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        while (i.hasNext()) {
            e = i.next();
            if (canDeleteLink(data, e.getValue()))
                return true;
        }
        return false;
    }

    /**
     * Returns <code>true</code> if the object is a modulo annotation,
     * <code>false</code> otherwise.
     * 
     * @param data The data to handle.
     * @return See above.
     */
    boolean isModulo(Object data) {
        if (!(data instanceof XMLAnnotationData))
            return false;
        //parse the annotation.
        XMLAnnotationData d = (XMLAnnotationData) data;
        ModuloParser parser = new ModuloParser(d.getText());
        try {
            parser.parse();
            return !CollectionUtils.isEmpty(parser.getModulos());
        } catch (Exception e) {
            LogMessage msg = new LogMessage();
            msg.append("Error while reading modulo annotation.");
            msg.print(e);
            MetadataViewerAgent.getRegistry().getLogger().error(this, msg);
        }
        return false;
    }

    /**
     * Returns <code>true</code> if the selected objects belong to several
     * groups, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isAcrossGroups() {
        List<DataObject> l = getSelectedObjects();
        if (l == null || l.size() == 0)
            return false;
        List<Long> ids = new ArrayList<Long>();
        Iterator<DataObject> i = l.iterator();
        DataObject data;
        while (i.hasNext()) {
            data = i.next();
            if (!ids.contains(data.getGroupId()))
                ids.add(data.getGroupId());
        }
        return ids.size() > 1;
    }

    /**
     * Returns <code>true</code> if the annotation can be added, should
     * only be invoked for tagging or adding attachments, <code>false</code>
     * otherwise.
     * 
     * @return See above.
     */
    boolean canAddAnnotationLink() {
        if (!canAnnotate())
            return false;
        if (!isMultiSelection())
            return true;
        //multi selection.
        List l = parent.getRelatedNodes();
        if (l == null)
            return false;
        Iterator i = l.iterator();
        DataObject data;
        List<Long> ids = new ArrayList<Long>();
        while (i.hasNext()) {
            data = (DataObject) i.next();
            if (!(data instanceof GroupData || data instanceof ExperimenterData)) {
                if (!ids.contains(data.getGroupId()))
                    ids.add(data.getGroupId());
            }
        }
        return ids.size() <= 1;
    }

    /**
     * Returns <code>true</code> if the annotation can be deleted, should
     * only be invoked for tagging or adding attachments, <code>false</code>
     * otherwise.
     * 
     * @return See above.
     */
    boolean canDeleteAnnotationLink() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return false;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        StructuredDataResults data;
        Collection<AnnotationLinkData> links;
        Iterator<AnnotationLinkData> j;
        while (i.hasNext()) {
            e = i.next();
            data = e.getValue();
            links = data.getAnnotationLinks();
            if (links != null) {
                j = links.iterator();
                while (j.hasNext())
                    if (j.next().canDelete())
                        return true;
            }
        }
        return false;
    }

    /**
     * Returns <code>true</code> if the currently logged in user is the 
     * owner of the object, <code>false</code> otherwise.
     * 
     * @param object The object to handle.
     * @return See above.
     */
    boolean isUserOwner(Object object) {
        long id = getUserID();
        if (object == null)
            return false;
        if (object instanceof ExperimenterData)
            return (((ExperimenterData) object).getId() == id);
        if (!(object instanceof DataObject))
            return false;
        if (object instanceof FileData || object instanceof ImageData) {
            DataObject f = (DataObject) object;
            if (f.getId() < 0)
                return id == getUserID();
        }
        return EditorUtil.isUserOwner(object, id);
    }

    /**
     * Returns <code>true</code> if the current user is the owner of the link
     * between the passed annotation and the currently selected object.
     * 
     * @param annotation The annotation to handle.
     * @return See above.
     */
    boolean isLinkOwner(Object annotation) {
        Map<DataObject, StructuredDataResults> data = getAllStructuredData();
        if (data == null)
            return false;

        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> j = data.entrySet().iterator();

        Iterator<AnnotationLinkData> i;
        Collection<AnnotationLinkData> links;
        AnnotationLinkData link;
        DataObject ann = (DataObject) annotation;

        long id = getUserID();

        while (j.hasNext()) {
            e = j.next();
            links = e.getValue().getAnnotationLinks();
            if (links != null) {
                i = links.iterator();
                while (i.hasNext()) {
                    link = i.next();
                    if (link.getChild().getId() == ann.getId() && link.getOwner().getId() == id) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Returns <code>true</code> if the annotation  has been added by others,
     * <code>false</code> otherwise.
     * 
     * @param data The annotation to handle.
     * @return See above.
     */
    boolean isAnnotatedByOther(DataObject data) {
        if (data == null)
            return false;

        List<ExperimenterData> annotators = getAnnotators(data);
        if (annotators == null || annotators.size() == 0)
            return false;
        if (annotators.size() == 1) {
            ExperimenterData exp = annotators.get(0);
            long id = getUserID();
            return exp.getId() != id;
        }
        return true;
    }

    /**
     * Returns the collection of experimenters who use the annotation.
     * 
     * @param annotation    The annotation to handle.
     * @return See above.
     */
    List<ExperimenterData> getAnnotators(Object annotation) {
        return getAnnotators(null, annotation);
    }

    /**
     * Returns the collection of experimenters who use the annotation.
     * 
     * @param ref The object of reference.
     * @param annotation The annotation to handle.
     * @return See above.
     */
    List<ExperimenterData> getAnnotators(DataObject ref, Object annotation) {
        List<ExperimenterData> list = new ArrayList<ExperimenterData>();
        Map<DataObject, StructuredDataResults> data = getAllStructuredData();
        if (data == null)
            return list;

        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> j = data.entrySet().iterator();

        Iterator<AnnotationLinkData> i;
        Collection<AnnotationLinkData> links;
        AnnotationLinkData link;
        DataObject ann = (DataObject) annotation;

        List<Long> ids = new ArrayList<Long>();
        while (j.hasNext()) {
            e = j.next();
            if (ref == null) {
                links = e.getValue().getAnnotationLinks();
                if (links != null) {
                    i = links.iterator();
                    while (i.hasNext()) {
                        link = i.next();
                        if (link.getChild().getId() == ann.getId()) {

                            if (!ids.contains(link.getOwner().getId())) {
                                list.add(link.getOwner());
                                ids.add(link.getOwner().getId());
                            }
                        }
                    }
                }
            } else {
                if (ref.getId() == e.getKey().getId()) {
                    links = e.getValue().getAnnotationLinks();
                    if (links != null) {
                        i = links.iterator();
                        while (i.hasNext()) {
                            link = i.next();
                            if (link.getChild().getId() == ann.getId()) {

                                if (!ids.contains(link.getOwner().getId())) {
                                    list.add(link.getOwner());
                                    ids.add(link.getOwner().getId());
                                }
                            }
                        }
                    }
                    break;
                }
            }

        }
        return list;
    }

    /**
     * Returns the links corresponding to the level and the annotation.
     * 
     * @param level The level to handle.
     * @param ho The annotation.
     * @return
     */
    List<Object> getLinks(int level, AnnotationData ho) {
        Map<DataObject, StructuredDataResults> data = getAllStructuredData();
        if (data == null)
            return null;

        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> j = data.entrySet().iterator();

        Iterator<AnnotationLinkData> i;
        AnnotationLinkData d;
        List<Object> results = new ArrayList<Object>();
        long userID = getCurrentUser().getId();
        Collection<AnnotationLinkData> links;
        while (j.hasNext()) {
            e = j.next();
            links = e.getValue().getAnnotationLinks();
            if (links != null) {
                i = links.iterator();
                switch (level) {
                case ALL:
                    while (i.hasNext()) {
                        d = i.next();
                        if (ho.getId() == d.getChild().getId()) {
                            results.add(d.getLink());
                        }
                    }
                    break;
                case ME:
                    while (i.hasNext()) {
                        d = i.next();
                        if (ho.getId() == d.getChild().getId() && userID == d.getOwner().getId()) {
                            results.add(d.getLink());
                        }
                    }
                    break;
                case OTHER:
                    while (i.hasNext()) {
                        d = i.next();
                        if (ho.getId() == d.getChild().getId() && userID != d.getOwner().getId()) {
                            results.add(d.getLink());
                        }
                    }
                }
            }

        }
        return results;
    }

    /**
     * Returns the FileAnnotations corresponding to the given level.
     * 
     * @param level The level to handle.
     * @return See above.
     */
    List<FileAnnotationData> getFileAnnotatationsByLevel(int level) {
        List<FileAnnotationData> result = new ArrayList<FileAnnotationData>();
        Collection<FileAnnotationData> all = getAllAttachments();
        for (FileAnnotationData f : all) {
            switch (level) {
            case ALL:
                result.add(f);
                break;
            case ME:
                if (MetadataViewerAgent.getUserDetails().getId() == f.getOwner().getId()) {
                    result.add(f);
                }
                break;
            case OTHER:
                if (MetadataViewerAgent.getUserDetails().getId() != f.getOwner().getId()) {
                    result.add(f);
                }
            }
        }
        return result;
    }

    /**
     * Returns <code>true</code> if the annotation is already used by the 
     * current user, <code>false</code> otherwise.
     * 
     * @param annotation The annotation to handle.
     * @return See above.
     */
    boolean isAnnotationUsedByUser(Object annotation) {
        Map<DataObject, StructuredDataResults> data = getAllStructuredData();
        if (data == null)
            return false;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> j = data.entrySet().iterator();
        Collection<AnnotationLinkData> links;
        Iterator<AnnotationLinkData> i;
        AnnotationLinkData link;

        long id = MetadataViewerAgent.getUserDetails().getId();
        DataObject ann = (DataObject) annotation;

        while (j.hasNext()) {
            e = j.next();
            links = e.getValue().getAnnotationLinks();
            i = links.iterator();
            while (i.hasNext()) {
                link = i.next();
                if (link.getChild().getId() == ann.getId() && link.getOwner().getId() == id)
                    return true;
            }

        }
        return false;
    }

    /**
     * Returns <code>true</code> if the user currently logged in, is a leader
     * of the selected group, <code>false</code> otherwise.
     * 
     * @return
     */
    boolean isAdministrator() {
        return MetadataViewerAgent.isAdministrator();
    }

    /**
     * Returns <code>true</code> if the user currently logged in, is a leader
     * of the selected group, <code>false</code> otherwise.
     * 
     * @return
     */
    boolean isGroupLeader() {
        ExperimenterData exp = MetadataViewerAgent.getUserDetails();
        Collection groups = MetadataViewerAgent.getAvailableUserGroups();
        if (groups == null)
            return false;
        long groupID = exp.getDefaultGroup().getId();
        Iterator i = groups.iterator();
        GroupData g;
        Set leaders = null;
        while (i.hasNext()) {
            g = (GroupData) i.next();
            if (g.getId() == groupID) {
                leaders = g.getLeaders();
                break;
            }
        }
        if (leaders == null)
            return false;
        i = leaders.iterator();
        ExperimenterData data;
        while (i.hasNext()) {
            data = (ExperimenterData) i.next();
            if (data.getId() == exp.getId())
                return true;
        }
        return false;
    }

    /**
     * Returns the first name and the last name of the owner.
     * 
     * @param object The object to handle.
     * @return See above.
     */
    String formatOwner(DataObject object) {
        if (object == null)
            return "";
        if (object instanceof ExperimenterData) {
            return EditorUtil.formatExperimenter((ExperimenterData) object);
        }
        return formatOwner(object.getOwner());
    }

    /**
     * Returns the date.
     * 
     * @param object The object to handle.
     * @return See above.
     */
    String formatDate(DataObject object) {
        return EditorUtil.formatDate(object);
    }

    /**
     * Returns the number of URLs linked to the <code>DataObject</code>.
     * 
     * @return See above.
     */
    int getTermsCount() {
        Collection<TermAnnotationData> terms = getTerms();
        if (terms == null)
            return 0;
        return terms.size();
    }

    /**
     * Returns the collection of the URLs linked to the <code>DataObject</code>.
     * 
     * @return See above.
     */
    Collection<TermAnnotationData> getTerms() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return null;
        return data.getTerms();
    }

    /**
     * Returns the number of tags linked to the <code>DataObject</code>.
     * 
     * @return See above.
     */
    int getTagsCount() {
        Collection tags = getTags();
        if (tags == null)
            return 0;
        return tags.size();
    }

    /**
     * Returns the collection of the tags linked to the <code>DataObject</code>.
     * 
     * @return See above.
     */
    Collection<TagAnnotationData> getTags() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return new ArrayList<TagAnnotationData>();
        Collection<TagAnnotationData> tags = data.getTags();
        if (tags == null || tags.size() == 0)
            return new ArrayList<TagAnnotationData>();
        return (Collection<TagAnnotationData>) sorter.sort(tags);
    }

    /**
     * Returns the collection of the tags linked to the
     * <code>DataObject</code>s.
     * 
     * @return See above.
     */
    Collection<TagAnnotationData> getAllTags() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<TagAnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        Collection<TagAnnotationData> tags;
        List<TagAnnotationData> results = new ArrayList<TagAnnotationData>();
        List<Long> ids = new ArrayList<Long>();
        Iterator<TagAnnotationData> j;
        TagAnnotationData tag;
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getTags();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    if (!ids.contains(tag.getId())) {
                        results.add(tag);
                        ids.add(tag.getId());
                    }
                }
            }
        }
        return (Collection<TagAnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the collection of the tags that are linked to all the selected
     * objects.
     * 
     * @return See above.
     */
    Collection<TagAnnotationData> getCommonTags() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<TagAnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        Collection<TagAnnotationData> tags;
        Map<Long, Integer> ids = new HashMap<Long, Integer>();
        Iterator<TagAnnotationData> j;
        TagAnnotationData tag;

        Integer value;
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getTags();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    value = ids.get(tag.getId());
                    if (value != null) {
                        value++;
                    } else
                        value = 1;
                    ids.put(tag.getId(), value);
                }
            }
        }

        //Extract the common tags.
        //The number of selected objects.
        List<TagAnnotationData> results = new ArrayList<TagAnnotationData>();
        List<Long> count = new ArrayList<Long>();

        int max = r.size();
        i = r.entrySet().iterator();
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getTags();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    value = ids.get(tag.getId());
                    if (value != null && value == max && !count.contains(tag.getId())) {
                        results.add(tag);
                        count.add(tag.getId());
                    }
                }
            }
        }

        return (Collection<TagAnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the objects tagged by the specified object.
     * 
     * @param refTag The tag of reference.
     * @return See above.
     */
    Map<DataObject, Boolean> getTaggedObjects(AnnotationData refTag) {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        Map<DataObject, Boolean> m = new HashMap<DataObject, Boolean>();
        if (r == null)
            return m;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        Collection<TagAnnotationData> tags;
        Iterator<TagAnnotationData> j;
        TagAnnotationData tag;
        DataObject o;
        StructuredDataResults result;
        while (i.hasNext()) {
            e = i.next();
            result = e.getValue();
            tags = result.getTags();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    if (tag.getId() == refTag.getId()) {
                        o = (DataObject) result.getRelatedObject();
                        m.put(o, canDeleteLink(tag, result));
                        break;
                    }
                }
            }
        }
        return m;
    }

    /**
     * Returns the objects linked by the specified object.
     * 
     * @param refFile The file of reference.
     * @return See above.
     */
    Map<DataObject, Boolean> getObjectsWith(AnnotationData refFile) {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        Map<DataObject, Boolean> m = new HashMap<DataObject, Boolean>();
        if (r == null)
            return m;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        if (refFile instanceof TermAnnotationData) {
            Collection<TermAnnotationData> files;
            Iterator<TermAnnotationData> j;
            TermAnnotationData file;
            DataObject o;
            StructuredDataResults result;
            while (i.hasNext()) {
                e = i.next();
                result = e.getValue();
                files = result.getTerms();
                if (files != null) {
                    j = files.iterator();
                    while (j.hasNext()) {
                        file = j.next();
                        if (file.getId() == refFile.getId()) {
                            o = (DataObject) result.getRelatedObject();
                            m.put(o, canDeleteLink(file, result));
                            break;
                        }
                    }
                }
            }
        } else if (refFile instanceof FileAnnotationData) {
            Collection<FileAnnotationData> files;
            Iterator<FileAnnotationData> j;
            FileAnnotationData file;
            DataObject o;
            StructuredDataResults result;
            while (i.hasNext()) {
                e = i.next();
                result = e.getValue();
                files = result.getAttachments();
                if (files != null) {
                    j = files.iterator();
                    while (j.hasNext()) {
                        file = j.next();
                        if (file.getId() == refFile.getId()) {
                            o = (DataObject) result.getRelatedObject();
                            m.put(o, canDeleteLink(file, result));
                            break;
                        }
                    }
                }
            }
        } else if (refFile instanceof XMLAnnotationData) {
            Collection<XMLAnnotationData> files;
            Iterator<XMLAnnotationData> j;
            XMLAnnotationData file;
            DataObject o;
            StructuredDataResults result;
            while (i.hasNext()) {
                e = i.next();
                result = e.getValue();
                files = result.getXMLAnnotations();
                if (files != null) {
                    j = files.iterator();
                    while (j.hasNext()) {
                        file = j.next();
                        if (file.getId() == refFile.getId()) {
                            o = (DataObject) result.getRelatedObject();
                            m.put(o, canDeleteLink(file, result));
                            break;
                        }
                    }
                }
            }
        }
        return m;
    }

    /**
     * Returns the collection of the files linked to the 
     * <code>DataObject</code> at import.
     * 
     * @return See above.
     */
    Collection<FileAnnotationData> getCompanionFiles() {
        StructuredDataResults data = parent.getStructuredData();
        List<FileAnnotationData> list = new ArrayList<FileAnnotationData>();
        if (data == null)
            return list;
        Collection<FileAnnotationData> attachments = data.getAttachments();
        if (attachments == null)
            return list;
        Iterator<FileAnnotationData> i = attachments.iterator();
        FileAnnotationData f;
        String ns;
        while (i.hasNext()) {
            f = i.next();
            ns = f.getNameSpace();
            if (FileAnnotationData.COMPANION_FILE_NS.equals(ns) && f != originalMetadata) {
                list.add(f);
            }
        }
        if (refObject instanceof WellSampleData) {
            data = parent.getParentStructuredData();
            if (data != null) {
                attachments = data.getAttachments();
                if (attachments != null) {
                    while (i.hasNext()) {
                        f = i.next();
                        ns = f.getNameSpace();
                        if (FileAnnotationData.COMPANION_FILE_NS.equals(ns) && f != originalMetadata) {
                            list.add(f);
                        }
                    }
                }
            }
        }
        return (Collection<FileAnnotationData>) sorter.sort(list);
    }

    /**
     * Returns the collection of the attachments linked to the 
     * <code>DataObject</code>.
     * 
     * @return See above.
     */
    Collection<FileAnnotationData> getAttachments() {
        StructuredDataResults data = parent.getStructuredData();
        List<FileAnnotationData> l = new ArrayList<FileAnnotationData>();
        if (data == null)
            return l;
        Collection<FileAnnotationData> attachments = data.getAttachments();
        if (attachments == null)
            return l;
        Iterator<FileAnnotationData> i = attachments.iterator();
        FileAnnotationData f;
        String ns;
        while (i.hasNext()) {
            f = i.next();
            ns = f.getNameSpace();
            if (FileAnnotationData.COMPANION_FILE_NS.equals(ns)) {
                //tmp
                String name = f.getFileName();
                if (name.contains(FileAnnotationData.ORIGINAL_METADATA_NAME))
                    originalMetadata = f;
            } else if (!isNameSpaceExcluded(ns)) {
                l.add(f);
            }

        }
        if (getRefObject() instanceof WellSampleData) {
            data = parent.getParentStructuredData();
            if (data != null) {
                attachments = data.getAttachments();
                if (attachments != null) {
                    i = attachments.iterator();
                    while (i.hasNext()) {
                        f = i.next();
                        ns = f.getNameSpace();
                        if (FileAnnotationData.COMPANION_FILE_NS.equals(ns)) {
                            String name = f.getFileName();
                            if (name.contains(FileAnnotationData.ORIGINAL_METADATA_NAME))
                                originalMetadata = f;
                        }
                    }
                }
            }
        }
        return (Collection<FileAnnotationData>) sorter.sort(l);
    }

    /**
     * Returns the collection of the attachments linked to the 
     * <code>DataObject</code>.
     * 
     * @return See above.
     */
    Collection<FileAnnotationData> getAllAttachments() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<FileAnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();

        Collection<FileAnnotationData> files;
        List<FileAnnotationData> results = new ArrayList<FileAnnotationData>();
        List<Long> ids = new ArrayList<Long>();
        Iterator<FileAnnotationData> j;
        FileAnnotationData file;
        String ns;
        while (i.hasNext()) {
            e = i.next();
            files = e.getValue().getAttachments();
            if (files != null) {
                j = files.iterator();
                while (j.hasNext()) {
                    file = j.next();
                    ns = file.getNameSpace();
                    if (!isNameSpaceExcluded(ns)) {
                        if (!ids.contains(file.getId())) {
                            results.add(file);
                            ids.add(file.getId());
                        }
                    }
                }
            }
        }
        return (Collection<FileAnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the collection of the files that are linked to all the selected
     * objects.
     * 
     * @return See above.
     */
    Collection<FileAnnotationData> getCommonAttachments() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<FileAnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        Collection<FileAnnotationData> tags;
        Map<Long, Integer> ids = new HashMap<Long, Integer>();
        Iterator<FileAnnotationData> j;
        FileAnnotationData tag;

        Integer value;
        String ns;
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getAttachments();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    ns = tag.getNameSpace();
                    if (!isNameSpaceExcluded(ns)) {
                        value = ids.get(tag.getId());
                        if (value != null) {
                            value++;
                        } else
                            value = 1;
                        ids.put(tag.getId(), value);
                    }

                }
            }
        }

        //Extract the common tags.
        //The number of selected objects.
        List<FileAnnotationData> results = new ArrayList<FileAnnotationData>();
        List<Long> count = new ArrayList<Long>();

        int max = r.size();
        i = r.entrySet().iterator();
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getAttachments();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    value = ids.get(tag.getId());
                    if (value != null && value == max && !count.contains(tag.getId())) {
                        results.add(tag);
                        count.add(tag.getId());
                    }
                }
            }
        }

        return (Collection<FileAnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the collection of others annotations like Term, XML
     * 
     * @return See above.
     */
    Collection<AnnotationData> getOtherAnnotations() {
        StructuredDataResults data = parent.getStructuredData();
        List<AnnotationData> l = new ArrayList<AnnotationData>();
        if (data == null)
            return l;
        Collection<XMLAnnotationData> xml = data.getXMLAnnotations();
        if (xml != null && !xml.isEmpty())
            l.addAll(xml);
        Collection<AnnotationData> others = data.getOtherAnnotations();
        if (others != null && !others.isEmpty())
            l.addAll(others);
        return l;
    }

    /**
     * Returns the collection of the other annotations linked to the 
     * <code>DataObject</code>.
     * 
     * @return See above.
     */
    Collection<AnnotationData> getAllOtherAnnotations() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<AnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();

        Collection<XMLAnnotationData> files;
        Collection<AnnotationData> others;
        List<AnnotationData> results = new ArrayList<AnnotationData>();
        List<Long> ids = new ArrayList<Long>();
        Iterator<XMLAnnotationData> j;
        Iterator<AnnotationData> k;
        XMLAnnotationData file;
        AnnotationData other;
        while (i.hasNext()) {
            e = i.next();
            files = e.getValue().getXMLAnnotations();
            if (files != null) {
                j = files.iterator();
                while (j.hasNext()) {
                    file = j.next();
                    if (!ids.contains(file.getId())) {
                        results.add(file);
                        ids.add(file.getId());
                    }
                }
            }
            others = e.getValue().getOtherAnnotations();
            if (others != null) {
                k = others.iterator();
                while (k.hasNext()) {
                    other = k.next();
                    if (!ids.contains(other.getId())) {
                        results.add(other);
                        ids.add(other.getId());
                    }
                }
            }
        }
        return (Collection<AnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the modulo information if any associated to a given image.
     *
     * @return See above.
     */
    Map<Integer, ModuloInfo> getModulo() {
        Collection<XMLAnnotationData> annotations = getXMLAnnotations();
        Map<Integer, ModuloInfo> modulo = new HashMap<Integer, ModuloInfo>();
        if (CollectionUtils.isEmpty(annotations))
            return modulo;
        ModuloParser parser;
        Iterator<XMLAnnotationData> i = annotations.iterator();
        XMLAnnotationData data;
        List<ModuloInfo> infos;
        Iterator<ModuloInfo> j;
        ModuloInfo info;
        while (i.hasNext()) {
            data = i.next();
            parser = new ModuloParser(data.getText());
            try {
                parser.parse();
                infos = parser.getModulos();
                j = infos.iterator();
                while (j.hasNext()) {
                    info = j.next();
                    modulo.put(info.getModuloIndex(), info);
                }
            } catch (Exception e) {
                LogMessage msg = new LogMessage();
                msg.append("Error while reading modulo annotation.");
                msg.print(e);
                MetadataViewerAgent.getRegistry().getLogger().error(this, msg);
            }
        }
        return modulo;
    }

    /**
      * Returns the collection of XML annotations linked to the 
      * <code>DataObject</code>.
      * 
      * @return See above.
      */
    Collection<XMLAnnotationData> getXMLAnnotations() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<XMLAnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();

        Collection<XMLAnnotationData> files;
        List<AnnotationData> results = new ArrayList<AnnotationData>();
        List<Long> ids = new ArrayList<Long>();
        Iterator<XMLAnnotationData> j;
        XMLAnnotationData file;
        while (i.hasNext()) {
            e = i.next();
            files = e.getValue().getXMLAnnotations();
            if (files != null) {
                j = files.iterator();
                while (j.hasNext()) {
                    file = j.next();
                    if (!ids.contains(file.getId())) {
                        results.add(file);
                        ids.add(file.getId());
                    }
                }
            }
        }
        return (Collection<XMLAnnotationData>) sorter.sort(results);
    }

    /**
     * Returns the collection of the other annotations that are linked to all
     * the selected objects.
     * 
     * @return See above.
     */
    Collection<AnnotationData> getCommonOtherAnnotations() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return new ArrayList<AnnotationData>();
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = r.entrySet().iterator();
        Collection<XMLAnnotationData> tags;
        Collection<AnnotationData> others;
        Map<Long, Integer> ids = new HashMap<Long, Integer>();
        Iterator<XMLAnnotationData> j;
        XMLAnnotationData tag;
        Iterator<AnnotationData> k;
        AnnotationData other;
        Integer value;
        String ns;
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getXMLAnnotations();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    value = ids.get(tag.getId());
                    if (value != null) {
                        value++;
                    } else
                        value = 1;
                    ids.put(tag.getId(), value);
                }
            }
            others = e.getValue().getOtherAnnotations();
            if (others != null) {
                k = others.iterator();
                while (k.hasNext()) {
                    other = k.next();
                    value = ids.get(other.getId());
                    if (value != null) {
                        value++;
                    } else
                        value = 1;
                    ids.put(other.getId(), value);
                }
            }
        }

        //The number of selected objects.
        List<AnnotationData> results = new ArrayList<AnnotationData>();
        List<Long> count = new ArrayList<Long>();

        int max = r.size();
        i = r.entrySet().iterator();
        while (i.hasNext()) {
            e = i.next();
            tags = e.getValue().getXMLAnnotations();
            if (tags != null) {
                j = tags.iterator();
                while (j.hasNext()) {
                    tag = j.next();
                    value = ids.get(tag.getId());
                    if (value != null && value == max && !count.contains(tag.getId())) {
                        results.add(tag);
                        count.add(tag.getId());
                    }
                }
            }
            others = e.getValue().getOtherAnnotations();
            if (others != null) {
                k = others.iterator();
                while (k.hasNext()) {
                    other = k.next();
                    value = ids.get(other.getId());
                    if (value != null && value == max && !count.contains(other.getId())) {
                        results.add(other);
                        count.add(other.getId());
                    }
                }
            }
        }

        return (Collection<AnnotationData>) sorter.sort(results);
    }

    /** 
     * Returns the objects displaying analysis results.
     * 
     * @return See above.
     */
    List<AnalysisResultsItem> getAnalysisResults() {
        Map<DataObject, StructuredDataResults> r = parent.getAllStructuredData();
        if (r == null)
            return null;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> j = r.entrySet().iterator();
        StructuredDataResults data;
        Collection<FileAnnotationData> attachments;
        Iterator<FileAnnotationData> i;
        FileAnnotationData f;
        String ns;
        AnalysisResultsItem item;

        Map<Long, FileAnnotationData> ids = new HashMap<Long, FileAnnotationData>();

        while (j.hasNext()) {
            e = j.next();
            data = e.getValue();
            if (data != null) {
                attachments = data.getAttachments();
                if (attachments != null) {
                    i = attachments.iterator();
                    while (i.hasNext()) {
                        f = i.next();
                        ns = f.getNameSpace();
                        if (FileAnnotationData.FLIM_NS.equals(ns)) {
                            ids.put(f.getId(), f);
                        }
                    }
                }
            }
        }

        if (ids.size() == 0)
            return null;
        List<Long> orderedIds = (List<Long>) sorter.sort(ids.keySet());

        int index = 0; //this should be modified.
        Iterator<Long> k = orderedIds.iterator();
        Long id;
        List<AnalysisResultsItem> results = new ArrayList<AnalysisResultsItem>();
        item = null;
        int n = 6;
        int number = 1;
        while (k.hasNext()) {
            id = k.next();
            if (index == 0) {
                item = new AnalysisResultsItem((DataObject) getRefObject(), FileAnnotationData.FLIM_NS, number);
                results.add(item);
                number++;
            } else if (index == n) {
                index = -1;
            }
            item.addAttachment(ids.get(id));
            index++;
        }
        return results;
    }

    /**
     * Returns the companion file generated while importing the file
     * and containing the metadata found in the file, or <code>null</code>
     * if no file was generated.
     * 
     * @return See above
     */
    FileAnnotationData getOriginalMetadata() {
        return originalMetadata;
    }

    /**
     * Returns the number of people who viewed the image.
     * 
     * @return See above.
     */
    int getViewedByCount() {
        Collection viewedBy = getViewedBy();
        if (viewedBy == null)
            return 0;
        return viewedBy.size();
    }

    /**
     * Returns the collection of people who viewed the image.
     * 
     * @return See above.
     */
    Collection getViewedBy() {
        /*
        StructuredDataResults data = parent.getStructuredData();
        if (data == null) return null;
        Collection l = data.getViewedBy(); 
        if (l == null) return null;
        Iterator i = l.iterator();
        ViewedByDef def;
        long userID = MetadataViewerAgent.getUserDetails().getId();
        List<ViewedByDef> results = new ArrayList<ViewedByDef>();
        Map<String, ViewedByDef> 
           m = new HashMap<String, ViewedByDef>();
        String value;
        List<String> names = new ArrayList<String>();
        ExperimenterData exp;
        while (i.hasNext()) {
           def = (ViewedByDef) i.next();
           exp = def.getExperimenter();
           if (exp.getId() != userID) {
        value = formatOwner(exp);
        names.add(value);
        m.put(value, def);
        def.setFormattedExperimenter(value);
           }
        }
        l = sorter.sort(names);
        i = l.iterator();
        while (i.hasNext()) {
           value = (String) i.next();
           results.add(m.get(value));
        }
        return results; 
        */
        return null;
    }

    /**
     * Returns the number of ratings for that object.
     * 
     * @param filter One of the filtering components defined by this class.
     * @return See above.
     */
    int getRatingCount(int filter) {
        Map<DataObject, StructuredDataResults> data = parent.getAllStructuredData();
        if (data == null)
            return 0;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = data.entrySet().iterator();
        Collection<RatingAnnotationData> ratings;
        StructuredDataResults results;
        Iterator<RatingAnnotationData> j;
        int n = 0;
        long userID = getUserID();
        while (i.hasNext()) {
            e = i.next();
            results = e.getValue();
            ratings = results.getRatings();
            if (ratings != null) {

                switch (filter) {
                case ALL:
                    n += ratings.size();
                    break;
                case ME:
                    j = ratings.iterator();
                    while (j.hasNext()) {
                        if (j.next().getOwner().getId() == userID)
                            n++;
                    }
                case OTHER:
                default:
                    j = ratings.iterator();
                    while (j.hasNext()) {
                        if (j.next().getOwner().getId() != userID)
                            n++;
                    }
                }
            }
        }
        return n;
    }

    /**
     * Returns the rating annotation related to the logged in user,
     * or <code>null</code> if no annotation.
     * 
     * @return See above.
     */
    RatingAnnotationData getUserRatingAnnotation() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return null;
        Collection<RatingAnnotationData> ratings = data.getRatings();
        if (ratings == null || ratings.size() == 0)
            return null;
        Iterator<RatingAnnotationData> i = ratings.iterator();
        RatingAnnotationData rate;
        long id = getUserID();
        while (i.hasNext()) {
            rate = i.next();
            if (rate.getOwner().getId() == id)
                return rate;
        }
        return null;
    }

    /**
     * Returns the rating annotation related to the logged in user,
     * or <code>null</code> if no annotation.
     * 
     * @return See above.
     */
    Map<DataObject, RatingAnnotationData> getAllUserRatingAnnotation() {
        Map<DataObject, StructuredDataResults> data = parent.getAllStructuredData();
        if (data == null)
            return null;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = data.entrySet().iterator();
        Collection<RatingAnnotationData> ratings;
        StructuredDataResults results;
        Iterator<RatingAnnotationData> j;
        RatingAnnotationData rating;
        Map<DataObject, RatingAnnotationData> map = new HashMap<DataObject, RatingAnnotationData>();
        long id = getUserID();
        while (i.hasNext()) {
            e = i.next();
            results = e.getValue();
            if (results != null) {
                ratings = results.getRatings();
                if (ratings != null) {
                    j = ratings.iterator();
                    while (j.hasNext()) {
                        rating = j.next();
                        if (rating.getOwner().getId() == id) {
                            map.put(e.getKey(), rating);
                        }
                    }
                }
            }
        }

        return map;
    }

    /**
     * Returns the published annotation.
     * 
     * @return See above.
     */
    BooleanAnnotationData getPublishedAnnotation() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return null;
        return null;
    }

    /**
     * Returns the rating done by the current user.
     * 
     * @return See above
     */
    int getUserRating() {
        RatingAnnotationData data = getUserRatingAnnotation();
        if (data == null)
            return 0;
        return data.getRating();
    }

    /**
     * Returns the rating done by the current user.
     * 
     * @return See above
     */
    int getAllUserRating() {
        Map<DataObject, RatingAnnotationData> map = getAllUserRatingAnnotation();
        if (map == null)
            return 0;
        Collection<RatingAnnotationData> ratings = map.values();
        Iterator<RatingAnnotationData> i = ratings.iterator();
        int n = 0;
        while (i.hasNext()) {
            n += i.next().getRating();
        }
        return n;
    }

    /** 
     * Returns the average rating value.
     * 
     * @param filter One of the filtering components defined by this class.
     * @return See above.
     */
    int getRatingAverage(int filter) {
        Map<DataObject, StructuredDataResults> data = parent.getAllStructuredData();
        if (data == null)
            return 0;
        Entry<DataObject, StructuredDataResults> e;
        Iterator<Entry<DataObject, StructuredDataResults>> i = data.entrySet().iterator();
        Collection<RatingAnnotationData> ratings;
        StructuredDataResults results;
        Iterator<RatingAnnotationData> j;
        RatingAnnotationData rating;
        int n = 0;
        int value = 0;
        long userID = getUserID();
        while (i.hasNext()) {
            e = i.next();
            results = e.getValue();
            ratings = results.getRatings();
            if (ratings != null) {

                j = ratings.iterator();
                switch (filter) {
                case ALL:
                    while (j.hasNext()) {
                        rating = j.next();
                        value += rating.getRating();
                        n++;
                    }
                    break;
                case ME:

                    while (j.hasNext()) {
                        rating = j.next();
                        if (rating.getOwner().getId() == userID) {
                            value += rating.getRating();
                            n++;
                        }
                    }
                case OTHER:
                default:
                    j = ratings.iterator();
                    while (j.hasNext()) {
                        rating = j.next();
                        if (rating.getOwner().getId() != userID) {
                            value += rating.getRating();
                            n++;
                        }
                    }
                }
            }
        }
        if (n == 0)
            return 0;
        return value / n;
    }

    /**
     * Returns the number of textual annotations for that object.
     * 
     * @return See above.
     */
    int getTextualAnnotationCount() {
        Collection<TextualAnnotationData> annotations = getTextualAnnotations();
        if (annotations == null)
            return 0;
        return annotations.size();
    }

    /**
     * Returns the collection of textual annotations.
     * 
     * @return See above.
     */
    Collection<TextualAnnotationData> getTextualAnnotations() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return null;
        return data.getTextualAnnotations();
    }

    /**
     * Returns the annotations ordered by date.
     * 
     * @return See above.
     */
    List getTextualAnnotationsByDate() {
        if (textualAnnotationsByDate != null)
            return textualAnnotationsByDate;
        textualAnnotationsByDate = (List) getTextualAnnotations();
        sortAnnotationByDate(textualAnnotationsByDate);
        return textualAnnotationsByDate;
    }

    /**
     * Returns the annotation made by the currently selected user.
     * 
     * @return See above.
     */
    TextualAnnotationData getLastUserAnnotation() {
        Map<Long, List> m = getTextualAnnotationByOwner();
        long userID = MetadataViewerAgent.getUserDetails().getId();
        List l = m.get(userID);
        if (l == null || l.size() == 0)
            return null;
        return (TextualAnnotationData) l.get(0);
    }

    /**
     * Returns the annotations organized by users.
     * 
     * @return See above.
     */
    Map<Long, List> getTextualAnnotationByOwner() {
        if (textualAnnotationsByUsers != null && textualAnnotationsByUsers.size() > 0)
            return textualAnnotationsByUsers;
        textualAnnotationsByUsers = new HashMap<Long, List>();
        Collection<TextualAnnotationData> original = getTextualAnnotations();
        if (original == null)
            return textualAnnotationsByUsers;
        Iterator i = original.iterator();
        AnnotationData annotation;
        Long ownerID;
        List<AnnotationData> userAnnos;
        while (i.hasNext()) {
            annotation = (AnnotationData) i.next();
            ownerID = Long.valueOf(annotation.getOwner().getId());
            userAnnos = textualAnnotationsByUsers.get(ownerID);
            if (userAnnos == null) {
                userAnnos = new ArrayList<AnnotationData>();
                textualAnnotationsByUsers.put(ownerID, userAnnos);
            }
            userAnnos.add(annotation);
        }
        i = textualAnnotationsByUsers.keySet().iterator();

        while (i.hasNext()) {
            ownerID = (Long) i.next();
            sortAnnotationByDate(textualAnnotationsByUsers.get(ownerID));
        }
        //sort users by name.
        /* REMOVE Comments when user is not root
        Map groups = (Map) MetadataViewerAgent.getRegistry().lookup(
                    LookupNames.USER_GROUP_DETAILS);
        GroupData g;
        i = groups.keySet().iterator();
        ViewerSorter sorter = new ViewerSorter();
        List orderedUsers = new ArrayList();
        while (i.hasNext()) {
         g = (GroupData) i.next();
         orderedUsers.addAll(sorter.sort((Set) groups.get(g)));
        }
        Map<Long, List> orderedAnnotationsByUser = new HashMap<Long, List>();
        i = orderedUsers.iterator();
        ExperimenterData exp;
        List ann;
        while (i.hasNext()) {
         exp = (ExperimenterData) i.next();
         ann = textualAnnotationsByUsers.get(exp.getId());
         if (ann != null)
        orderedAnnotationsByUser.put(exp.getId(), ann);
             
        }
        textualAnnotationsByUsers = orderedAnnotationsByUser;
        */
        return textualAnnotationsByUsers;
    }

    /**
     * Returns <code>true</code> if it is the same object,
     * <code>false</code> otherwise.
     * 
     * @param object The object to handle.
     * @return See above.
     */
    boolean isSameObject(Object object) {
        return parent.isSameObject(object);
    }

    /** 
     * Sets the object of reference.
     * 
     * @param refObject The value to set.
     */
    void setRootObject(Object refObject) {
        boolean b = isSameObject(this.refObject);
        set = null;
        largeImage = false;
        this.refObject = refObject;
        if (existingTags != null)
            existingTags.clear();
        existingTags = null;
        if (textualAnnotationsByUsers != null)
            textualAnnotationsByUsers.clear();
        textualAnnotationsByUsers = null;
        if (textualAnnotationsByDate != null)
            textualAnnotationsByDate.clear();
        textualAnnotationsByDate = null;
        if (existingAttachments != null)
            existingAttachments.clear();
        existingAttachments = null;

        if (resultsLoader != null)
            resultsLoader.clear();
        resultsLoader = null;
        if (!b) {
            parentRefObject = null;
            gpRefObject = null;
            if (emissionsWavelengths != null)
                emissionsWavelengths.clear();
            emissionsWavelengths = null;
            if (channelAcquisitionDataMap != null)
                channelAcquisitionDataMap.clear();
            if (channelPlaneInfoMap != null)
                channelPlaneInfoMap.clear();
            imageAcquisitionData = null;
            instrumentData = null;
            originalMetadata = null;
            if (refObject instanceof ImageData || refObject instanceof WellSampleData) {
                fireChannelEnumerationsLoading();
                fireImageEnumerationsLoading();
                fireLargeImageLoading();
            } else if (refObject instanceof ExperimenterData) {
                fireExperimenterPhotoLoading();
            }
            if (renderer != null) {
                renderer.discard();
                renderer = null;
            }
        }
    }

    /**
     * Sets the parent of the object of reference.
     * 
     * @param parentRefObject The value to set.
     * @param gpRefObject The value to set.
     */
    void setParentRootObject(Object parentRefObject, Object gpRefObject) {
        this.parentRefObject = parentRefObject;
        this.gpRefObject = gpRefObject;
    }

    /**
     * Returns the parent of the object of reference.
     * 
     * @return See above.
     */
    Object getParentRootObject() {
        return parentRefObject;
    }

    /**
     * Returns the parent of the object of reference.
     * 
     * @return See above.
     */
    Object getGrandParentRootObject() {
        return gpRefObject;
    }

    /**
     * Returns the owner of the reference object or <code>null</code>
     * if the object is not a <code>DataObject</code>.
     * 
     * @return See above.
     */
    ExperimenterData getRefObjectOwner() {
        Object ref = getRefObject();
        if (ref instanceof DataObject)
            return getOwner((DataObject) ref);
        return null;
    }

    /** Fires an asynchronous retrieval of existing tags. */
    void loadExistingTags() {
        EditorLoader l;
        boolean exist = false;
        Iterator i = loaders.iterator();
        while (i.hasNext()) {
            l = (EditorLoader) i.next();
            if (l instanceof TagsLoader) {
                exist = true;
                break;
            }
        }
        if (exist)
            return;
        TagsLoader loader = new TagsLoader(component, parent.getSecurityContext(), canRetrieveAll());
        loader.load();
        loaders.add(loader);
    }

    /** 
     * Fires an asynchronous retrieval of existing attachments 
     * for the currently logged in user. 
     */
    void loadExistingAttachments() {
        EditorLoader l;
        boolean exist = false;
        Iterator i = loaders.iterator();
        while (i.hasNext()) {
            l = (EditorLoader) i.next();
            if (l instanceof AttachmentsLoader) {
                exist = true;
                break;
            }
        }
        if (exist)
            return;
        AttachmentsLoader loader = new AttachmentsLoader(component, parent.getSecurityContext(), canRetrieveAll());
        loader.load();
        loaders.add(loader);
    }

    /** Cancels any ongoing tags retrieval. */
    void cancelExistingTagsLoading() {
        Iterator i = loaders.iterator();
        EditorLoader loader;
        List<EditorLoader> toKeep = new ArrayList<EditorLoader>();
        while (i.hasNext()) {
            loader = (EditorLoader) i.next();
            if (loader instanceof TagsLoader)
                loader.cancel();
            else
                toKeep.add(loader);
        }
        loaders.clear();
        loaders.addAll(toKeep);
    }

    /** Fires an asynchronous retrieval of the channel data. */
    void loadChannelData() {
        Object refObject = getRefObject();
        ImageData data = null;
        if (refObject instanceof ImageData)
            data = (ImageData) refObject;
        else if (refObject instanceof WellSampleData) {
            WellSampleData wsd = (WellSampleData) refObject;
            data = wsd.getImage();
            if (data != null && data.getId() < 0)
                data = null;
        }
        if (data == null)
            return;
        try {
            PixelsData pixs = data.getDefaultPixels();
            ChannelDataLoader loader = new ChannelDataLoader(component, parent.getSecurityContext(), pixs.getId(),
                    parent.getUserID());
            loader.load();
            loaders.add(loader);
        } catch (Exception e) {
        }
    }

    /** Cancels any ongoing tags retrieval. */
    void cancelChannelDataLoading() {
        Iterator i = loaders.iterator();
        EditorLoader loader;
        List<EditorLoader> toKeep = new ArrayList<EditorLoader>();
        while (i.hasNext()) {
            loader = (EditorLoader) i.next();
            if (loader instanceof ChannelDataLoader)
                loader.cancel();
            else
                toKeep.add(loader);
        }
        loaders.clear();
        loaders.addAll(toKeep);
    }

    /**
     * Sets the collection of existing tags.
     * 
     * @param tags The value to set.
     */
    void setExistingTags(Collection tags) {
        if (tags != null)
            existingTags = sorter.sort(tags);
        Iterator i = loaders.iterator();
        EditorLoader loader;
        EditorLoader toRemove = null;
        while (i.hasNext()) {
            loader = (EditorLoader) i.next();
            if (loader instanceof TagsLoader) {
                toRemove = loader;
                break;
            }
        }
        if (toRemove != null)
            loaders.remove(toRemove);
    }

    /**
     * Returns the collection of existing tags.
     * 
     * @return See above.
     */
    Collection getExistingTags() {
        return existingTags;
    }

    /**
     * Sets the collection of existing attachments.
     * 
     * @param attachments The value to set.
     */
    void setExistingAttachments(Collection attachments) {
        if (attachments != null)
            existingAttachments = sorter.sort(attachments);
        Iterator i = loaders.iterator();
        EditorLoader loader;
        EditorLoader toRemove = null;
        while (i.hasNext()) {
            loader = (EditorLoader) i.next();
            if (loader instanceof AttachmentsLoader) {
                toRemove = loader;
                break;
            }
        }
        if (toRemove != null)
            loaders.remove(toRemove);
    }

    /**
     * Returns the collection of existing attachments.
     * 
     * @return See above.
     */
    Collection getExistingAttachments() {
        return existingAttachments;
    }

    /**
     * Sets the channel data.
     * 
     * @param data The value to set.
     */
    void setChannelData(Map data) {
        List l = sorter.sort(data.keySet());
        emissionsWavelengths = new LinkedHashMap();
        Iterator i = l.iterator();
        Object channel;
        while (i.hasNext()) {
            channel = i.next();
            emissionsWavelengths.put(channel, data.get(channel));
        }
    }

    /**
     * Returns the channels data related to the image.
     * 
     * @return See above.
     */
    Map getChannelData() {
        if (getRndIndex() == MetadataViewer.RND_SPECIFIC) {
            if (renderer != null) {
                List<ChannelData> l = renderer.getChannelData();
                Map m = new LinkedHashMap();
                Iterator<ChannelData> i = l.iterator();
                ChannelData data;
                while (i.hasNext()) {
                    data = i.next();
                    m.put(data, renderer.getChannelColor(data.getIndex()));
                }
                return m;
            }
        }
        return emissionsWavelengths;
    }

    /**
     * Starts an asynchronous call to save the annotations.
     * 
     * @param object The annotation/link to add or remove.
     * @param metadata   The metadata to save.
     * @param asynch    Pass <code>true</code> to save data asynchronously,
      *                 <code>false</code> otherwise.
     */
    void fireAnnotationSaving(DataToSave object, List<Object> metadata, boolean asynch) {
        Object ref = getRefObject();
        if (ref instanceof DataObject) {
            DataObject data = (DataObject) ref;
            if (data instanceof WellSampleData) {
                data = ((WellSampleData) ref).getImage();
            }
            List<AnnotationData> list = null;
            if (CollectionUtils.isNotEmpty(toDelete)) {
                list = new ArrayList<AnnotationData>();
                Iterator<AnnotationData> i = toDelete.iterator();
                while (i.hasNext())
                    list.add(i.next());
                toDelete.clear();
            }
            parent.saveData(object, list, metadata, data, asynch);
        }
    }

    /**
     * Updates the specified channels. If <code>applyToAll</code> is
     * <code>true</code>, the channels of all the images in the dataset or
     * the plate will be updated,
     * 
     * @param channels The channels to update.
     * @param applyToAll Pass <code>true</code> to update the channels of all
     *                   the images in the dataset or the plate.
     *                   Pass <code>false</code> to only update the image.
     */
    void fireChannelSaving(List<ChannelData> channels, boolean applyToAll) {
        DataObject object = null;
        if (applyToAll) {
            if (parentRefObject instanceof DatasetData)
                object = (DataObject) parentRefObject;
            else if (gpRefObject instanceof PlateData)
                object = (DataObject) gpRefObject;
            else
                return;
        } else
            object = (DataObject) refObject;
        ChannelDataSaver loader = new ChannelDataSaver(component, getSecurityContext(), channels, object);
        loader.load();
    }

    /**
     * Starts an asynchronous call to delete the annotations.
     * 
     * @param annotations The annotations to delete.
     */
    void fireAnnotationsDeletion(List<AnnotationData> annotations) {
        Object ref = getRefObject();
        if (ref instanceof DataObject) {
            DataObject data = (DataObject) ref;
            if (data instanceof WellSampleData) {
                data = ((WellSampleData) ref).getImage();
            }
            parent.saveData(null, annotations, new ArrayList<Object>(), data, true);
        }
    }

    /**
     * Starts an asynchronous call to check the removal of the given FileAnnotations.
     * 
     * @param data     The annotations to check
     */
    void fireFileAnnotationRemoveCheck(List<FileAnnotationData> annotations) {
        FileAnnotationChecker check = new FileAnnotationChecker(component, getSecurityContext(), annotations);
        check.load();
    }

    /**
     * Starts an asynchronous call to update the experimenter or the group.
     * 
     * @param data     The experimenter or the group to save.
     * @param asynch Pass <code>true</code> to save data asynchronously,
      *              <code>false</code> otherwise.
     */
    void fireAdminSaving(Object data, boolean asynch) {
        if (data instanceof ExperimenterData || data instanceof AdminObject || data instanceof GroupData)
            parent.updateAdminObject(data, asynch);
    }

    /**
     * Returns <code>true</code> if the imported set of pixels has been 
     * archived, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isArchived() {
        return isArchived(getImage());
    }

    /**
     * Returns <code>true</code> if the imported set of pixels has been 
     * archived, <code>false</code> otherwise.
     * 
     * @param ho The object to handle.
     * @return See above.
     */
    boolean isArchived(DataObject ho) {
        ImageData img = null;
        if (ho instanceof ImageData)
            img = (ImageData) ho;
        if (img == null)
            return false;
        return img.isArchived();
    }

    /** 
     * Starts an asynchronous loading. 
     * 
     * @param file The file where to download the content.
     * If it is a multi-images file a zip will be created.
     * @param override Flag indicating to override the existing file if it
      * exists, <code>false</code> otherwise.
     */
    void download(File file, boolean override) {
        if (refObject instanceof ImageData) {
            downloadImages(file, override);
        } else if (refObject instanceof FileAnnotationData) {
            downloadFiles(file, override);
        }
    }

    /** 
     * Starts an asynchronous call to retrieve disk space information. 
     * 
     * @param type    Either <code>ExperimenterData</code> or
     *             <code>GroupData</code>.
     * @param id    The identifier of the user or group.
     */
    void loadDiskSpace(Class type, long id) {
        DiskSpaceLoader loader = new DiskSpaceLoader(component, parent.getSecurityContext(), type, id);
        loader.load();
        loaders.add(loader);
    }

    /** Cancels the disk space loading. */
    void cancelDiskSpaceLoading() {
        Iterator i = loaders.iterator();
        EditorLoader loader;
        List<EditorLoader> toKeep = new ArrayList<EditorLoader>();
        while (i.hasNext()) {
            loader = (EditorLoader) i.next();
            if (loader instanceof DiskSpaceLoader) {
                loader.cancel();
            } else
                toKeep.add(loader);
        }
        loaders.clear();
        loaders.addAll(toKeep);
    }

    /**
     * Fires an asynchronous call to modify the password.
     * 
     * @param old The old password.
     * @param confirm The new password.
     */
    void changePassword(String old, String confirm) {
        EditorLoader loader = new PasswordEditor(component, parent.getSecurityContext(), old, confirm);
        loader.load();
        loaders.add(loader);
    }

    /**
     * Returns <code>true</code> if the object of reference is tag of another
     * tag, <code>false</code> if it is tag linked to images, without any links
     * or if it is not a tag.
     * 
     * @return See above.
     */
    boolean hasTagsAsChildren() {
        Object ref = getRefObject();
        if (!(ref instanceof TagAnnotationData))
            return false;
        TagAnnotationData tag = (TagAnnotationData) ref;
        Set tags = tag.getTags();
        if (tags != null && tags.size() > 0)
            return true;
        return false;
    }

    /**
     * Returns the owner of the passed annotation.
     * 
     * @param data The annotation to handle.
     * @return  See above.
     */
    ExperimenterData getOwner(DataObject data) {
        if (data == null)
            return null;
        long id = data.getOwner().getId();
        return MetadataViewerAgent.getExperimenter(id);
    }

    /**
     * Fires an asynchronous retrieval of containers hosting the currently
     * edited object. 
     */
    void loadParents() {
        parent.loadParents();
    }

    /** Cancels any ongoing parents retrieval. */
    void cancelParentsLoading() {
    }

    /**
     * Returns <code>true</code> if the image has been viewed by other users,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean hasBeenViewedBy() {
        if (!(getRefObject() instanceof ImageData))
            return false;
        return getViewedByCount() != 0;
    }

    /** Loads the image metadata enumerations. */
    void fireImageEnumerationsLoading() {
        EnumerationLoader loader = new EnumerationLoader(component, parent.getSecurityContext(),
                EnumerationLoader.IMAGE);
        loader.load();
    }

    /** Loads the channel metadata enumerations. */
    void fireChannelEnumerationsLoading() {
        EnumerationLoader loader = new EnumerationLoader(component, parent.getSecurityContext(),
                EnumerationLoader.CHANNEL);
        loader.load();
    }

    /** Loads the image acquisition data. */
    void fireImagAcquisitionDataLoading() {
        Object ref = getRefObject();
        ImageData data = null;
        if (ref instanceof WellSampleData) {
            data = ((WellSampleData) ref).getImage();
        } else if (ref instanceof ImageData) {
            data = (ImageData) ref;
        }
        if (data == null)
            return;
        AcquisitionDataLoader loader = new AcquisitionDataLoader(component, parent.getSecurityContext(), data);
        loader.load();
    }

    /** 
     * Loads the image acquisition data. 
     * 
     * @param channel The channel to handle.
     */
    void fireChannelAcquisitionDataLoading(ChannelData channel) {
        AcquisitionDataLoader loader = new AcquisitionDataLoader(component, parent.getSecurityContext(), channel);
        loader.load();
    }

    /**
     * Starts an asynchronous call to load the instrument linked to the image.
     * 
     * @param instrumentID The id of the instrument.
     */
    void fireInstrumentDataLoading(long instrumentID) {
        InstrumentDataLoader loader = new InstrumentDataLoader(component, parent.getSecurityContext(),
                instrumentID);
        loader.load();
    }

    /**
     * Sets the image acquisition data.
     * 
     * @param data The value to set.
     */
    void setImageAcquisitionData(ImageAcquisitionData data) {
        imageAcquisitionData = data;
    }

    /**
     * Returns the image acquisition data.
     * 
     * @return See above.
     */
    ImageAcquisitionData getImageAcquisitionData() {
        return imageAcquisitionData;
    }

    /**
     * Sets the acquisition data for the specified channel.
     * 
     * @param index The index of the channel.
     * @param data  The value to set.
     */
    void setChannelAcquisitionData(int index, ChannelAcquisitionData data) {
        if (channelAcquisitionDataMap == null)
            channelAcquisitionDataMap = new HashMap<Integer, ChannelAcquisitionData>();
        channelAcquisitionDataMap.put(index, data);
    }

    /**
     * Returns the channel acquisition data.
     * 
     * @param index The index of the channels.
     * @return See above.
     */
    ChannelAcquisitionData getChannelAcquisitionData(int index) {
        if (channelAcquisitionDataMap == null)
            return null;
        return channelAcquisitionDataMap.get(index);
    }

    /**
     * Sets the collection of plane info for the specified channel.
     * 
     * @param index The index of the channel.
     * @param data  The value to set.
     */
    void setPlaneInfo(int index, Collection data) {
        if (channelPlaneInfoMap == null)
            channelPlaneInfoMap = new HashMap<Integer, Collection>();
        channelPlaneInfoMap.put(index, sortPlane(data));
    }

    /**
     * Returns the channel acquisition data.
     * 
     * @param index The index of the channel.
     * @return See above.
     */
    Collection getChannelPlaneInfo(int index) {
        if (channelPlaneInfoMap == null)
            return null;
        return channelPlaneInfoMap.get(index);
    }

    /**
     * Returns the collection of objects corresponding to the passed name.
     * 
     * @param name The name of the enumeration.
     * @return See above.
     */
    List<EnumerationObject> getImageEnumerations(String name) {
        if (imageEnumerations != null)
            return (List<EnumerationObject>) imageEnumerations.get(name);
        return new ArrayList<EnumerationObject>();
    }

    /**
     * Returns the collection of objects corresponding to the passed name.
     * 
     * @param name The name of the enumeration.
     * @return See above.
     */
    List<EnumerationObject> getChannelEnumerations(String name) {
        if (channelEnumerations != null)
            return (List<EnumerationObject>) channelEnumerations.get(name);
        return new ArrayList<EnumerationObject>();
    }

    /**
     * Returns the enumeration object corresponding to the passed enumeration 
     * name and value for the image metadata.
     * 
     * @param name   The type of enumeration.
     * @param value   The value to select.
     * @return See above.
     */
    Object getImageEnumerationSelected(String name, String value) {
        List<EnumerationObject> l = getImageEnumerations(name);
        if (l.size() == 0)
            return null;
        EnumerationObject o;
        Iterator i = l.iterator();
        String v;
        while (i.hasNext()) {
            o = (EnumerationObject) i.next();
            v = o.getValue();
            v = v.trim();
            if (v.equals(value))
                return o;
        }
        return null;
    }

    /**
     * Returns the enumeration object corresponding to the passed enumeration 
     * name and value for the channel metadata.
     * 
     * @param name   The type of enumeration.
     * @param value   The value to select.
     * @return See above.
     */
    Object getChannelEnumerationSelected(String name, String value) {
        if (value == null)
            return null;
        List<EnumerationObject> l = getChannelEnumerations(name);
        if (l.size() == 0)
            return null;
        EnumerationObject o;
        Iterator i = l.iterator();
        value = value.trim();
        String v;
        while (i.hasNext()) {
            o = (EnumerationObject) i.next();
            v = o.getValue();
            v = v.trim();
            if (v.equals(value))
                return o;
        }
        return null;
    }

    /**
     * Sets the enumerations related to channel metadata.
     * 
     * @param enumerations The value to set.
     */
    void setChannelEnumerations(Map enumerations) {
        channelEnumerations = new HashMap<String, List<EnumerationObject>>();
        Set set = enumerations.entrySet();
        Entry entry;
        Iterator i = set.iterator();
        String key;
        List<EnumerationObject> values;
        while (i.hasNext()) {
            entry = (Entry) i.next();
            key = (String) entry.getKey();
            values = (List<EnumerationObject>) entry.getValue();
            sortEnumerations(values);
            channelEnumerations.put(key, values);
        }
    }

    /**
     * Sets the enumerations related to image metadata.
     * 
     * @param enumerations The value to set.
     */
    void setImageEnumerations(Map enumerations) {
        imageEnumerations = new HashMap<String, List<EnumerationObject>>();
        Set set = enumerations.entrySet();
        Entry entry;
        Iterator i = set.iterator();
        String key;
        List<EnumerationObject> values;
        while (i.hasNext()) {
            entry = (Entry) i.next();
            key = (String) entry.getKey();
            values = (List<EnumerationObject>) entry.getValue();
            sortEnumerations(values);
            imageEnumerations.put(key, values);
        }
    }

    /**
     * Returns the description of the passed annotation.
     * 
     * @param annotation The annotation to handle.
     * @return See above.
     */
    String getAnnotationDescription(AnnotationData annotation) {
        if (annotation == null)
            return "";
        if (annotation instanceof TagAnnotationData)
            return ((TagAnnotationData) annotation).getTagDescription();
        else if (annotation instanceof TermAnnotationData)
            return ((TermAnnotationData) annotation).getTermDescription();
        else if (annotation instanceof XMLAnnotationData)
            return ((XMLAnnotationData) annotation).getDescription();
        return "";
    }

    /**
     * Returns <code>true</code> if the display is for a single 
     * object, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isSingleMode() {
        return parent.isSingleMode();
    }

    /** 
     * Returns the object path i.e. if a dataset is selected,
     * the name of the project_name of the dataset.
     * 
     * @return See above.
     */
    String getObjectPath() {
        return parent.getObjectPath();
    }

    /**
     * Starts asynchronous load of the plane info.
     * 
     * @param channel    The selected channel.
     * @param z       The selected z-section.
     */
    void firePlaneInfoLoading(int channel, int z) {
        Object ref = getRefObject();
        if (ref instanceof WellSampleData) {
            WellSampleData wsd = (WellSampleData) ref;
            ref = wsd.getImage();
        }
        if (!(ref instanceof ImageData))
            return;
        ImageData img = (ImageData) ref;
        PlaneInfoLoader loader = new PlaneInfoLoader(component, parent.getSecurityContext(),
                img.getDefaultPixels().getId(), channel, z);
        loader.load();
    }

    /**
     * Brings up the dialog to create a movie.
     * 
     * @param scaleBar       The value of the scale bar. 
     *                   If not greater than <code>0</code>, the value is not 
     *                   taken into account.
     * @param overlayColor The color of the scale bar and text. 
     */
    void makeMovie(int scaleBar, Color overlayColor) {
        parent.makeMovie(scaleBar, overlayColor);
    }

    /**
     * Analyzes the data. 
     * 
     * @param index The index identifying the analysis to perform.
     */
    void analyse(int index) {
        parent.analyse(index);
    }

    /**
     * Returns <code>true</code> if the renderer has been loaded,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isRendererLoaded() {
        return renderer != null;
    }

    /** 
     * Starts an asynchronous call to load the rendering control. 
     * Returns <code>false</code> if already loaded, <code>true</code>
     * otherwise.
     * 
     * @param pixelsID The id of the pixels set.
     * @param index One of the constants defined by the RenderingControlLoader
     * class
     * @return 
     */
    boolean fireRenderingControlLoading(long pixelsID, int index) {
        if (isRendererLoaded() && index == RenderingControlLoader.LOAD)
            return false;
        RenderingControlLoader loader = new RenderingControlLoader(component, parent.getSecurityContext(), pixelsID,
                index);
        loader.load();
        return true;
    }

    /**
     * Sets the rendering control.
     * 
     * @param rndControl The value to set.
     */
    void setRenderingControl(RenderingControl rndControl) {
        if (renderer != null) {
            renderer.onSettingsApplied(rndControl);
        } else {
            renderer = RendererFactory.createRenderer(getSecurityContext(), rndControl, getImage(), getRndIndex(),
                    getXMLAnnotations());
        }
    }

    /**
     * Returns the renderer.
     * 
     * @return See above.
     */
    Renderer getRenderer() {
        return renderer;
    }

    /**
     * Returns the rendering constants. 
     * 
     * @return See above.
     */
    int getRndIndex() {
        return parent.getRndIndex();
    }

    /**
     * Returns <code>true </code> if the object e.g. image has been published,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean hasBeenPublished() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return false;
        return getPublishedAnnotation() != null;
    }

    /**
     * Loads the original metadata file for the image currently selected.
     * 
     * @param uiView The view to notify.
     */
    void loadOriginalMetadata(Object uiView) {
        ImageData img = getImage();
        OriginalMetadataLoader loader = new OriginalMetadataLoader(component, parent.getSecurityContext(),
                img.getId(), uiView);
        loader.load();
    }

    /**
     * Loads the specified files.
     * 
     * @param files  The file to load.
     */
    void loadFiles(Map<FileAnnotationData, Object> files) {
        if (!MetadataViewerAgent.isBinaryAvailable())
            return;
        FileLoader loader = new FileLoader(component, parent.getSecurityContext(), files);
        loader.load();
    }

    /** 
     * Notifies that the rendering control has been loaded. 
     * 
     * @param reload Pass <code>true</code> if the rendering control has been
     *              reloaded following an exception, <code>false</code> if 
     *              it is an initial load.
     */
    void onRndLoaded(boolean reload) {
        if (renderer == null)
            return;
        parent.onRndLoaded(reload);
    }

    /**
     * Sets the instrument used to capture the image.
     * 
     * @param data The value to set.
     */
    void setInstrumentData(InstrumentData data) {
        instrumentData = data;
    }

    /**
     * Returns the instrument data.
     * 
     * @return See above.
     */
    InstrumentData getInstrumentData() {
        return instrumentData;
    }

    /** 
      * Sorts the passed collection of data objects by ID.
      * 
      * @param values  Collection of <code>DataObject</code>s to sort.
      */
    void sortDataObjectByID(List values) {
        if (values == null || values.size() == 0)
            return;
        Comparator c = new Comparator() {
            public int compare(Object o1, Object o2) {
                long n1 = ((DataObject) o1).getId(), n2 = ((DataObject) o2).getId();
                int v = 0;
                if (n1 < n2)
                    v = -1;
                else if (n1 > n2)
                    v = 1;
                return -v;
            }
        };
        Collections.sort(values, c);
    }

    /** Updates the permissions. */
    void upgradePermissions() {
    }

    /**
     * Invokes when the color of the channel has been modified using the 
     * renderer.
     * 
     * @param index The index of the channel.
     * @return See above.
     */
    Color getChannelColor(int index) {
        if (renderer == null)
            return null;
        return renderer.getChannelColor(index);
    }

    /**
     * Returns <code>true</code> if it is an image with a lot of channels.
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isNumerousChannel() {
        if (!(refObject instanceof ImageData || refObject instanceof WellSampleData))
            return false;
        if (renderer != null)
            return renderer.getPixelsDimensionsC() >= Renderer.MAX_CHANNELS;
        ImageData img = null;
        if (refObject instanceof WellSampleData)
            img = ((WellSampleData) refObject).getImage();
        if (refObject instanceof ImageData)
            img = (ImageData) refObject;
        PixelsData pixels = null;
        try {
            pixels = img.getDefaultPixels();
        } catch (Exception e) {
            //ignore
        }
        if (pixels == null)
            return false;
        return pixels.getSizeC() >= Renderer.MAX_CHANNELS;
    }

    /**
     * Returns <code>true</code> if the preview is available, 
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isPreviewAvailable() {
        if (!(refObject instanceof ImageData || refObject instanceof WellSampleData))
            return false;
        if (!MetadataViewerAgent.isBinaryAvailable())
            return false;

        ImageData img = null;
        if (refObject instanceof WellSampleData)
            img = ((WellSampleData) refObject).getImage();
        if (refObject instanceof ImageData)
            img = (ImageData) refObject;
        if (img.getId() < 0)
            return false;
        PixelsData pixels = null;
        try {
            pixels = img.getDefaultPixels();
        } catch (Exception e) {
            //ignore
        }
        if (pixels == null)
            return false;
        int size = 1500;
        if (pixels.getSizeX() <= size && pixels.getSizeY() <= size)
            return true;
        return false;
    }

    /**
     * Creates a figure.
     * 
     * @param value The value containing the parameters for the figure.
     */
    void createFigure(Object value) {
        //reset the rendering settings.
        parent.createFigure(value);
    }

    /**
     * Runs the passed script.
     * 
     * @param script The script to run.
     * @param index  Indicated to run, download or view.
     */
    void manageScript(ScriptObject script, int index) {
        parent.manageScript(script, index);
    }

    /**
     * Returns the pixels data objects.
     * 
     * @return See above.
     */
    PixelsData getPixels() {
        ImageData img = getImage();
        if (img == null)
            return null;
        return img.getDefaultPixels();
    }

    /**
     * Returns the image or <code>null</code> if the primary select
     * node is an image or a well.
     * 
     * @return See above.
     */
    ImageData getImage() {
        Object object = getPrimarySelect();
        ImageData img = null;
        if (object instanceof WellSampleData)
            img = ((WellSampleData) object).getImage();
        if (object instanceof ImageData)
            img = (ImageData) object;
        return img;
    }

    /** 
     * Loads the ROI for the selected image. 
     * 
     * @param index The index of the figure to create.
     */
    void fireROILoading(int index) {
        ImageData img = getImage();
        if (img == null)
            return;
        long userID = MetadataViewerAgent.getUserDetails().getId();
        ROILoader loader = new ROILoader(component, parent.getSecurityContext(), img.getId(), userID, index);
        loader.load();
    }

    /** Discards the renderer. */
    void discardRenderer() {
        if (renderer != null)
            renderer.discard();
        renderer = null;
    }

    /** Refreshes the view. */
    void refresh() {
        parent.refresh();
    }

    /**
     * Sets the collection of scripts.
     * 
     * @param scripts The value to set.
     */
    void setScripts(List scripts) {
        if (scripts == null) {
            this.scripts = null;
            return;
        }
        //sort the scripts.
        Map<Long, ScriptObject> map = new LinkedHashMap<Long, ScriptObject>();
        List l = sorter.sort(scripts);
        Iterator i = l.iterator();
        ScriptObject s;
        while (i.hasNext()) {
            s = (ScriptObject) i.next();
            map.put(s.getScriptID(), s);
        }
        this.scripts = map;
    }

    /**
     * Returns the collection of scripts.
     * 
     * @return See above.
     */
    Collection<ScriptObject> getScripts() {
        if (scripts == null)
            return null;
        return scripts.values();
    }

    /** 
     * Loads the specified script.
     * 
     * @param scriptID The identifier of the script to load.
     */
    void loadScript(long scriptID) {
        ScriptLoader loader = new ScriptLoader(component, parent.getSecurityContext(), scriptID);
        loader.load();
        loaders.add(loader);
    }

    /**
     * Sets the specified script.
     * 
     * @param script The loaded script.
     */
    void setScript(ScriptObject script) {
        if (scripts == null || scripts.size() == 0)
            return;
        ScriptObject sc = scripts.get(script.getScriptID());
        if (sc != null)
            sc.setJobParams(script.getParameters());
    }

    /**
     * Returns the script corresponding to the passed identifier.
     * 
     * @param scriptID The identifier of the script.
     * @return See above.
     */
    ScriptObject getScript(long scriptID) {
        return scripts.get(scriptID);
    }

    /** Loads the scripts. */
    void loadScripts() {
        ScriptsLoader loader = new ScriptsLoader(component, parent.getSecurityContext(), false);
        loader.load();
        loaders.add(loader);
    }

    /**
     * Returns the id of the possible owner. This should only be used to 
     * handle <code>FileData</code> objects.
     * 
     * @return See above.
     */
    long getUserID() {
        return parent.getUserID();
    }

    /**
     * Returns the user currently logged in.
     * 
     * @return See above.
     */
    ExperimenterData getCurrentUser() {
        return MetadataViewerAgent.getUserDetails();
    }

    /**
     * Returns the experimenter corresponding to the specified identifier.
     * or <code>null</code>.
     * 
     * @param expID The identifier of the experimenter.
     * @return see above.
     */
    ExperimenterData getExperimenter(long expID) {
        List l = (List) MetadataViewerAgent.getRegistry().lookup(LookupNames.USERS_DETAILS);
        if (l == null)
            return null;
        Iterator i = l.iterator();
        ExperimenterData exp;
        while (i.hasNext()) {
            exp = (ExperimenterData) i.next();
            if (exp.getId() == expID)
                return exp;
        }
        return null;
    }

    /** 
     * Returns the name of the owner or <code>null</code> if the current owner
     * is the user currently logged in.
     * @return
     */
    String getOwnerName() {
        Object o = getRefObject();
        if (o == null)
            return null;
        if (o instanceof ExperimenterData || o instanceof GroupData)
            return null;
        if (o instanceof DataObject) {
            ExperimenterData user = getCurrentUser();
            DataObject data = (DataObject) o;
            long id = user.getId();
            if (data.getId() < 0)
                return null;
            if (!((DataObject) o).isLoaded())
                return null;
            try {
                ExperimenterData owner = data.getOwner();
                if (owner.getId() == id)
                    return EditorUtil.formatExperimenter(user);
                if (owner.isLoaded())
                    return EditorUtil.formatExperimenter(owner);
                owner = getExperimenter(owner.getId());
                if (owner != null)
                    return EditorUtil.formatExperimenter(owner);
            } catch (Exception e) {
            }
        }
        return null;
    }

    /**
     * Resets the password of the user currently edited.
     * 
     * @param newPass The value to set.
     */
    void resetPassword(String newPass) {
        if (refObject instanceof ExperimenterData && MetadataViewerAgent.isAdministrator()) {
            parent.resetPassword(newPass);
        }
    }

    /** Uploads the user's photo. 
     * 
     * @param photo  The photo to upload.
     * @param format The format of the photo.
     */
    void uploadPicture(File photo, String format) {
        if (refObject instanceof ExperimenterData) {
            //ExperimenterData exp = (ExperimenterData) refObject;
            ExperimenterData exp = MetadataViewerAgent.getUserDetails();
            UserPhotoUploader loader = new UserPhotoUploader(component, parent.getSecurityContext(), exp, photo,
                    format);
            loader.load();
        }
    }

    /** Deletes the user's photos.*/
    void deletePicture() {
        if (refObject instanceof ExperimenterData) {
            try {
                ExperimenterData exp = MetadataViewerAgent.getUserDetails();
                if (usersPhoto != null)
                    usersPhoto.remove(exp.getId());
                OmeroMetadataService svc = MetadataViewerAgent.getRegistry().getMetadataService();
                Collection photos = svc.loadAnnotations(parent.getSecurityContext(), FileAnnotationData.class,
                        FileAnnotationData.EXPERIMENTER_PHOTO_NS, exp.getId());
                if (photos == null || photos.size() == 0)
                    return;
                List<DeletableObject> l = new ArrayList<DeletableObject>();
                Iterator<AnnotationData> j = photos.iterator();
                while (j.hasNext())
                    l.add(new DeletableObject(j.next()));
                IconManager icons = IconManager.getInstance();
                DeleteActivityParam p = new DeleteActivityParam(icons.getIcon(IconManager.APPLY_22), l);
                p.setUIRegister(false);
                p.setFailureIcon(icons.getIcon(IconManager.DELETE_22));
                UserNotifier un = MetadataViewerAgent.getRegistry().getUserNotifier();
                un.notifyActivity(getSecurityContext(), p);
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
    }

    /** Notifies the parent to upload the script. */
    void uploadScript() {
        parent.uploadScript();
    }

    /**
     * Returns the collection of scripts with a UI, mainly the figure scripts.
     * 
     * @return See above.
     */
    private List<ScriptObject> getScriptsWithUI() {
        /*
        if (scriptsWithUI != null) return scriptsWithUI;
        try {
           OmeroImageService svc = 
         MetadataViewerAgent.getRegistry().getImageService();
           scriptsWithUI = svc.loadAvailableScriptsWithUI();
           return scriptsWithUI;
        } catch (Exception e) {
          LogMessage msg = new LogMessage();
          msg.print("Scripts with UI");
          msg.print(e);
          MetadataViewerAgent.getRegistry().getLogger().error(this, msg);
        }
        */
        return new ArrayList<ScriptObject>();
    }

    /**
     * Returns the script corresponding to the specified name.
     * 
     * @return See above.
     */
    ScriptObject getScriptFromName(String name) {
        List<ScriptObject> scripts = getScriptsWithUI();
        Iterator<ScriptObject> i = scripts.iterator();
        ScriptObject script;
        while (i.hasNext()) {
            script = i.next();
            if (name.contains(script.getName()))
                return script;
        }
        return null;
    }

    /**
     * Loads the attachments.
     * 
     * @param analysis The object hosting the results.
     */
    void loadAnalysisResults(AnalysisResultsItem analysis) {
        if (resultsLoader == null)
            resultsLoader = new HashMap<AnalysisResultsItem, EditorLoader>();
        AnalysisResultsFileLoader loader = new AnalysisResultsFileLoader(component, parent.getSecurityContext(),
                analysis);
        resultsLoader.put(analysis, loader);
        loader.load();
    }

    /**
     * Cancels on-going loading of results.
     * 
     * @param item The object hosting the results.
     */
    void cancelAnalysisResultsLoading(AnalysisResultsItem item) {
        if (resultsLoader == null)
            return;
        EditorLoader loader = resultsLoader.get(item);
        if (loader != null)
            loader.cancel();
    }

    /**
     * Cancels on-going loading of results.
     * 
     * @param item The object hosting the results.
     */
    void removeAnalysisResultsLoading(AnalysisResultsItem item) {
        if (resultsLoader == null)
            return;
        resultsLoader.remove(item);
    }

    /**
     * Returns <code>true</code> if the passed annotation has to be 
     * deleted, <code>false</code> otherwise.
     * 
     * @param annotation The annotation to handle.
     * @return See above.
     */
    boolean isAnnotationToDelete(AnnotationData annotation) {
        if (toDelete == null || toDelete.size() == 0)
            return false;
        Iterator<AnnotationData> i = toDelete.iterator();
        AnnotationData data;
        while (i.hasNext()) {
            data = i.next();
            if (data.getId() == annotation.getId())
                return true;
        }
        return false;
    }

    /**
     * Returns the collection of the attachments linked to the 
     * <code>DataObject</code>.
     * 
     * @return See above.
     */
    List<FileAnnotationData> getTabularData() {
        StructuredDataResults data = parent.getStructuredData();
        List<FileAnnotationData> l = new ArrayList<FileAnnotationData>();
        if (data == null)
            return l;
        Collection<FileAnnotationData> attachments = data.getAttachments();
        if (attachments == null)
            return l;
        Iterator<FileAnnotationData> i = attachments.iterator();
        FileAnnotationData f;
        String ns;
        while (i.hasNext()) {
            f = i.next();
            ns = f.getNameSpace();
            if (FileAnnotationData.BULK_ANNOTATIONS_NS.equals(ns)) {
                l.add(f);
            }
        }
        return l;
    }

    /**
     * Returns the object to save as.
     * 
     * @param ho The data object.
     * @return See above.
     */
    private DataObject saveAsObject(Object ho) {
        if (ho instanceof ImageData || ho instanceof DatasetData) {
            return (DataObject) ho;
        }
        if (ho instanceof WellSampleData) {
            return ((WellSampleData) ho).getImage();
        }
        return null;
    }

    /** 
     * Saves locally the images as <code>JPEG</code>, <code>PNG</code>
     * or <code>TIFF</code>.
     * 
     * @param folder The folder where to save the images.
     * @param format The format to use.
     */
    void saveAs(File folder, int format) {
        Collection l = parent.getRelatedNodes();
        List<DataObject> objects = new ArrayList<DataObject>();
        Object o;
        DataObject data;
        if (l != null) {
            Iterator i = l.iterator();
            while (i.hasNext()) {
                data = saveAsObject(i.next());
                if (data != null)
                    objects.add(data);
            }
        }
        data = saveAsObject(getRefObject());
        if (data != null)
            objects.add(data);
        if (objects.size() > 0) {
            IconManager icons = IconManager.getInstance();
            SaveAsParam p = new SaveAsParam(folder, objects);
            p.setIndex(format);
            p.setIcon(icons.getIcon(IconManager.SAVE_AS_22));
            UserNotifier un = MetadataViewerAgent.getRegistry().getUserNotifier();
            un.notifyActivity(getSecurityContext(), p);
        }
    }

    /**
     * Returns the collection of selected objects.
     * 
     * @return See above.
     */
    List<DataObject> getSelectedObjects() {
        List<DataObject> objects = new ArrayList<DataObject>();
        Collection l = parent.getRelatedNodes();
        if (CollectionUtils.isEmpty(l)) {
            if (getRefObject() instanceof DataObject)
                objects.add((DataObject) getRefObject());
            return objects;
        }
        Iterator i = l.iterator();
        Object o;
        while (i.hasNext()) {
            o = i.next();
            if (o instanceof DataObject)
                objects.add((DataObject) o);
        }

        return objects;
    }

    /**
     * Returns <code>true</code> if the image is a large image,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isLargeImage() {
        return largeImage;
    }

    /**
     * Indicates if the image is a big image or not.
     * 
     * @param value The value to set.
     */
    void setLargeImage(Boolean value) {
        if (value == null)
            largeImage = false;
        largeImage = value.booleanValue();
    }

    /**
     * Returns the parent UI.
     * 
     * @return See above.
     */
    JFrame getRefFrame() {
        return parent.getParentUI();
    }

    /**
     * Sets the photo associated to the current user.
     * 
     * @param photo The photo to set.
     * @param expId The identifier of the experimenter.
     */
    void setUserPhoto(BufferedImage photo, long expId) {
        if (usersPhoto == null)
            usersPhoto = new HashMap<Long, BufferedImage>();
        usersPhoto.put(expId, photo);
    }

    /**
     * Returns the photo associated to the current user.
     * 
     * @param expId The identifier of the experimenter.
     * @return See above.
     */
    BufferedImage getUserPhoto(long expId) {
        if (usersPhoto == null)
            return null;
        return usersPhoto.get(expId);
    }

    /**
     * Returns the security context.
     * 
     * @return See above.
     */
    SecurityContext getSecurityContext() {
        return parent.getSecurityContext();
    }

    /**
     * Returns <code>true</code> if the name of the group already exists,
     * <code>false</code> otherwise.
     * 
     * @param data The group to handle.
     * @param name The name to check.
     * @return
     */
    boolean doesGroupExist(GroupData data, String name) {
        if (data == null)
            return false;
        AdminService svc = MetadataViewerAgent.getRegistry().getAdminService();
        try {
            GroupData g = svc.lookupGroup(getSecurityContext(), name);
            if (g != null && data.getId() != g.getId()) {
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }

    /**
     * Returns the annotations associated to the selected objects.
     * 
     * @return See above.
     */
    Map<DataObject, StructuredDataResults> getAllStructuredData() {
        return parent.getAllStructuredData();
    }

    /**
     * Returns <code>true</code> if the specified namespace is excluded.
     * <code>false</code> otherwise.
     * 
     * @param ns The namespace to handle.
     * @return See above.
     */
    boolean isNameSpaceExcluded(String ns) {
        if (ns == null)
            return false;
        return EXCLUDED_FILE_NS.contains(ns);
    }

    /**
     * Update the channels.
     * 
     * @param channels The value to set.
     */
    void updateChannels(List<ChannelData> channels) {
        List l = sorter.sort(channels);
        emissionsWavelengths = new LinkedHashMap();
        Iterator i = l.iterator();
        Object channel;
        while (i.hasNext()) {
            channel = i.next();
            emissionsWavelengths.put(channel, emissionsWavelengths.get(channel));
        }
    }

    /**
     * Returns the file set associated to the image if any.
     * 
     * @return See above.
     */
    Set<FilesetData> getFileset() {
        return set;
    }

    /**
     * Sets the file set associated to the image if any.
     * 
     * @param set The value to set.
     */
    void setFileset(Set<FilesetData> set) {
        this.set = set;
    }

    /** Loads the file set if the specified object is an image.*/
    void fireFilesetLoading() {
        ImageData img = getImage();
        if (img == null)
            return;
        FilesetLoader loader = new FilesetLoader(component, getSecurityContext(), img.getId());
        loader.load();
    }

    /*** Returns the display mode. One of the constants defined by 
     * {@link LookupNames}.
     * 
     * @return See above.
     */
    int getDisplayMode() {
        Integer value = (Integer) MetadataViewerAgent.getRegistry().lookup(LookupNames.DATA_DISPLAY);
        if (value == null)
            return LookupNames.EXPERIMENTER_DISPLAY;
        switch (value.intValue()) {
        case LookupNames.EXPERIMENTER_DISPLAY:
        case LookupNames.GROUP_DISPLAY:
            return value.intValue();
        }
        return LookupNames.EXPERIMENTER_DISPLAY;
    }

    /**
     * Returns <code>true</code> if the annotations are loaded,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isAnnotationLoaded() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return false;
        return data.isLoaded();
    }

    /**
     * Returns <code>true</code> if the image has an original metadata file
     * linked to it, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean hasOriginalMetadata() {
        //FileAnnotationData fa = getOriginalMetadata();
        //return fa != null;
        return true;
    }

    /**
     * Returns the groups the user is member of.
     * 
     * @return See above.
     */
    Collection<GroupData> getAvailableGroups() {
        return MetadataViewerAgent.getAvailableUserGroups();
    }

    /**
     * Returns <code>true</code> if the user is a system user e.g. root
     * <code>false</code> otherwise.
     *
     * @param id The identifier of the user.
     * @return See above.
     */
    boolean isSystemUser(long id) {
        return MetadataViewerAgent.getRegistry().getAdminService().isSystemUser(id);
    }

    /**
     * Returns <code>true</code> if the group is a system group e.g. System
     * <code>false</code> otherwise.
     *
     * @param id The identifier of the group.
     * @return See above.
     */
    boolean isSystemGroup(long id) {
        return MetadataViewerAgent.getRegistry().getAdminService().isSecuritySystemGroup(id);
    }

    /**
     * Returns <code>true</code> if the group is a system group e.g. System
     * <code>false</code> otherwise.
     *
     * @param id The identifier of the group.
     * @param key The type of group to check.
     * @return See above.
     */
    boolean isSystemGroup(long id, String key) {
        return MetadataViewerAgent.getRegistry().getAdminService().isSecuritySystemGroup(id, key);
    }

    /**
     * Returns <code>true</code> if the file is an inplace import <code>false</code> otherwise.
     * @return See above.
     */
    boolean isInplaceImport() {
        StructuredDataResults data = parent.getStructuredData();
        if (data == null)
            return false;
        return CollectionUtils.isNotEmpty(data.getTransferLinks());
    }
}