/* * Copyright (c) 2009 The Jackson Laboratory * * This software was developed by Gary Churchill's Lab at The Jackson * Laboratory (see http://research.jax.org/faculty/churchill). * * This 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 software 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 software. If not, see <http://www.gnu.org/licenses/>. */ package org.jax.qtl.project; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.logging.Level; import java.util.logging.Logger; import org.jax.qtl.cross.Cross; import org.jax.r.jriutilities.JRIUtilityFunctions; import org.jax.r.jriutilities.RInterface; import org.jax.r.jriutilities.RInterfaceFactory; import org.jax.r.jriutilities.RObject; /** * This class is used to represent all of the R data that our QTL * functionality cares about. * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class QtlDataModel { /** * our logger */ private static final Logger LOG = Logger.getLogger(QtlDataModel.class.getName()); /** * the R interface */ private final RInterface rInterface; /** * a mapping from identifiers to cross maps */ private final Map<String, Cross> identifierToCrossMap = Collections.synchronizedMap(new HashMap<String, Cross>()); /** * a list of our listeners */ private final ConcurrentLinkedQueue<QtlDataModelListener> listenerList = new ConcurrentLinkedQueue<QtlDataModelListener>(); /** * Constructor * @param rInterface * the R interface that this data model uses */ public QtlDataModel(RInterface rInterface) { this.rInterface = rInterface; this.updateAll(); } /** * Add a listener * @param listener * the listener */ public void addQtlDataModelListener(QtlDataModelListener listener) { this.listenerList.add(listener); } /** * Remove a listener * @param listener * the listener */ public void removeQtlDataModelListener(QtlDataModelListener listener) { this.listenerList.remove(listener); } /** * refresh the data model */ public void updateAll() { List<RObject> crosses = QtlDataModel.getAllCrossRObjects(); // add new crosses List<Cross> addedCrosses = new ArrayList<Cross>(); for(RObject currCrossObj: crosses) { Cross matchingCross = this.identifierToCrossMap.get( currCrossObj.getAccessorExpressionString()); if(matchingCross == null) { matchingCross = new Cross( this.rInterface, currCrossObj.getAccessorExpressionString()); this.identifierToCrossMap.put( currCrossObj.getAccessorExpressionString(), matchingCross); addedCrosses.add(matchingCross); } } // remove any missing crosses List<Cross> removedCrosses = new ArrayList<Cross>(); synchronized(this.identifierToCrossMap) { Iterator<Cross> crossEntryIter = this.identifierToCrossMap.values().iterator(); while(crossEntryIter.hasNext()) { Cross currCross = crossEntryIter.next(); boolean foundMatch = false; for(RObject currCrossRObject: crosses) { if(currCross.getAccessorExpressionString().equals( currCrossRObject.getAccessorExpressionString())) { foundMatch = true; } } if(!foundMatch) { removedCrosses.add(currCross); crossEntryIter.remove(); } } } // handle notification for(Cross currAddedCross: addedCrosses) { this.fireCrossAdded(currAddedCross); } for(Cross currRemovedCross: removedCrosses) { this.fireCrossRemoved(currRemovedCross); } } /** * Notify our listeners that a {@link Cross} has been added. * @param addedCross * the {@link Cross} that was added */ private void fireCrossAdded(Cross addedCross) { Iterator<QtlDataModelListener> listenerIter = this.listenerList.iterator(); while(listenerIter.hasNext()) { listenerIter.next().crossAdded(this, addedCross); } } /** * Notify our listeners that a {@link Cross} has been removed * @param removedCross * the {@link Cross} that was removed */ private void fireCrossRemoved(Cross removedCross) { Iterator<QtlDataModelListener> listenerIter = this.listenerList.iterator(); while(listenerIter.hasNext()) { listenerIter.next().crossRemoved(this, removedCross); } } /** * Get a cross map where the keys are the cross names and values are * the crosses * @return * the mapping */ public Map<String, Cross> getCrossMap() { return this.identifierToCrossMap; } /** * A convenience function that uses {@link #getCrossMap()} to construct * an array of crosses * @return * the array */ public Cross[] getCrosses() { synchronized(this.identifierToCrossMap) { Cross[] crosses = new Cross[this.identifierToCrossMap.size()]; return this.identifierToCrossMap.values().toArray(crosses); } } /** * Scan the current R environment and return all cross object * identifiers. * @return * the list of cross identifiers */ private static List<RObject> getAllCrossRObjects() { RInterface rInterface = RInterfaceFactory.getRInterfaceInstance(); List<RObject> crossIdentifiers = JRIUtilityFunctions.getTopLevelObjectsOfType( rInterface, Cross.TYPE_STRING); if(LOG.isLoggable(Level.FINEST)) { StringBuffer message = new StringBuffer("detected crosses:"); for(RObject currCrossId: crossIdentifiers) { message.append(" " + currCrossId.getAccessorExpressionString()); } LOG.finest(message.toString()); } return crossIdentifiers; } }