/*******************************************************************************
* GenPlay, Einstein Genome Analyzer
* Copyright (C) 2009, 2014 Albert Einstein College of Medicine
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* Authors: Julien Lajugie <julien.lajugie@einstein.yu.edu>
* Nicolas Fourel <nicolas.fourel@einstein.yu.edu>
* Eric Bouhassira <eric.bouhassira@einstein.yu.edu>
*
* Website: <http://genplay.einstein.yu.edu>
******************************************************************************/
package edu.yu.einstein.genplay.gui.dataScalerForTrackDisplay;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import edu.yu.einstein.genplay.dataStructure.enums.Nucleotide;
import edu.yu.einstein.genplay.dataStructure.gene.Gene;
import edu.yu.einstein.genplay.dataStructure.list.chromosomeWideList.repeatListView.RepeatFamilyListView;
import edu.yu.einstein.genplay.dataStructure.list.listView.ListView;
import edu.yu.einstein.genplay.dataStructure.scoredChromosomeWindow.ScoredChromosomeWindow;
import edu.yu.einstein.genplay.exception.ExceptionManager;
import edu.yu.einstein.genplay.gui.mainFrame.MainFrame;
import edu.yu.einstein.genplay.gui.track.layer.AbstractSCWLayer;
import edu.yu.einstein.genplay.gui.track.layer.GeneLayer;
import edu.yu.einstein.genplay.gui.track.layer.Layer;
import edu.yu.einstein.genplay.gui.track.layer.MaskLayer;
import edu.yu.einstein.genplay.gui.track.layer.NucleotideLayer;
import edu.yu.einstein.genplay.gui.track.layer.RepeatLayer;
/**
* Associates data scalers objects to layers and their data.
* Makes sure that if many layers display the same data they share a unique data scaler
* that scale the data.
* Allows the data scalers to notify their associated layers that their data has been scaled
* and are ready to be printed.
* @author Julien Lajugie
*/
public class DataScalerManager {
/**
* Threads that cleans the map of data scalers associated to the data to scale
* and the map of layers associated to the data scalers that scales their data.
* Remove the elements that don't need to be scaled anymore because they are not displayed
* in order to avoid memory leaks.
* @author Julien Lajugie
*/
private class DataScalerManagerCleaner extends Thread {
/** Interval between two cleanings in seconds */
private final static int CLEANING_INTERVALS = 10;
/**
* Cleans the map that associates data keys to data scaler values that scale their associated keys.
*/
private void cleanDataScalerMap() {
if (MainFrame.isInitialized()) {
List<Layer<?>> displayedLayers = MainFrame.getInstance().getTrackListPanel().getModel().getAllLayers(); // retrieve the list of all the layers displayed
Map<Object, Boolean> isDataDisplayedMap = new HashMap<Object, Boolean>(); // map with data as key and isDisplayed as value
for (Object currentData: dataScalerMap.keySet()) {
isDataDisplayedMap.put(currentData, false);
}
// set to true the is displayed value of data of displayed layer
for (Layer<?> currentLayer: displayedLayers) {
isDataDisplayedMap.put(currentLayer.getData(), true);
}
// remove data scalers that are not displayed
Iterator<Entry<Object, DataScalerForTrackDisplay<?, ?>>> dataScalerMapEntryIterator = dataScalerMap.entrySet().iterator();
while (dataScalerMapEntryIterator.hasNext()) {
Entry<Object, DataScalerForTrackDisplay<?, ?>> currentEntry = dataScalerMapEntryIterator.next();
if (!isDataDisplayedMap.get(currentEntry.getKey())) {
dataScalerMapEntryIterator.remove();
}
}
}
}
/**
* Cleans the map that associates data scaler keys to sets of layers that use their keys to scale their data
*/
private void cleanLayerListMap() {
Iterator<Entry<DataScalerForTrackDisplay<?, ?>, Set<Layer<?>>>> layerListMapIterator = layerListMap.entrySet().iterator();
while(layerListMapIterator.hasNext()) {
Entry<DataScalerForTrackDisplay<?, ?>, Set<Layer<?>>> currentLayerListMapEntry = layerListMapIterator.next();
Iterator<Layer<?>> layerSetIterator = currentLayerListMapEntry.getValue().iterator();
while (layerSetIterator.hasNext()) {
Layer<?> currentLayer = layerSetIterator.next();
DataScalerForTrackDisplay<?, ?> layerScaler = dataScalerMap.get(currentLayer.getData());
if ((layerScaler == null) || (layerScaler != currentLayerListMapEntry.getKey())) {
layerSetIterator.remove();
}
}
if (currentLayerListMapEntry.getValue().isEmpty()) {
layerListMapIterator.remove();
}
}
}
@Override
public void run() {
Thread thisThread = Thread.currentThread();
setName("Data Scaler Cleaner Thread");
while (DataScalerManager.getInstance().cleanerThread == thisThread) {
cleanDataScalerMap();
cleanLayerListMap();
try {
sleep(CLEANING_INTERVALS * 1000);
} catch (InterruptedException e) {
ExceptionManager.getInstance().caughtException(e);
}
}
}
}
/** Instance of the {@link DataScalerManager} singleton */
private static DataScalerManager instance = null;
/**
* @return the instance of the {@link DataScalerManager} singleton
*/
public synchronized static DataScalerManager getInstance() {
if (instance == null) {
// we synchronize to make sure that there is no 2 instances created
synchronized(DataScalerManager.class) {
if (instance == null) {
instance = new DataScalerManager();
}
}
}
return instance;
}
/** Thread that cleans the maps */
private final DataScalerManagerCleaner cleanerThread;
/** Map that associates data scaler keys to sets of layers values that use their keys to scale their data */
private final Map<DataScalerForTrackDisplay<?, ?>, Set<Layer<?>>> layerListMap;
/** Map that associates data keys to data scaler values that scale their associated keys */
private final Map<Object, DataScalerForTrackDisplay<?, ?>> dataScalerMap; // map with data as key and data scalers as values
/**
* Creates an instance of {@link DataScalerManager}
*/
private DataScalerManager() {
layerListMap = new ConcurrentHashMap<DataScalerForTrackDisplay<?,?>, Set<Layer<?>>>();
dataScalerMap = new ConcurrentHashMap<Object, DataScalerForTrackDisplay<?,?>>();
cleanerThread = new DataScalerManagerCleaner();
cleanerThread.start();
}
/**
* @param layer
* @return the data of the specified layer scaled for display
*/
@SuppressWarnings("unchecked")
public ListView<ScoredChromosomeWindow> getScaledData(AbstractSCWLayer<?> layer) {
return (ListView<ScoredChromosomeWindow>) retrieveDataScaler(layer).getDataScaledForTrackDisplay();
}
/**
* @param layer
* @return the data of the specified layer scaled for display
*/
@SuppressWarnings("unchecked")
public List<ListView<Gene>> getScaledData(GeneLayer layer) {
return (List<ListView<Gene>>) retrieveDataScaler(layer).getDataScaledForTrackDisplay();
}
/**
* @param layer
* @return the data of the specified layer scaled for display
*/
@SuppressWarnings("unchecked")
public ListView<ScoredChromosomeWindow> getScaledData(MaskLayer layer) {
return (ListView<ScoredChromosomeWindow>) retrieveDataScaler(layer).getDataScaledForTrackDisplay();
}
/**
* @param layer
* @return the data of the specified layer scaled for display
*/
public Nucleotide[] getScaledData(NucleotideLayer layer) {
return (Nucleotide[]) retrieveDataScaler(layer).getDataScaledForTrackDisplay();
}
/**
* @param layer
* @return the data of the specified layer scaled for display
*/
@SuppressWarnings("unchecked")
public List<RepeatFamilyListView> getScaledData(RepeatLayer layer) {
return (List<RepeatFamilyListView>) retrieveDataScaler(layer).getDataScaledForTrackDisplay();
}
/**
* Redraws the layers that displays data scaled by the specified data scaler
* @param dataScaler a {@link DataScalerForTrackDisplay}
*/
void redrawLayers(DataScalerForTrackDisplay<?, ?> dataScaler) {
Set<Layer<?>> layersToRedraw = layerListMap.get(dataScaler);
for (Layer<?> currentLayer: layersToRedraw) {
currentLayer.getTrack().repaint();
}
}
/**
* Register a layer to the map of layer sets and register and map a data scaler to the data it scales
* @param layer
*/
private void registerLayer(Layer<?> layer) {
if (!dataScalerMap.containsValue(layer.getData())) {
DataScalerForTrackDisplay<?, ?> dataScaler = DataScalerFactory.createDataScaler(layer);
dataScalerMap.put(layer.getData(), dataScaler);
Set<Layer<?>> layerList = new HashSet<Layer<?>>();
layerList.add(layer);
layerListMap.put(dataScaler, layerList);
}
}
/**
* @param layer a {@link Layer}
* @return the {@link DataScalerForTrackDisplay} object that scales the data of the
* specified layer. Creates it if it doesn't exist.
*/
private DataScalerForTrackDisplay<?, ?> retrieveDataScaler(Layer<?> layer) {
DataScalerForTrackDisplay<?, ?> dataScaler = dataScalerMap.get(layer.getData());
if (dataScaler == null) {
registerLayer(layer);
dataScaler = dataScalerMap.get(layer.getData());
}
layerListMap.get(dataScaler).add(layer); // add the layer to the list associated with the data scaler if not present
return dataScaler;
}
}