savant.view.variation.VariationController.java Source code

Java tutorial

Introduction

Here is the source code for savant.view.variation.VariationController.java

Source

/**
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package savant.view.variation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import savant.api.adapter.RangeAdapter;
import savant.api.adapter.VariantDataSourceAdapter;
import savant.api.data.DataFormat;
import savant.api.data.Record;
import savant.api.data.VariantRecord;
import savant.api.event.DataRetrievalEvent;
import savant.api.event.LocationChangedEvent;
import savant.api.event.TrackEvent;
import savant.api.util.Listener;
import savant.api.util.RangeUtils;
import savant.controller.GraphPaneController;
import savant.controller.LocationController;
import savant.controller.TrackController;
import savant.settings.ResolutionSettings;
import savant.util.MiscUtils;
import savant.util.Range;
import savant.view.tracks.VariantTrack;
import savant.view.variation.swing.VariationModule;

/**
 * Controller class which governs the behaviour of the various variation-related views.
 * @author tarkvara
 */
public class VariationController implements Listener<DataRetrievalEvent> {
    static final Log LOG = LogFactory.getLog(VariationController.class);

    /**
     * Compare two variant records with an eye to merger.  The comparison is based on reference and position only (ignoring
     * the refBases and altAlleles fields which are usually considered by compareTo.
     */
    private static final Comparator<VariantRecord> VARIANT_COMPARATOR = new Comparator<VariantRecord>() {
        @Override
        public int compare(VariantRecord t, VariantRecord t1) {
            return new CompareToBuilder().append(t.getReference(), t1.getReference())
                    .append(t.getPosition(), t1.getPosition()).toComparison();

        }
    };

    private static VariationController instance;

    private List<VariantTrack> tracks = new ArrayList<VariantTrack>();
    private List<List<VariantRecord>> rawData = new ArrayList<List<VariantRecord>>();
    private List<VariantRecord> aggregateData = null;
    private String[] participants = new String[0];
    private Set<String> controls = new HashSet<String>();

    private String visibleRef;
    private Range visibleRange;

    private VariationModule module;

    private VariationController() {
        LocationController.getInstance().addListener(new Listener<LocationChangedEvent>() {
            @Override
            public void handleEvent(LocationChangedEvent event) {
                // Only change the variation range if the track range falls outside.
                String ref = event.getReference();
                RangeAdapter r = event.getRange();
                if (!ref.equals(visibleRef) || !RangeUtils.contains(visibleRange, r)) {
                    setLocation(ref, (Range) r);
                }
            }
        });

        // Attach listeners for track change events.
        TrackController.getInstance().addListener(new Listener<TrackEvent>() {

            @Override
            public void handleEvent(TrackEvent event) {
                if (event.getTrack().getDataFormat() == DataFormat.VARIANT) {
                    VariantTrack t = (VariantTrack) event.getTrack();
                    switch (event.getType()) {
                    case ADDED:
                        tracks.add(t);
                        rawData.add(null);
                        t.addListener(VariationController.this);
                        if (controls.contains(t.getName())) {
                            controls.addAll(Arrays.asList(t.getParticipantNames()));
                        }
                        break;
                    case REMOVED:
                        int index = tracks.indexOf(t);
                        tracks.remove(index);
                        rawData.remove(index);
                        t.removeListener(VariationController.this);
                        if (controls.contains(t.getName())) {
                            controls.remove(t.getName());
                            controls.removeAll(Arrays.asList(t.getParticipantNames()));
                        }
                        recalculate();
                        break;
                    }
                }
            }
        });
    }

    public static VariationController getInstance() {
        if (instance == null) {
            instance = new VariationController();
        }
        return instance;
    }

    /**
     * Sets the data to null so we know that there's nothing to render.
     *
     * @param evt describes the data being received
     */
    @Override
    public void handleEvent(DataRetrievalEvent evt) {
        if (evt.getRange().equals(visibleRange)) {
            int index = tracks.indexOf(evt.getTrack());
            if (index >= 0) {
                switch (evt.getType()) {
                case STARTED:
                    rawData.set(index, null);
                    module.showProgress("Retrieving variant data\u2026", -1.0);
                    break;
                case COMPLETED:
                    if (evt.getData() != null) {
                        rawData.set(index, (List) evt.getData());
                    } else {
                        rawData.set(index, new ArrayList<VariantRecord>(0));
                    }
                    LOG.trace("Received " + rawData.get(index).size() + " records for " + evt.getTrack()
                            + "; recalculating.");
                    recalculate();
                    break;
                case FAILED:
                    LOG.info("Received " + evt.getError() + " error for " + evt.getTrack());
                    module.showMessage(MiscUtils.getMessage(evt.getError()));
                    break;
                }
            }
        }
    }

    public synchronized List<VariantRecord> getData() {
        if (aggregateData == null) {
            int n = 0;
            int i = 0;
            List<String> names = new ArrayList<String>();
            for (VariantTrack t : tracks) {
                List<VariantRecord> trackData = rawData.get(i++);
                if (trackData != null) {
                    if (aggregateData == null) {
                        aggregateData = new ArrayList<VariantRecord>(trackData.size());
                    }
                    // Slower process.  Traverse the list inserting data as we go.
                    // It would might be more efficient to insert everything and sort,
                    // but we have to allow for the possibility of having to munge together
                    // two VariantRecords.
                    for (VariantRecord rec : trackData) {
                        int index = Collections.binarySearch(aggregateData, rec, VARIANT_COMPARATOR);
                        if (index < 0) {
                            // Not found in list.  Insert it at the given location.
                            int insertionPos = -index - 1;
                            if (LOG.isDebugEnabled()) {
                                String before = insertionPos > 0 ? aggregateData.get(insertionPos - 1).toString()
                                        : "START";
                                String after = insertionPos < aggregateData.size()
                                        ? aggregateData.get(insertionPos).toString()
                                        : "END";

                                LOG.debug("Inserting " + rec + " after " + before + " and before " + after);
                            }
                            aggregateData.add(insertionPos, new PaddedVariantRecord(rec, n));
                        } else {
                            VariantRecord oldRec = aggregateData.get(index);
                            LOG.debug("Merging " + rec + " into " + oldRec + " padding "
                                    + (n - oldRec.getParticipantCount()));
                            aggregateData.set(index,
                                    new MergedVariantRecord(oldRec, rec, n - oldRec.getParticipantCount()));
                        }
                    }
                    names.addAll(Arrays.asList(((VariantDataSourceAdapter) t.getDataSource()).getParticipants()));
                    n += t.getParticipantCount();
                }
            }
            participants = names.toArray(new String[0]);
        }
        return aggregateData;
    }

    /**
     * This method is used when storing the controls in the project.  We don't need to double-store the participants
     * for any tracks which are recorded as being stored.
     */
    public Set<String> getControls() {
        for (VariantTrack t : tracks) {
            if (controls.contains(t.getName())) {
                controls.removeAll(Arrays.asList(t.getParticipantNames()));
            }
        }
        return controls;
    }

    public void setControls(Collection<String> value) {
        controls.clear();
        controls.addAll(value);
        for (VariantTrack t : tracks) {
            if (controls.contains(t.getName())) {
                controls.addAll(Arrays.asList(t.getParticipantNames()));
            }
        }
    }

    public boolean isAControl(String p) {
        return controls.contains(p);
    }

    public String[] getParticipants() {
        return participants;
    }

    public int getParticipantCount() {
        return participants.length;
    }

    public VariantTrack[] getTracks() {
        return tracks.toArray(new VariantTrack[0]);
    }

    public void setLocation(String ref, Range r) {
        visibleRef = ref;
        setVisibleRange(r);
    }

    public Range getVisibleRange() {
        return visibleRange;
    }

    void setVisibleRange(Range r) {
        if (!r.equals(visibleRange)) {
            visibleRange = r;
            module.visibleRangeChanged(visibleRef, r);
            if (r.getLength() <= ResolutionSettings.getVariantLowToHighThreshold()) {
                for (VariantTrack t : tracks) {
                    t.requestData(visibleRef, visibleRange);
                }
            }
        }
    }

    /**
     * Adjust the visible range by setting its start.  Used when scrolling.
     */
    public void setVisibleRange(int start) {
        if (start != visibleRange.getFrom()) {
            setVisibleRange(new Range(start,
                    Math.min(start + visibleRange.getLength(), LocationController.getInstance().getMaxRangeEnd())));
        }
    }

    public String getReference() {
        return visibleRef;
    }

    /**
     * Zoom out by a factor of two.
     */
    public void zoomOut() {
        zoomToLength(visibleRange.getLength() * 2);
    }

    /**
     * Zoom in by a factor of two.
     */
    public void zoomIn() {
        zoomToLength(visibleRange.getLength() / 2);
    }

    private void zoomToLength(int length) {
        int maxLen = LocationController.getInstance().getMaxRangeEnd();
        if (length > maxLen) {
            length = maxLen;
        } else if (length < 1) {
            length = 1;
        }
        LOG.trace("Zooming to length " + length);
        int from = (visibleRange.getFrom() + 1 + visibleRange.getTo() - length) / 2;
        int to = from + length - 1;

        if (from < 1) {
            to += 1 - from;
            from = 1;
        }

        if (to > maxLen) {
            from -= to - maxLen;
            to = maxLen;

            if (from < 1) {
                from = 1;
            }
        }

        setVisibleRange(new Range(from, to));
    }

    /**
     * One of our panels has clicked or double-clicked.  Navigate to the record in the main window.
     */
    public void navigateToRecord(Record rec) {
        if (rec != null) {
            Range r;
            if (rec instanceof LDRecord) {
                // Unlike other records, LDRecords consist of two actual locations.
                List<VariantRecord> recs = ((LDRecord) rec).getConstituents();
                int pos0 = recs.get(0).getPosition();
                int pos1 = recs.get(1).getPosition();
                if (pos0 < pos1) {
                    r = new Range(pos0, pos1);
                } else {
                    r = new Range(pos1, pos0);
                }
            } else {
                int pos = ((VariantRecord) rec).getPosition();
                r = new Range(pos, pos);
            }
            LocationController.getInstance().setLocation(visibleRef, (Range) RangeUtils.addMargin(r));
        }
    }

    /**
     * A mouse-move on one of our sub-panels.  Update the status bar in the main window.
     */
    public void updateStatusBar(VariantRecord rec) {
        if (rec != null) {
            GraphPaneController.getInstance().setMouseXPosition(rec.getPosition());
        } else {
            GraphPaneController.getInstance().setMouseXPosition(-1);
        }
    }

    void recalculate() {
        for (List<VariantRecord> trackData : rawData) {
            if (trackData == null) {
                // One of the tracks hasn't reported in yet.
                return;
            }
        }
        module.showProgress("Aggregating variant data\u2026", -1.0);
        aggregateData = null;
        module.recalculated(getData());
    }

    public void cancelDataRequests() {
        LOG.info("Cancelling data requests for " + tracks.size() + " variant tracks.");
        for (VariantTrack t : tracks) {
            t.cancelDataRequest();
        }
    }

    public VariationModule getModule() {
        if (module == null) {
            module = new VariationModule(this);
        }
        return module;
    }

    public boolean isDPrimeSelected() {
        return module.isDPrimeSelected();
    }

    /**
     * Provides a displayable name for variant records which lack a name of their own.
     */
    public static String getDisplayName(VariantRecord rec) {
        String result = rec.getName();
        if (result == null || result.isEmpty()) {
            result = rec.getReference() + ":" + rec.getPosition();
        }
        return result;
    }
}