/* SimpleEntryGroup.java * * created: Wed Nov 11 1998 * * This file is part of Artemis * * Copyright(C) 1998,1999,2000,2001,2002 Genome Research Limited * * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $ **/ package uk.ac.sanger.artemis; import uk.ac.sanger.artemis.sequence.*; import uk.ac.sanger.artemis.components.MessageDialog; import uk.ac.sanger.artemis.io.GFFDocumentEntry; import uk.ac.sanger.artemis.io.IndexedGFFDocumentEntry; import uk.ac.sanger.artemis.io.Range; import uk.ac.sanger.artemis.io.StreamSequence; import uk.ac.sanger.artemis.io.SimpleDocumentEntry; import uk.ac.sanger.artemis.io.DatabaseDocumentEntry; import uk.ac.sanger.artemis.io.DocumentEntry; import uk.ac.sanger.artemis.util.DatabaseDocument; import uk.ac.sanger.artemis.util.ReadOnlyException; import uk.ac.sanger.artemis.util.OutOfRangeException; import java.util.Vector; import java.util.NoSuchElementException; /** * This class implements a vector of Entry objects, with additional methods * for querying and changing the feature tables of all the entries at * once. Objects of this class act a bit like single Entry objects. * * @author Kim Rutherford * @version $Id: SimpleEntryGroup.java,v 1.8 2008-06-11 15:15:23 tjc Exp $ **/ public class SimpleEntryGroup extends EntryVector implements EntryGroup { /** vector of those objects listening for entry change events. */ final private Vector entry_group_listener_list = new Vector(); /** vector of those objects listening for entry change events. */ final private Vector entry_listener_list = new Vector(); /** vector of those objects listening for feature change events. */ final private Vector feature_listener_list = new Vector(); /** vector of entries that are currently active (visible). */ final private EntryVector active_entries = new EntryVector(); /** * The default Entry for this SimpleEntryGroup. The "default" is the Entry * where new features are created. **/ private Entry default_entry = null; /** Bases object that was passed to the constructor. */ private Bases bases; /** Incremented by ref(), decremented by unref(). */ private int reference_count = 0; /** The ActionController of this EntryGroup (used for undo). */ final private ActionController action_controller = new ActionController(); /** * Create a new empty SimpleEntryGroup object. **/ public SimpleEntryGroup(final Bases bases) { this.bases = bases; addFeatureChangeListener(getActionController()); addEntryChangeListener(getActionController()); getBases().addSequenceChangeListener(getActionController(), Bases.MIN_PRIORITY); addEntryGroupChangeListener(getActionController()); } public SimpleEntryGroup() { addFeatureChangeListener(getActionController()); addEntryGroupChangeListener(getActionController()); } /** * Returns true if and only if there are any unsaved changes in any of the * Entry objects in this EntryGroup. **/ public boolean hasUnsavedChanges() { final int my_size = size(); for(int entry_index = 0; entry_index < my_size; ++entry_index) { if(elementAt(entry_index).hasUnsavedChanges()) return true; } return false; } /** * Return the default Entry for this SimpleEntryGroup. The "default" is the * Entry where new features are created. **/ public Entry getDefaultEntry() { return default_entry; } /** * Set the default Entry. The "default" is the Entry where new features * are created. * @param entry The new default entry. If this Entry is not active this * method will return immediately. **/ public void setDefaultEntry(Entry entry) { if(entry != null && !isActive(entry)) return; // do nothing if(default_entry == entry) return; else default_entry = entry; // now inform the listeners that a change has occured final EntryGroupChangeEvent event = new EntryGroupChangeEvent(this, getDefaultEntry(), EntryGroupChangeEvent.NEW_DEFAULT_ENTRY); fireEvent(entry_group_listener_list, event); } /** * Return the index of a feature within this object. This method treats * all the features in all the active entries as if they were in one big * array. The first feature of the first entry will have index 1, the * first from the second entry will have index 1 +(the number of features * in the first entry), etc. * @param feature The feature to find the index of. * @return The index of the feature or -1 if the feature isn't in any of * the entries. The first index is 0 the last is the total number of * features in all the entries of this object minus one. **/ public int indexOf(Feature feature) { int feature_count_of_previous_entries = 0; final int active_entries_size = active_entries.size(); for(int entry_index = 0; entry_index < active_entries_size; ++entry_index) { final Entry this_entry = active_entries.elementAt(entry_index); final int feature_index = this_entry.indexOf(feature); if(feature_index != -1) return feature_index + feature_count_of_previous_entries; feature_count_of_previous_entries += this_entry.getFeatureCount(); } return -1; } /** * Return true if any of the active entries in the group contains the given * feature. **/ public boolean contains(Feature feature) { final int active_entries_size = active_entries.size(); for(int i = 0; i < active_entries_size; ++i) { final Entry current_entry = active_entries.elementAt(i); if(current_entry.contains(feature)) return true; } return false; } /** * Return true if the given Entry is active(visible). The Feature objects * in an Entry that is not active will be ignored by the methods that deal * will features: featureAt(), indexOf(), contains(), features(), etc. **/ public boolean isActive(Entry entry) { if(active_entries.contains(entry)) return true; else return false; } /** * Set the "active" setting of the Entry at the given index. If the index * refers to the default entry and new_active is false, the default entry * will be set to the active entry or null if there are no active entries. * @param index The index of the Entry to change. * @param active The new active setting. **/ public void setIsActive(int index, boolean new_active) { final Entry entry = elementAt(index); if(new_active) { // no change if(isActive(entry)) return; else { // this is slow but it guarantees that the Entry references in the // active_entries vector are in the same order as in the // SimpleEntryGroup final EntryVector new_active_entries = new EntryVector(); final int my_size = size(); for(int i = 0; i < my_size; ++i) { if(active_entries.contains(elementAt(i)) || index == i) new_active_entries.add(elementAt(i)); } active_entries.removeAllElements(); final int new_active_entries_size = new_active_entries.size(); for(int i = 0; i < new_active_entries_size; ++i) active_entries.add(new_active_entries.elementAt(i)); if(active_entries.size() >= 1 && getDefaultEntry() == null) { // there was no default entry before calling addElement() so // make the first non-sequence entry the default entry if(active_entries.elementAt(0) == getSequenceEntry() && active_entries.size() == 1) { // don't set the default entry to be the sequence entry unless // the user asks for it setDefaultEntry(null); } else { if(active_entries.size() == 1) setDefaultEntry(active_entries.elementAt(0)); else setDefaultEntry(active_entries.elementAt(1)); } } } } else { // no change if(!isActive(entry)) return; else { active_entries.removeElement(entry); if(entry == getDefaultEntry()) { if(active_entries.size() > 0) { if(active_entries.elementAt(0) == getSequenceEntry()) { // don't set the default entry to be the sequence entry unless // the user asks for it if(active_entries.size() > 1) setDefaultEntry(active_entries.elementAt(1)); else setDefaultEntry(null); } else setDefaultEntry(active_entries.elementAt(0)); } else setDefaultEntry(null); } } } // now inform the listeners that a change has occured final EntryGroupChangeEvent event; // change state if(new_active) // become active event = new EntryGroupChangeEvent(this, entry, EntryGroupChangeEvent.ENTRY_ACTIVE); else // become inactive event = new EntryGroupChangeEvent(this, entry, EntryGroupChangeEvent.ENTRY_INACTIVE); fireEvent(entry_group_listener_list, event); } /** * Set the "active" setting of the given Entry. The Entry is the default * entry and new_active is false, the default entry will be set to the * active entry or null if there are no active entries. * @param entry The Entry to activate or deactivate. * @param new_active The new active setting. **/ public void setIsActive(final Entry entry, final boolean new_active) { setIsActive(indexOf(entry), new_active); } /** * Return the Entry from this SimpleEntryGroup that contains the sequence * to view or return null if none of the entries contains a sequence. **/ public Entry getSequenceEntry() { if(size() == 0) return null; else return elementAt(0); } /** * Returns the base length of the sequence of the first Entry in this group * or 0 if this group is empty. **/ public int getSequenceLength() { return getBases().getLength(); } /** * Returns the Bases object of the first Entry in this group or null if * this group is empty. **/ public Bases getBases() { return bases; } /** * Reverse and complement the sequence and all features in every Entry in * this SimpleEntryGroup. **/ public void reverseComplement() throws ReadOnlyException { if(isReadOnly()) throw new ReadOnlyException(); // reverse the sequence getBases().reverseComplement(); } /** * Return true if and only if one or more of the entries or features in * this SimpleEntryGroup are read-only. **/ public boolean isReadOnly() { final int my_size = size(); for(int i = 0; i < my_size; ++i) { final Entry this_entry = elementAt(i); if(this_entry.isReadOnly()) return true; final FeatureEnumeration feature_enum = this_entry.features(); while(feature_enum.hasMoreFeatures()) { if(feature_enum.nextFeature().isReadOnly()) return true; } } return false; } /** * Increment the reference count for this EntryGroup. **/ public void ref() { ++reference_count; } /** * Decrement the reference count for this EntryGroup. When the reference * count goes to zero a EntryGroupChangeEvent is sent to all * EntryGroupChangeListeners with type EntryGroupChangeEvent.DONE_GONE. * The listeners should then stop using the EntryGroup and release any * associated resources. **/ public void unref() { --reference_count; if(reference_count == 0) { // remove all the entries which will close any edit or view windows while(size() > 0) { final Entry this_entry = elementAt(0); remove(this_entry); this_entry.getEMBLEntry().dispose(); } // now inform the listeners that the EntryGroup is no more final EntryGroupChangeEvent event = new EntryGroupChangeEvent(this, null, EntryGroupChangeEvent.DONE_GONE); fireEvent(entry_group_listener_list, event); } } /** * Return the current reference count. **/ public int refCount() { return reference_count; } /** * Return the Feature at the given index. This method treats all the * features in all the entries as if they were in one big array. See * the comment on indexOf(). * @param index The index of the required Feature. * @return The Feature at the given index. The first index is 0 the last * is the total number of features in all the entries of this object minus * one. If the index is out of range then null will be returned. **/ public Feature featureAt(int index) { if(index < 0) throw new Error("internal error - index out of range: " + index); final int active_entries_size = active_entries.size(); for(int entry_index = 0; entry_index < active_entries_size; ++entry_index) { final Entry this_entry = active_entries.elementAt(entry_index); if(index < this_entry.getFeatureCount()) return this_entry.getFeature(index); index -= this_entry.getFeatureCount(); } throw new Error("internal error - index out of range: " + index); } /** * Return a vector containing the references of the Feature objects within * the given range of indices. * @param start_index The index of the first feature to return. * @param end_index The index of the last feature to return. **/ public FeatureVector getFeaturesInIndexRange(final int start_index, final int end_index) { final FeatureVector return_vector = new FeatureVector(); for(int i = start_index; i <= end_index; ++i) return_vector.add(featureAt(i)); return return_vector; } /** * Return a vector containing the references of the Feature objects within * the given range for all the active entries in the SimpleEntryGroup. * @param range Return features that overlap this range - ie the start of * the feature is less than or equal to the end of the range and the end * of the feature is greater than or equal to the start of the range. * @return The non-source key features of this feature table the are within * the given range. The returned object is a copy - changes will not * effect the FeatureTable object itself. **/ public FeatureVector getFeaturesInRange(Range range) throws OutOfRangeException { final FeatureVector return_vector = new FeatureVector(); final int my_size = size(); for(int i = 0; i < my_size; ++i) { final Entry this_entry = elementAt(i); if(isActive(this_entry)) { final FeatureVector visible_entry_features = elementAt(i).getFeaturesInRange(range); final int visible_entry_features_size = visible_entry_features.size(); for(int feature_index = 0; feature_index < visible_entry_features_size; ++feature_index) { final Feature this_feature = visible_entry_features.elementAt(feature_index); return_vector.add(this_feature); } } } return return_vector; } /** * Return a vector containing the references of the Feature objects from * all the active entries in the SimpleEntryGroup. * @return The non-source key features in active entries of this * SimpleEntryGroup. The returned object is a copy - changes will not * effect the SimpleEntryGroup object itself. **/ public FeatureVector getAllFeatures() { final FeatureVector return_vector = new FeatureVector(); final int my_size = size(); for(int i = 0; i < my_size; ++i) { final Entry this_entry = elementAt(i); if(isActive(this_entry)) { final FeatureVector entry_features = elementAt(i).getAllFeatures(); final int entry_features_size = entry_features.size(); for(int feature_index = 0; feature_index < entry_features_size; ++feature_index) { final Feature this_feature = entry_features.elementAt(feature_index); return_vector.add(this_feature); } } } return return_vector; } /** * Return a count of the number of Feature objects from all the active * entries in the SimpleEntryGroup. * @return A count of the non-source key features in active entries of this * SimpleEntryGroup. **/ public int getAllFeaturesCount() { int return_count = 0; final int my_size = size(); for(int i = 0; i < my_size; ++i) { final Entry this_entry = elementAt(i); if(isActive(this_entry)) return_count += this_entry.getFeatureCount(); } return return_count; } /** * Add an Entry to this object and then emit the appropriate EntryChange * events. **/ public void addElement(Entry entry) { super.addElement(entry); // set the default Entry to whichever Entry gets added first if(default_entry == null) default_entry = entry; active_entries.add(entry); // now inform the listeners that an addition has occured final EntryGroupChangeEvent event = new EntryGroupChangeEvent(this, entry, EntryGroupChangeEvent.ENTRY_ADDED); fireEvent(entry_group_listener_list, event); // make the new entry the default entry if and only if there was no entry // previously or there was only one entry previously and it contained // sequence but no features if(size() == 1) setDefaultEntry(entry); else { if(size() == 2) { final Entry first_entry = elementAt(0); if(first_entry.getFeatureCount() == 0) { final Bases first_entry_bases = first_entry.getBases(); if(first_entry_bases != null && first_entry_bases.getLength() > 0) setDefaultEntry(entry); } } } entry.addEntryChangeListener(this); entry.addFeatureChangeListener(this); } /** * A convenience method that does the same as addElement(Entry). **/ public void add(final Entry entry) { if(entry.getEMBLEntry() instanceof IndexedGFFDocumentEntry) ((IndexedGFFDocumentEntry)entry.getEMBLEntry()).setEntryGroup(this); else if(entry.getEMBLEntry() instanceof GFFDocumentEntry) { ((GFFDocumentEntry)entry.getEMBLEntry()).adjustCoordinates( getSequenceEntry() ); if(!Options.isBlackBeltMode() && size() > 1 && entry.getEMBLEntry().getSequence() != null ) { new MessageDialog (null, "Warning", "Overlaying a GFF with a sequence onto an entry with a sequence.", false); } } addElement(entry); } /** * Remove an Entry from this object and then emit the appropriate * EntryGroupChange events. The first entry in the group can only be * removed if it is the only Entry because the first Entry contains the * sequence. * @return true if the removal succeeded, false if it fails(which can if * the given Entry isn't in this SimpleEntryGroup or if the user tries to * remove the first Entry). **/ public boolean removeElement(final Entry entry) { // this call will sort out the default entry setIsActive(indexOf(entry), false); entry.dispose(); final boolean remove_return = super.removeElement(entry); entry.removeEntryChangeListener(this); entry.removeFeatureChangeListener(this); active_entries.removeElement(entry); // now inform the listeners that a deletion has occured final EntryGroupChangeEvent event = new EntryGroupChangeEvent(this, entry, EntryGroupChangeEvent.ENTRY_DELETED); fireEvent(entry_group_listener_list, event); return remove_return; } /** * A convenience method that does the same as removeElement(Entry). **/ public boolean remove(final Entry entry) { return removeElement(entry); } /** * Create a new(blank) Feature in the default Entry of this * SimpleEntryGroup. See getDefaultEntry() and Entry.createFeature(). * @return The new Feature. **/ public Feature createFeature() throws ReadOnlyException { final Feature new_feature = getDefaultEntry().createFeature(); return new_feature; } /** * Create a new(empty) Entry in this SimpleEntryGroup. See * Entry.newEntry(). * @return The reference of the new Entry. **/ public Entry createEntry() { Entry new_entry = null; Entry default_entry = getDefaultEntry(); if(default_entry != null && default_entry.getEMBLEntry() != null && default_entry.getEMBLEntry() instanceof DatabaseDocumentEntry) { DatabaseDocument doc = (DatabaseDocument)((DocumentEntry)default_entry.getEMBLEntry()).getDocument(); DatabaseDocument new_doc = doc.createDatabaseDocument(); try { DatabaseDocumentEntry new_doc_entry = new DatabaseDocumentEntry(); new_doc_entry.setDocument(new_doc); new_entry = new Entry(getBases(), new_doc_entry); } catch(Exception e) { e.printStackTrace(); } } else new_entry = Entry.newEntry(getBases()); add(new_entry); return new_entry; } /** * Create a new(empty) Entry in this SimpleEntryGroup. See * Entry.newEntry(). * @param name The(file) name of the new Entry. * @return The reference of the new Entry. **/ public Entry createEntry(final String name) { final Entry new_entry = createEntry(); new_entry.setName(name); return new_entry; } /** * Returns an enumeration of the Feature objects in this * SimpleEntryGroup. The returned FeatureEnumeration object will generate * all features in this object in turn. The first item generated is the * item at index 0, then the item at index 1, and so on. **/ public FeatureEnumeration features() { return new FeatureEnumerator(); } /** * An Enumeration of Feature objects. **/ public class FeatureEnumerator implements FeatureEnumeration { /** * The EntryVector object that we are enumerating. Set to null when there * are no more Feature objects. **/ private EntryVector active_entries; /** * The index of the Entry that we will get the next Feature from. **/ private int entry_index = -1; /** Enumeration for the current entry */ private FeatureEnumeration feature_enumerator; /** * Create a new FeatureEnumeration that will enumerate the enclosing * SimpleEntryGroup object. The SimpleEntryGroup object must not be * changed while the enumeration is active. **/ public FeatureEnumerator() { active_entries = getActiveEntries(); entry_index = 0; if(active_entries.size() > 0) feature_enumerator = active_entries.elementAt(entry_index).features(); else feature_enumerator = null; } /** * See the FeatureEnumeration interface for details. **/ public boolean hasMoreFeatures() { if(feature_enumerator == null) return false; if(feature_enumerator.hasMoreFeatures()) return true; ++entry_index; if(entry_index == active_entries.size()) return false; else { feature_enumerator = active_entries.elementAt(entry_index).features(); return hasMoreFeatures(); } } /** * See the FeatureEnumeration interface for details. **/ public Feature nextFeature() throws NoSuchElementException { if(feature_enumerator == null) throw new NoSuchElementException(); return feature_enumerator.nextFeature(); } } /** * Implementation of the FeatureChangeListener interface. We listen for * changes in every feature of every entry in this group. **/ public void featureChanged(FeatureChangeEvent event) { // pass the action straight through fireEvent(feature_listener_list, event); } /** * Implementation of the EntryChangeListener interface. We listen for * changes from every entry in this group and pass the events though to all * the object listening for EntryChangeEvents for the event from this * SimpleEntryGroup. **/ public void entryChanged(EntryChangeEvent event) { // pass the action straight through fireEvent(entry_listener_list, event); } /** * Send an event to those object listening for it. * @param listeners A Vector of the objects that the event should be sent * to. * @param event The event to send **/ private void fireEvent(Vector listeners, ChangeEvent event) { final Vector targets; // copied from a book - synchronising the whole method might cause a // deadlock synchronized(this) { targets = (Vector)listeners.clone(); } //boolean seen_chado_manager = false; final int targets_size = targets.size(); for(int i = 0; i < targets_size; ++i) { ChangeListener target =(ChangeListener) targets.elementAt(i); if(event instanceof EntryGroupChangeEvent) { final EntryGroupChangeListener entry_group_change_listener = (EntryGroupChangeListener) target; final EntryGroupChangeEvent group_change_event = (EntryGroupChangeEvent) event; entry_group_change_listener.entryGroupChanged(group_change_event); } else { if(event instanceof EntryChangeEvent) { final EntryChangeListener entry_change_listener = (EntryChangeListener) target; // if(entry_change_listener instanceof ChadoTransactionManager) // { // just call this listener once // if(!seen_chado_manager) // { // entry_change_listener.entryChanged((EntryChangeEvent) event); // seen_chado_manager = true; // } // } // else entry_change_listener.entryChanged((EntryChangeEvent) event); } else { final FeatureChangeListener feature_change_listener = (FeatureChangeListener) target; feature_change_listener.featureChanged((FeatureChangeEvent) event); } } } } /** * Adds the specified event listener to receive entry group change events * from this object. * @param l the event change listener. **/ public void addEntryGroupChangeListener(EntryGroupChangeListener l) { entry_group_listener_list.addElement(l); } /** * Removes the specified event listener so that it no longer receives * entry group change events from this object. * @param l the event change listener. **/ public void removeEntryGroupChangeListener(EntryGroupChangeListener l) { entry_group_listener_list.removeElement(l); } /** * Adds the specified event listener to receive entry change events from * this object. * @param l the event change listener. **/ public void addEntryChangeListener(EntryChangeListener l) { entry_listener_list.addElement(l); } /** * Removes the specified event listener so that it no longer receives * entry change events from this object. * @param l the event change listener. **/ public void removeEntryChangeListener(EntryChangeListener l) { entry_listener_list.removeElement(l); } /** * Adds the specified event listener to receive feature change events from * this object. * @param l the event change listener. **/ public void addFeatureChangeListener(FeatureChangeListener l) { feature_listener_list.addElement(l); } /** * Removes the specified event listener so that it no longer receives * feature change events from this object. * @param l the event change listener. **/ public void removeFeatureChangeListener(FeatureChangeListener l) { feature_listener_list.removeElement(l); } /** * Return the reference of an EntryVector containing the active entries of * this EntryGroup. **/ public EntryVector getActiveEntries() { return(EntryVector) active_entries.clone(); } /** * This method translates the start and end of every each Range in every * Location into another coordinate system. The Ranges will be truncated * if necessary. * @param constraint This contains the start and end base of the new * coordinate system. The position given by constraint.getStart() will * be at postion/base 1 in the new coordinate system. * @return a copy of the EntryGroup which has been translated into the new * coordinate system. **/ public EntryGroup truncate(final Range constraint) { final Bases new_bases = getBases().truncate(constraint); final EntryGroup new_entry_group = new SimpleEntryGroup(new_bases); final int my_size = size(); for(int i = 0; i < my_size; ++i) { final Entry this_entry = elementAt(i); final Entry new_entry = this_entry.truncate(new_bases, constraint); new_entry_group.add(new_entry); } if(size() > 0) { final StreamSequence sequence = (StreamSequence) new_bases.getSequence(); final SimpleDocumentEntry document_entry = (SimpleDocumentEntry) new_entry_group.elementAt(0).getEMBLEntry(); document_entry.setSequence(sequence); } return new_entry_group; } /** * Return the ActionController for this EntryGroup(for undo). **/ public ActionController getActionController() { return action_controller; } }