package org.phenoscape.model; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; import org.apache.log4j.Logger; import org.jdesktop.observablecollections.ObservableList; import org.jdesktop.observablecollections.ObservableListListener; import org.obo.app.controller.UndoController; import org.obo.app.model.Observer; import org.obo.app.model.PropertyChangeObject; public class UndoObserver { private final UndoController undoController; private final DataSetObserver dataSetObserver = new DataSetObserver(); // should this logic be in this class or UndoController? private boolean undoing = false; public UndoObserver(UndoController controller) { this.undoController = controller; } public void setDataSet(DataSet data) { this.dataSetObserver.startObserving(data); } private class DataSetObserver implements Observer<DataSet> { private final PropertyChangeListener publicationListener = new PropertyUndoer("Edit Publication"); private final PropertyChangeListener publicationLabelListener = new PropertyUndoer("Edit Publication Label"); private final PropertyChangeListener publicationCitationListener = new PropertyUndoer("Edit Publication Citation"); private final PropertyChangeListener publicationURIListener = new PropertyUndoer("Edit Publication URI"); private final PropertyChangeListener pubNotesListener = new PropertyUndoer("Edit Publication Notes"); private final PropertyChangeListener curatorsListener = new PropertyUndoer("Edit Curators"); private final PropertyChangeListener matrixListener = new MatrixCellUndoer(); private final TaxonObserver taxonObserver = new TaxonObserver(); private final ListObserver<Taxon> taxaObserver = new ListObserver<Taxon>(this.taxonObserver, "Taxon"); private final CharacterObserver characterObserver = new CharacterObserver(); private final ListObserver<Character> charactersObserver = new ListObserver<Character>(this.characterObserver, "Character"); @Override public void startObserving(DataSet data) { data.addPropertyChangeListener(DataSet.PUBLICATION, this.publicationListener); data.addPropertyChangeListener(DataSet.PUBLICATION_LABEL, this.publicationLabelListener); data.addPropertyChangeListener(DataSet.PUBLICATION_CITATION, this.publicationCitationListener); data.addPropertyChangeListener(DataSet.PUBLICATION_URI, this.publicationURIListener); data.addPropertyChangeListener(DataSet.PUBLICATION_NOTES, this.pubNotesListener); data.addPropertyChangeListener(DataSet.CURATORS, this.curatorsListener); data.addPropertyChangeListener(DataSet.MATRIX_CELL, this.matrixListener); this.taxaObserver.startObserving(data.getTaxa()); this.charactersObserver.startObserving(data.getCharacters()); } @Override public void startObserving(Collection<DataSet> objects) { for (DataSet data : objects) { this.startObserving(data); } } @Override public void stopObserving(DataSet data) { data.removePropertyChangeListener(DataSet.PUBLICATION, this.publicationListener); data.removePropertyChangeListener(DataSet.PUBLICATION_LABEL, this.publicationLabelListener); data.removePropertyChangeListener(DataSet.PUBLICATION_CITATION, this.publicationCitationListener); data.removePropertyChangeListener(DataSet.PUBLICATION_URI, this.publicationURIListener); data.removePropertyChangeListener(DataSet.PUBLICATION_NOTES, this.pubNotesListener); data.removePropertyChangeListener(DataSet.CURATORS, this.curatorsListener); data.removePropertyChangeListener(DataSet.MATRIX_CELL, this.matrixListener); this.taxaObserver.stopObserving(data.getTaxa()); this.charactersObserver.stopObserving(data.getCharacters()); } @Override public void stopObserving(Collection<DataSet> objects) { for (DataSet data : objects) { this.stopObserving(data); } } private class MatrixCellUndoer implements PropertyChangeListener { @Override public void propertyChange(final PropertyChangeEvent change) { if (undoing) { undoing = false; return; } postEdit(new AbstractUndoableEdit() { @Override public String getPresentationName() { return "Edit Matrix Cell"; } @Override public void redo() throws CannotRedoException { super.redo(); undoing = true; final MatrixCellValue newValue = (MatrixCellValue)(change.getNewValue()); ((DataSet)(change.getSource())).setStateForTaxon(newValue.getCell().getTaxon(), newValue.getCell().getCharacter(), newValue.getState()); } @Override public void undo() throws CannotUndoException { super.undo(); undoing = true; final MatrixCellValue oldValue = (MatrixCellValue)(change.getOldValue()); ((DataSet)(change.getSource())).setStateForTaxon(oldValue.getCell().getTaxon(), oldValue.getCell().getCharacter(), oldValue.getState()); } }); } } } private class TaxonObserver implements Observer<Taxon> { private final PropertyChangeListener validNameListener = new PropertyUndoer("Edit Valid Taxon"); private final PropertyChangeListener pubNameListener = new PropertyUndoer("Edit Publication Taxon"); private final PropertyChangeListener matrixTaxonListener = new PropertyUndoer("Edit Matrix Taxon"); private final PropertyChangeListener commentListener = new PropertyUndoer("Edit Taxon Comment"); private final PropertyChangeListener figureListener = new PropertyUndoer("Edit Taxon Figure"); private final SpecimenObserver specimenObserver = new SpecimenObserver(); private final ListObserver<Specimen> specimensObserver = new ListObserver<Specimen>(this.specimenObserver, "Specimen"); @Override public void startObserving(Taxon taxon) { taxon.addPropertyChangeListener(Taxon.VALID_NAME, this.validNameListener); taxon.addPropertyChangeListener(Taxon.MATRIX_TAXON_NAME, this.matrixTaxonListener); taxon.addPropertyChangeListener(Taxon.PUBLICATION_NAME, this.pubNameListener); taxon.addPropertyChangeListener(Taxon.COMMENT, this.commentListener); taxon.addPropertyChangeListener(Taxon.FIGURE, this.figureListener); this.specimensObserver.startObserving(taxon.getSpecimens()); } @Override public void startObserving(Collection<Taxon> objects) { for (Taxon taxon : objects) { this.startObserving(taxon); } } @Override public void stopObserving(Taxon taxon) { taxon.removePropertyChangeListener(Taxon.VALID_NAME, this.validNameListener); taxon.removePropertyChangeListener(Taxon.MATRIX_TAXON_NAME, this.matrixTaxonListener); taxon.removePropertyChangeListener(Taxon.PUBLICATION_NAME, this.pubNameListener); taxon.removePropertyChangeListener(Taxon.COMMENT, this.commentListener); taxon.removePropertyChangeListener(Taxon.FIGURE, this.figureListener); this.specimensObserver.stopObserving(taxon.getSpecimens()); } @Override public void stopObserving(Collection<Taxon> objects) { for (Taxon taxon : objects) { this.stopObserving(taxon); } } } private class SpecimenObserver implements Observer<Specimen> { private final PropertyChangeListener collectionCodeListener = new PropertyUndoer("Edit Collection Code"); private final PropertyChangeListener catalogIDListener = new PropertyUndoer("Edit Catalog ID"); private final PropertyChangeListener commentListener = new PropertyUndoer("Edit Specimen Comment"); @Override public void startObserving(Specimen object) { object.addPropertyChangeListener(Specimen.COLLECTION_CODE, this.collectionCodeListener); object.addPropertyChangeListener(Specimen.CATALOG_ID, this.catalogIDListener); object.addPropertyChangeListener(Specimen.COMMENT, commentListener); } @Override public void startObserving(Collection<Specimen> objects) { for (Specimen specimen : objects) { this.startObserving(specimen); } } @Override public void stopObserving(Specimen object) { object.removePropertyChangeListener(Specimen.COLLECTION_CODE, this.collectionCodeListener); object.removePropertyChangeListener(Specimen.CATALOG_ID, this.catalogIDListener); object.removePropertyChangeListener(Specimen.COMMENT, this.commentListener); } @Override public void stopObserving(Collection<Specimen> objects) { for (Specimen specimen : objects) { this.stopObserving(specimen); } } } private class CharacterObserver implements Observer<Character> { private final PropertyChangeListener labelListener = new PropertyUndoer("Edit Character Description"); private final PropertyChangeListener commentListener = new PropertyUndoer("Edit Character Comment"); private final PropertyChangeListener figureListener = new PropertyUndoer("Edit Character Figure"); private final PropertyChangeListener discussionListener = new PropertyUndoer("Edit Character Discussion"); private final StateObserver stateObserver = new StateObserver(); private final ListObserver<State> statesObserver = new ListObserver<State>(stateObserver, "State"); @Override public void startObserving(Character character) { character.addPropertyChangeListener(Character.LABEL, this.labelListener); character.addPropertyChangeListener(Character.COMMENT, this.commentListener); character.addPropertyChangeListener(Character.FIGURE, this.figureListener); character.addPropertyChangeListener(Character.DISCUSSION, this.discussionListener); this.statesObserver.startObserving(character.getStates()); } @Override public void startObserving(Collection<Character> characters) { for (Character character : characters) { this.startObserving(character); } } @Override public void stopObserving(Character character) { character.removePropertyChangeListener(Character.LABEL, this.labelListener); character.removePropertyChangeListener(Character.COMMENT, this.commentListener); character.removePropertyChangeListener(Character.FIGURE, this.figureListener); character.removePropertyChangeListener(Character.DISCUSSION, this.discussionListener); this.statesObserver.stopObserving(character.getStates()); } @Override public void stopObserving(Collection<Character> characters) { for (Character character : characters) { this.stopObserving(character); } } } private class StateObserver implements Observer<State> { private final PropertyChangeListener labelListener = new PropertyUndoer("Edit State Description"); private final PropertyChangeListener symbolListener = new PropertyUndoer("Edit State Symbol"); private final PropertyChangeListener commentListener = new PropertyUndoer("Edit State Comment"); private final PropertyChangeListener figureListener = new PropertyUndoer("Edit State Figure"); private final PhenotypeObserver phenotypeObserver = new PhenotypeObserver(); private final ListObserver<Phenotype> phenotypesObserver = new ListObserver<Phenotype>(this.phenotypeObserver, "Phenotype"); @Override public void startObserving(State state) { state.addPropertyChangeListener(State.LABEL, this.labelListener); state.addPropertyChangeListener(State.SYMBOL, this.symbolListener); state.addPropertyChangeListener(State.COMMENT, this.commentListener); state.addPropertyChangeListener(State.FIGURE, this.figureListener); this.phenotypesObserver.startObserving(state.getPhenotypes()); } @Override public void startObserving(Collection<State> states) { for (State state : states) { this.startObserving(state); } } @Override public void stopObserving(State state) { state.removePropertyChangeListener(State.LABEL, this.labelListener); state.removePropertyChangeListener(State.SYMBOL, this.symbolListener); state.removePropertyChangeListener(State.COMMENT, this.commentListener); state.removePropertyChangeListener(State.FIGURE, this.figureListener); this.phenotypesObserver.stopObserving(state.getPhenotypes()); } @Override public void stopObserving(Collection<State> states) { for (State state : states) { this.stopObserving(state); } } } private class PhenotypeObserver implements Observer<Phenotype> { private final PropertyChangeListener entityListener = new PropertyUndoer("Edit Entity"); private final PropertyChangeListener qualityListener = new PropertyUndoer("Edit Quality"); private final PropertyChangeListener relatedEntityListener = new PropertyUndoer("Edit Related Entity"); private final PropertyChangeListener countListener = new PropertyUndoer("Edit Count"); private final PropertyChangeListener measurementListener = new PropertyUndoer("Edit Measurement"); private final PropertyChangeListener unitListener = new PropertyUndoer("Edit Unit"); private final PropertyChangeListener commentListener = new PropertyUndoer("Edit Phenotype Comment"); @Override public void startObserving(Phenotype phenotype) { phenotype.addPropertyChangeListener(Phenotype.ENTITY, this.entityListener); phenotype.addPropertyChangeListener(Phenotype.QUALITY, this.qualityListener); phenotype.addPropertyChangeListener(Phenotype.RELATED_ENTITY, this.relatedEntityListener); phenotype.addPropertyChangeListener(Phenotype.COUNT, this.countListener); phenotype.addPropertyChangeListener(Phenotype.MEASUREMENT, this.measurementListener); phenotype.addPropertyChangeListener(Phenotype.UNIT, this.unitListener); phenotype.addPropertyChangeListener(Phenotype.COMMENT, this.commentListener); } @Override public void startObserving(Collection<Phenotype> phenotypes) { for (Phenotype phenotype : phenotypes) { this.startObserving(phenotype); } } @Override public void stopObserving(Phenotype phenotype) { phenotype.removePropertyChangeListener(Phenotype.ENTITY, this.entityListener); phenotype.removePropertyChangeListener(Phenotype.QUALITY, this.qualityListener); phenotype.removePropertyChangeListener(Phenotype.RELATED_ENTITY, this.relatedEntityListener); phenotype.removePropertyChangeListener(Phenotype.COUNT, this.countListener); phenotype.removePropertyChangeListener(Phenotype.MEASUREMENT, this.measurementListener); phenotype.removePropertyChangeListener(Phenotype.UNIT, this.unitListener); phenotype.removePropertyChangeListener(Phenotype.COMMENT, this.commentListener); } @Override public void stopObserving(Collection<Phenotype> phenotypes) { for (Phenotype phenotype : phenotypes) { this.stopObserving(phenotype); } } } private class ListObserver<Y extends PropertyChangeObject> { private final Observer<Y> elementObserver; private final ListListener listListener = new ListListener(); private final String elementUndoName; public ListObserver(Observer<Y> elementObserver, String elementUndoName) { this.elementObserver = elementObserver; this.elementUndoName = elementUndoName; } public void startObserving(ObservableList<Y> list) { list.addObservableListListener(listListener); elementObserver.startObserving(list); } public void stopObserving(ObservableList<Y> list) { list.removeObservableListListener(listListener); elementObserver.stopObserving(list); } @SuppressWarnings("unchecked") private class ListListener implements ObservableListListener { @Override public void listElementPropertyChanged(@SuppressWarnings("rawtypes") ObservableList list, int index) {} @Override public void listElementReplaced(@SuppressWarnings("rawtypes") ObservableList list, int index, Object oldElement) { elementObserver.stopObserving((Y)oldElement); elementObserver.startObserving((Y)(list.get(index))); log().debug("Replaced " + elementUndoName); //TODO UNDO } @Override public void listElementsAdded(@SuppressWarnings("rawtypes") final ObservableList list, final int index, final int length) { final List<Y> added = list.subList(index, index + length); elementObserver.startObserving(added); if (undoing) { undoing = false; return; } postEdit(new AbstractUndoableEdit() { private final List<Y> items = new ArrayList<Y>(added); final int location = index; @Override public String getPresentationName() { return "Add " + elementUndoName; } @Override public void redo() throws CannotRedoException { super.redo(); undoing = true; list.addAll(this.location, this.items); } @Override public void undo() throws CannotUndoException { super.undo(); undoing = true; list.removeAll(this.items); } }); } @Override public void listElementsRemoved(@SuppressWarnings("rawtypes") final ObservableList list, final int index, @SuppressWarnings("rawtypes") final List oldElements) { elementObserver.stopObserving(oldElements); if (undoing) { undoing = false; return; } postEdit(new AbstractUndoableEdit() { final List<Y> items = new ArrayList<Y>(oldElements); final int location = index; @Override public String getPresentationName() { return "Delete " + elementUndoName; } @Override public void redo() throws CannotRedoException { super.redo(); undoing = true; list.removeAll(this.items); } @Override public void undo() throws CannotUndoException { super.undo(); undoing = true; list.addAll(this.location, this.items); } }); } } } private class PropertyUndoer implements PropertyChangeListener { private final String undoText; public PropertyUndoer(String presentationName) { this.undoText = presentationName; } @Override public void propertyChange(final PropertyChangeEvent change) { if (undoing) { undoing = false; return; } postEdit(new AbstractUndoableEdit() { @Override public String getPresentationName() { return undoText; } @Override public void redo() throws CannotRedoException { super.redo(); undoing = true; ((PropertyChangeObject)(change.getSource())).putValue(change.getPropertyName(), change.getNewValue()); } @Override public void undo() throws CannotUndoException { super.undo(); undoing = true; ((PropertyChangeObject)(change.getSource())).putValue(change.getPropertyName(), change.getOldValue()); } }); } } private void postEdit(UndoableEdit e) { this.undoController.postEdit(e); } private Logger log() { return Logger.getLogger(this.getClass()); } }