gt: 		if (parent == null) {
context: /*
   *------------------------------------------------------------------------------
   *  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.dataBrowser.view;
  
  
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Map.Entry;
  
  import org.openmicroscopy.shoola.agents.util.browser.TreeImageDisplay;
  import org.openmicroscopy.shoola.agents.util.browser.TreeImageTimeSet;
  
  import omero.gateway.SecurityContext;
  import omero.gateway.model.SearchResultCollection;
  
  import org.openmicroscopy.shoola.env.rnd.RndProxyDef;
  
  import omero.gateway.model.DataObject;
  import omero.gateway.model.DatasetData;
  import omero.gateway.model.ExperimenterData;
  import omero.gateway.model.FileData;
  import omero.gateway.model.GroupData;
  import omero.gateway.model.ImageData;
  import omero.gateway.model.PlateAcquisitionData;
  import omero.gateway.model.PlateData;
  import omero.gateway.model.ProjectData;
  import omero.gateway.model.TagAnnotationData;
  import omero.gateway.model.WellData;
  
  /** 
   * Factory to create {@link DataBrowser} components.
   * This class keeps track of all {@link DataBrowser} instances that have been
   * created and are not yet {@link DataBrowser#DISCARDED discarded}. A new
   * component is only created if none of the <i>tracked</i> ones is already
   * displaying the given hierarchy.  Otherwise, the existing component is
   * recycled.
   *
   * @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
   */
  public class DataBrowserFactory
  {
  	
  	/** The sole instance. */
  	private static final DataBrowserFactory  
  						singleton = new DataBrowserFactory();
  	
  	/** Discards all the tracked {@link DataBrowser}s. */
  	public static final void discardAll()
  	{
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		DataBrowserComponent comp;
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			comp = (DataBrowserComponent) entry.getValue();
  			comp.discard();
  			singleton.discardedBrowsers.add((String) entry.getKey());
  		}
  		System.gc();
  		singleton.browsers.clear();
  	}
  	
  	/**
  	 * Creates a browser to display the results.
  	 * 
  	 * @param result The value to set.
  	 * @return See above.
  	 */
  	public static final DataBrowser getSearchBrowser(
  			Map<SecurityContext, Collection<DataObject>> result)
  	{
  		return singleton.createSearchDataBrowser(result);
  	}
  	
  	/**
  	 * Creates a browser to display the results.
  	 * 
  	 * @param results The value to set.
  	 * @return See above.
  	 */
  	public static final DataBrowser getSearchBrowser(SearchResultCollection results)
  	{
  	    return singleton.createSearchDataBrowser(results);
  	}
  
  	/**
  	 * Returns the browser used for searching data.
  	 * 
  	 * @return See above.
  	 */
  	public static final DataBrowser getSearchBrowser()
  	{
  		return singleton.searchBrowser;
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of images.
  	 * 
  	 * @param ctx The security context.
  	 * @param ancestors		Map containing the ancestors of the node.
  	 * @param parent		The parent's node.
  	 * @param wells			The collection to set.
  	 * @param withThumbnails Pass <code>true</code> to load the thumbnails,
       * 						 <code>false</code> otherwise.
  	 * @return See above.
  	 */
  	public static final DataBrowser getWellsDataBrowser(
  			SecurityContext ctx, Map<Class, Object> ancestors, Object parent,
  			Set<WellData> wells, boolean withThumbnails)
  	{
  		return singleton.createWellsDataBrowser(ctx, ancestors, parent, wells,
  				withThumbnails);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of images.
  	 * 
  	 * @param ctx The security context.
  	 * @param grandParent	The grandparent of the node.
  	 * @param parent		The parent's node.
  	 * @param images		The collection to set.
  	 * @param node  		The node to handle.
  	 * @return See above.
  	 */
  	public static final DataBrowser getDataBrowser(
  			SecurityContext ctx, Object grandParent, 
  					Object parent, Collection<ImageData> images, 
  					TreeImageDisplay node)
  	{
  		return singleton.createImagesDataBrowser(ctx, grandParent, parent,
  				images, node);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of images.
  	 * 
  	 * @param ctx The security context.
  	 * @param parent	 The parent's node.
  	 * @param nodes		 The collection to set.
  	 * @param withImages Pass <code>true</code> to indicate that the images
  	 * 					 are loaded, <code>false</code> otherwise.
  	 * @return See above.
  	 */
  	public static final DataBrowser getTagsBrowser(
  			SecurityContext ctx, TagAnnotationData parent, 
  			Collection<DataObject> nodes, boolean withImages)
  	{
  		return singleton.createTagsDataBrowser(ctx, parent, nodes, withImages);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of 
  	 * experimenters.
  	 * 
  	 * @param ctx The security context.
  	 * @param parent		The parent's node.
  	 * @param experimenters	The collection to set.
  	 * @return See above.
  	 */
  	public static final DataBrowser getGroupsBrowser(
  			SecurityContext ctx, GroupData parent,
  			Collection<ExperimenterData> experimenters)
  	{
  		return singleton.createGroupsBrowser(ctx, parent, experimenters);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of 
  	 * files.
  	 * 
  	 * @param ctx The security context.
  	 * @param parent The parent's node.
  	 * @param files The collection to set.
  	 * @return See above.
  	 */
  	public static final DataBrowser getFSFolderBrowser(
  			SecurityContext ctx, FileData parent, Collection<DataObject> files)
  	{
  		return singleton.createFSFolderBrowser(ctx, parent, files);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of datasets.
  	 * 
  	 * @param ctx The security context.
  	 * @param parent The parent's node.
  	 * @param nodes The collection to set.
  	 * @return See above.
  	 */
  	public static final DataBrowser getDataBrowser(SecurityContext ctx, 
  			ProjectData parent, Set<DatasetData> nodes)
  	{
  		return singleton.createDatasetsDataBrowser(ctx, parent, nodes);
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed node.
  	 * 
  	 * @param parent The node.
  	 * @return See above.
  	 */
  	public static final DataBrowser getDataBrowser(Object parent)
  	{
  		if (parent == null) return null;
  		return singleton.browsers.get(createKey(parent));
  	}
  	
  	/**
  	 * Returns <code>true</code> if a {@link DataBrowser} has been discarded,
  	 * <code>false</code> otherwise.
  	 * 
  	 * @param parent The node.
  	 * @return See above.
  	 */
  	public static final boolean hasBeenDiscarded(Object parent)
  	{
  		if (parent == null) return false;
  		String key = createKey(parent);
  		Iterator<String> i = singleton.discardedBrowsers.iterator();
  		String value;
  		while (i.hasNext()) {
  			value = i.next();
  			if (value.equals(key)) return true;
  		}
  		return false;
  	}
  	
  	/** 
  	 * Refreshes the thumbnails corresponding to the passed image ids.
  	 * 
  	 * @param ids The collection of images to handle.
  	 */
  	public static final void refreshThumbnails(Collection ids)
  	{
  		if (ids != null && ids.size() > 0) {
  			if (singleton.searchBrowser != null)
  				singleton.searchBrowser.reloadThumbnails(ids);
  			Iterator<Entry<Object, DataBrowser>> 
  			v = singleton.browsers.entrySet().iterator();
  			DataBrowserComponent comp;
  			Entry<Object, DataBrowser> entry;
  			while (v.hasNext()) {
  				entry = v.next();
  				comp = (DataBrowserComponent) entry.getValue();
  				comp.reloadThumbnails(ids);
  			}
  			System.gc();
  		}
  	}
  	
  	/**
  	 * Sets the image to copy the settings from.
  	 * 
  	 * @param rndSettingsToCopy The reference image to copy the rendering settings from
           * @param rndDefToCopy 'Pending' rendering settings to copy (can be null)
  	 */
  	public static final void setRndSettingsToCopy(ImageData rndSettingsToCopy, RndProxyDef rndDefToCopy)
  	{
  		singleton.rndSettingsToCopy = rndSettingsToCopy; 
  		singleton.rndDefToCopy = rndDefToCopy; 
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		DataBrowserComponent comp;
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			comp = (DataBrowserComponent) entry.getValue();
  			comp.notifyRndSettingsToCopy();
  		}
  		if (singleton.searchBrowser != null)
  			((DataBrowserComponent) 
  					singleton.searchBrowser).notifyRndSettingsToCopy();
  	}
  	
  	/**
  	 * Sets the type of object to copy or <code>null</code> if no objects to 
  	 * copy.
  	 * 
  	 * @param dataToCopy The type of objects to copy.
  	 */
  	public static final void setDataToCopy(Class dataToCopy)
  	{
  		singleton.dataToCopy = dataToCopy;
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		DataBrowserComponent comp;
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			comp = (DataBrowserComponent) entry.getValue();
  			comp.notifyDataToCopy();
  		}
  		if (singleton.searchBrowser != null)
  			((DataBrowserComponent) singleton.searchBrowser).notifyDataToCopy();
  	}
  	
  	/**
  	 * Notifies the model that the user's group has successfully be modified
  	 * if the passed value is <code>true</code>, unsuccessfully 
  	 * if <code>false</code>.
  	 * 
  	 * @param success 	Pass <code>true</code> if successful, <code>false</code>
  	 * 					otherwise.
  	 */
  	public static final void onGroupSwitched(boolean success)
  	{
  		if (!success)  return;
  		singleton.dataToCopy = null;
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		DataBrowserComponent comp;
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			comp = (DataBrowserComponent) entry.getValue();
  			comp.discard();
  		}
  		singleton.browsers.clear();
  		singleton.discardedBrowsers.clear();
  		singleton.searchBrowser = null;
  	}
  	
  	/**
  	 * Notifies the model that the user has annotated data.
  	 * 
  	 * @param containers The objects to handle.
  	 * @param count A positive value if annotations are added, a negative value
  	 * if annotations are removed.
  	 */
  	public static void onAnnotated(List<DataObject> containers, int count)
  	{
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		DataBrowserComponent comp;
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			comp = (DataBrowserComponent) entry.getValue();
  			comp.onAnnotated(containers, count);
  		}
  	}
  	
  	/**
  	 * Sets the display mode.
  	 * 
  	 * @param displayMode The value to set.
  	 */
  	public static void setDisplayMode(int displayMode)
  	{
  		Iterator<Entry<Object, DataBrowser>> 
  		v = singleton.browsers.entrySet().iterator();
  		Entry<Object, DataBrowser> entry;
  		while (v.hasNext()) {
  			entry = v.next();
  			entry.getValue().setDisplayMode(displayMode);
  		}
  		onGroupSwitched(true);
  	}
  	
  	/**
  	 * Returns <code>true</code> if there are rendering settings to copy,
  	 * <code>false</code> otherwise.
  	 * 
  	 * @return See above.
  	 */
  	static boolean hasRndSettingsToCopy()
  	{
  		return singleton.rndSettingsToCopy != null || singleton.rndDefToCopy != null;
  	}
      
  	/**
  	 * Returns <code>true</code> if the image to copy the rendering settings
  	 * from is in the specified group, <code>false</code> otherwise.
  	 * 
  	 * @param groupID The group to handle.
  	 * @return See above.
  	 */
  	static boolean areSettingsCompatible(long groupID)
  	{
  		if (!hasRndSettingsToCopy()) return false;
  		RndProxyDef def = singleton.rndDefToCopy;
  		ImageData img = singleton.rndSettingsToCopy;
  		return singleton.rndDefToCopy != null || singleton.rndSettingsToCopy.getGroupId() == groupID;
  	}
  	
  	/**
  	 * Returns the type of objects to copy or <code>null</code> if no objects
  	 * selected.
  	 * 
  	 * @return See above.
  	 */
  	static Class hasDataToCopy() { return singleton.dataToCopy; }
  	
  	/**
  	 * Creates the key identifying the browser from the specified object.
  	 * 
  	 * @param parent The value to handle.
  	 * @return See above.
  	 */
  	private static final String createKey(Object parent)
  	{
  		String key = parent.toString();
  		if (parent instanceof DataObject) 
  			key += ((DataObject) parent).getId();
  		else if (parent instanceof TreeImageDisplay)
  			key = TreeImageTimeSet.createPath((TreeImageDisplay) parent, key);
  		return key;
  	}
  	
  	/** The collection of discarded browsers. */
  	private Set<String>					discardedBrowsers;
  	
  	/** Map used to keep track of the browsers. */
  	private Map<Object, DataBrowser> 	browsers;
  	
  	/** The {@link DataBrowser} displaying the result of a search. */
  	private DataBrowser					searchBrowser;
  	
  	/** The image to copy the rendering settings from. */
  	private ImageData rndSettingsToCopy;
  	
  	/** The copied 'pending' rendering settings */
  	private RndProxyDef rndDefToCopy;
  	
  	/** The type identifying the object to copy. */
  	private Class						dataToCopy;
  	
  	/** Creates a new instance. */
  	private DataBrowserFactory()
  	{
  		browsers = new HashMap<Object, DataBrowser>();
  		discardedBrowsers = new HashSet<String>();
  		searchBrowser = null;
  		rndSettingsToCopy = null;
  		dataToCopy = null;
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of wells.
  	 * 
  	 * @param ctx The security context.
  	 * @param ancestors		Map containing the ancestors of the node.
  	 * @param parent		The parent's node.
  	 * @param wells			The collection to set.
  	 * @param withThumbnails Pass <code>true</code> to load the thumbnails,
       * 						 <code>false</code> otherwise.
  	 * @return See above.
  	 */
  	private DataBrowser createWellsDataBrowser(SecurityContext ctx,
  			Map<Class, Object> ancestors,
  			Object parent, Set<WellData> wells, boolean withThumbnails)
  	{
  		Object p = parent;
  		Object go = null;
  		if (parent instanceof PlateAcquisitionData) {
  			p = ancestors.get(PlateData.class);
  			if (p == null) return null;
  			ancestors.remove(PlateData.class);
  		}
  		if (ancestors.size() > 0) {
  			Iterator<Entry<Class, Object>> i = ancestors.entrySet().iterator();
  			Entry<Class, Object> entry;
  			while (i.hasNext()) {
  				entry = i.next();
  				go = entry.getValue();
  				break;
  			}
  		}
  		DataBrowserModel model = new WellsModel(ctx, p, wells, withThumbnails);
  		model.setGrandParent(go);
  		DataBrowserComponent comp = new DataBrowserComponent(model);
  		model.initialize(comp);
  		comp.initialize();
  		if (parent instanceof PlateData) {
  			PlateData plate = (PlateData) parent;
  			Set<PlateAcquisitionData> set = plate.getPlateAcquisitions();
  			if (set != null && set.size() == 1) {
  				Iterator<PlateAcquisitionData> j = set.iterator();
  				while (j.hasNext()) {
  					parent = j.next();
  				}
  			}
  		}
  		String key = parent.toString();
  		if (parent instanceof DataObject) 
  			key += ((DataObject) parent).getId();
  		browsers.put(key, comp);
  		return comp;
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of images.
  	 * 
  	 * @param ctx The security context.
  	 * @param grandParent	The grandParent of the node.
  	 * @param parent		The parent's node.
  	 * @param images		The collection to set.
  	 * @param experimenter  The experimenter associated to the node.
  	 * @return See above.
  	 */
  	private DataBrowser createImagesDataBrowser(
  			SecurityContext ctx, Object grandParent, 
  					Object parent, Collection<ImageData> images, 
  					TreeImageDisplay node)
  	{
  		DataBrowserModel model = new ImagesModel(ctx, parent, images);
  		model.setGrandParent(grandParent);
  		DataBrowserComponent comp = new DataBrowserComponent(model);
  		model.initialize(comp);
  		comp.initialize();
  		String key = parent.toString();
  		if (parent instanceof DataObject) 
  			key += ((DataObject) parent).getId();
  		else key = TreeImageTimeSet.createPath(node, key);
  		browsers.put(key, comp);
  		return comp;
  	}
  	
  	/**
  	 * Creates a new {@link DataBrowser} for the passed collection of datasets.
  	 * 
  	 * @param ctx The security context.
  	 * @param parent	The parent's node.
  	 * @param datasets	The collection to set.
  	 * @return See above.
  	 */
  	private DataBrowser createDatasetsDataBrowser(SecurityContext ctx,
  			DataObject parent, Set<DatasetData> datasets)
  	{
  		DataBrowserModel model = new DatasetsModel(ctx, parent, datasets);
  		DataBrowserComponent comp = new DataBrowserComponent(model);
  		model.initialize(comp);
  		comp.initialize();
  		StringBuffer buffer = new StringBuffer();
  