/******************************************************************************* * 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.track; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import edu.yu.einstein.genplay.gui.track.layer.Layer; /** * Model that manages a list of layers * @author Julien Lajugie */ public class TrackModel implements Serializable, Collection<Layer<?>> { private static final long serialVersionUID = -3615098708228915823L; // generated serial ID private static final int SAVED_FORMAT_VERSION_NUMBER = 0; // saved format version private List<ListDataListener> dataListeners; // list of listeners that is notified each time a change to the data model occurs private LinkedList<Layer<?>> layers; // list of layers displayed in the track /** * Creates an instance of {@link TrackModel} */ public TrackModel() { dataListeners = new ArrayList<ListDataListener>(); layers = new LinkedList<Layer<?>>(); } /** * Adds the specified layer at the beginning of the list because layers are * stacked. They are also painted in reverse order * @param layer a {@link Layer} * @return true */ @Override public boolean add(Layer<?> layer) { int indexAdded = layers.size(); layers.addFirst(layer); ListDataEvent event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, indexAdded); notifyListeners(event); return true; } @Override public boolean addAll(Collection<? extends Layer<?>> layers) { int indexFirstAdded = this.layers.size(); int indexLastAdded = indexFirstAdded + layers.size(); boolean listChanged = this.layers.addAll(layers); if (listChanged) { ListDataEvent event = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, indexFirstAdded, indexLastAdded); notifyListeners(event); } return listChanged; } /** * Adds the specified layer at the end of the list * @param layer a {@link Layer} */ public void addLast(Layer<?> layer) { int indexAdded = layers.size(); layers.add(layer); ListDataEvent event = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, indexAdded, indexAdded); notifyListeners(event); } /** * Adds a listener to the list that's notified each time a change to the data model occurs. * @param dataListener */ public void addListDataListener(ListDataListener dataListener) { if (!dataListeners.contains(dataListener)) { dataListeners.add(dataListener); } } @Override public void clear() { int lastIndex = layers.size() - 1; layers.clear(); ListDataEvent event = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, 0, lastIndex); notifyListeners(event); } @Override public boolean contains(Object o) { return layers.contains(o); } @Override public boolean containsAll(Collection<?> c) { return layers.containsAll(c); } /** * @return an array containing the elements managed by this {@link TrackModel} */ public Layer<?>[] getLayers() { return layers.toArray(new Layer<?>[0]); } /** * Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index. * @param o element to search for * @return the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element * @throws ClassCastException - if the type of the specified element is incompatible with this list (optional) * @throws NullPointerException - if the specified element is null and this list does not permit null elements (optional) */ public int indexOf(Object o) { return layers.indexOf(o); } @Override public boolean isEmpty() { return layers.isEmpty(); } @Override public Iterator<Layer<?>> iterator() { return layers.iterator(); } /** * Notifies all the {@link ListDataListener} that the data changed * @param event */ private void notifyListeners(ListDataEvent event) { switch (event.getType()) { case ListDataEvent.CONTENTS_CHANGED: for (ListDataListener listener: dataListeners) { listener.contentsChanged(event); } case ListDataEvent.INTERVAL_ADDED: for (ListDataListener listener: dataListeners) { listener.intervalAdded(event); } case ListDataEvent.INTERVAL_REMOVED: for (ListDataListener listener: dataListeners) { listener.intervalRemoved(event); } } } /** * Method used for unserialization * @param in * @throws IOException * @throws ClassNotFoundException */ @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.readInt(); dataListeners = (List<ListDataListener>)in.readObject(); layers = (LinkedList<Layer<?>>)in.readObject(); // We remove layer with null data since if the unserialization of a layer failed, its data is null removeNoDataLayers(); } @Override public boolean remove(Object o) { int indexRemoved = layers.indexOf(o); if (indexRemoved != -1) { layers.remove(o); ListDataEvent event = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, indexRemoved, indexRemoved); notifyListeners(event); return true; } else { return false; } } @Override public boolean removeAll(Collection<?> c) { int sizeBeforeChanges = layers.size(); boolean listChanged = layers.removeAll(c); if (listChanged) { ListDataEvent event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, sizeBeforeChanges - 1); notifyListeners(event); return true; } else { return false; } } /** * Removes a listener from the list that's notified each time a change to the data model occurs. * @param dataListener */ public void removeListDataListener(ListDataListener dataListener) { dataListeners.remove(dataListener); } /** * This method removes all the layers with null data */ private void removeNoDataLayers() { int i = 0; while (i < layers.size()) { if (layers.get(i).getData() == null) { layers.remove(i); } else { i++; } } } @Override public boolean retainAll(Collection<?> c) { int sizeBeforeChanges = layers.size(); boolean listChanged = layers.retainAll(c); if (listChanged) { ListDataEvent event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, sizeBeforeChanges - 1); notifyListeners(event); return true; } else { return false; } } @Override public int size() { return layers.size(); } @Override public Object[] toArray() { return Collections.synchronizedList(layers).toArray(); } @Override public <T> T[] toArray(T[] array) { return layers.toArray(array); } /** * Method used for serialization * @param out * @throws IOException */ private void writeObject(ObjectOutputStream out) throws IOException { out.writeInt(SAVED_FORMAT_VERSION_NUMBER); out.writeObject(dataListeners); out.writeObject(layers); } }