diff --git a/src/main/java/org/broad/igv/feature/genome/Genome.java b/src/main/java/org/broad/igv/feature/genome/Genome.java index b1fe48dfd5..1ac4e85654 100644 --- a/src/main/java/org/broad/igv/feature/genome/Genome.java +++ b/src/main/java/org/broad/igv/feature/genome/Genome.java @@ -257,6 +257,10 @@ private void addTracks(GenomeConfig config) { res.setVisibilityWindow(vw); } + if (trackConfig.searchTrix != null) { + res.setTrixURL(trackConfig.searchTrix); + } + res.setFeatureInfoURL(trackConfig.infoURL); Boolean indexed = trackConfig.indexed; if (indexed != null) { @@ -351,7 +355,7 @@ public Genome(String id, List chromosomes) { * Return the canonical chromosome name for the (possibly) alias * * @param str chromosome or alias name - * @return the canonical chromsoome name -- i.e. chromosome name as defined by the reference sequence + * @return the canonical chromsoome name -if the chromosome exists. */ public String getCanonicalChrName(String str) { if (str == null) { @@ -361,9 +365,7 @@ public String getCanonicalChrName(String str) { } else if (chromAliasSource != null) { try { ChromAlias aliasRecord = chromAliasSource.search(str); - if (aliasRecord == null) { - return str; - } else { + if (aliasRecord != null) { String chr = aliasRecord.getChr(); chrAliasCache.put(str, chr); return chr; @@ -487,9 +489,9 @@ public Chromosome getChromosome(String name) { if (chromosomeMap.containsKey(chrName)) { return chromosomeMap.get(chrName); } else { - int idx = this.chromosomeMap.size(); int length = this.sequence.getChromosomeLength(chrName); if (length > 0) { + int idx = this.chromosomeMap.size(); Chromosome chromosome = new Chromosome(idx, chrName, length); chromosomeMap.put(chrName, chromosome); return chromosome; diff --git a/src/main/java/org/broad/igv/feature/genome/GenomeManager.java b/src/main/java/org/broad/igv/feature/genome/GenomeManager.java index 81f9a816d3..040050da0a 100644 --- a/src/main/java/org/broad/igv/feature/genome/GenomeManager.java +++ b/src/main/java/org/broad/igv/feature/genome/GenomeManager.java @@ -56,6 +56,7 @@ import org.broad.igv.ui.PanelName; import org.broad.igv.ui.commandbar.GenomeListManager; import org.broad.igv.ui.panel.FrameManager; +import org.broad.igv.ui.panel.ReferenceFrame; import org.broad.igv.ui.util.MessageUtils; import org.broad.igv.ui.util.ProgressBar; import org.broad.igv.ui.util.ProgressMonitor; @@ -72,6 +73,7 @@ import java.net.SocketException; import java.net.URL; import java.net.URLDecoder; +import java.sql.Ref; import java.util.*; import java.util.List; @@ -231,8 +233,9 @@ public Genome loadGenome(String genomePath, ProgressMonitor monitor) throws IOEx // hasInstance() test needed for unit tests if (IGV.hasInstance()) { - IGV.getInstance().goToLocus(newGenome.getDefaultPos()); + IGV.getInstance().goToLocus(newGenome.getHomeChromosome()); // newGenome.getDefaultPos()); loadGenomeAnnotations(newGenome); + IGV.getInstance().resetFrames(); } if (PreferencesManager.getPreferences().getAsBoolean(Constants.CIRC_VIEW_ENABLED) && CircularViewUtilities.ping()) { diff --git a/src/main/java/org/broad/igv/feature/genome/Sequence.java b/src/main/java/org/broad/igv/feature/genome/Sequence.java index 0d8c31641b..891a89b027 100644 --- a/src/main/java/org/broad/igv/feature/genome/Sequence.java +++ b/src/main/java/org/broad/igv/feature/genome/Sequence.java @@ -38,13 +38,26 @@ */ public interface Sequence { - byte[] getSequence(String chr, int start, int end); + /** + * Return the sequence for the given range. If sequence named "seq" does not exist returns null. + * @param chr + * @param start + * @param end + * @return The sequence in bytes, or null if no sequence exists + */ + byte[] getSequence(String seq, int start, int end) ; - byte getBase(String chr, int position); + byte getBase(String seq, int position); List getChromosomeNames(); - int getChromosomeLength(String chrname); + /** + * Return the given sequence length. If no sequence exists with name "seq" return -1. + * + * @param seq + * @return + */ + int getChromosomeLength(String seq); List getChromosomes(); diff --git a/src/main/java/org/broad/igv/feature/genome/SequenceNotFoundException.java b/src/main/java/org/broad/igv/feature/genome/SequenceNotFoundException.java new file mode 100644 index 0000000000..7c6910d0c5 --- /dev/null +++ b/src/main/java/org/broad/igv/feature/genome/SequenceNotFoundException.java @@ -0,0 +1,7 @@ +package org.broad.igv.feature.genome; + +public class SequenceNotFoundException extends RuntimeException { + public SequenceNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/org/broad/igv/feature/genome/fasta/FastaIndexedSequence.java b/src/main/java/org/broad/igv/feature/genome/fasta/FastaIndexedSequence.java index 1944a5e0af..ffa9f25c2d 100644 --- a/src/main/java/org/broad/igv/feature/genome/fasta/FastaIndexedSequence.java +++ b/src/main/java/org/broad/igv/feature/genome/fasta/FastaIndexedSequence.java @@ -191,7 +191,7 @@ public List getChromosomeNames() { @Override public int getChromosomeLength(String chrname) { - return index.getSequenceSize(chrname); + return index.getSequenceSize(chrname); } @Override diff --git a/src/main/java/org/broad/igv/session/History.java b/src/main/java/org/broad/igv/session/History.java index 06ff6dad50..8f4719f787 100644 --- a/src/main/java/org/broad/igv/session/History.java +++ b/src/main/java/org/broad/igv/session/History.java @@ -31,6 +31,7 @@ import org.broad.igv.ui.IGV; import org.broad.igv.ui.action.SearchCommand; import org.broad.igv.ui.panel.FrameManager; +import org.broad.igv.util.LongRunningTask; import java.util.ArrayList; import java.util.LinkedList; @@ -121,7 +122,7 @@ public void processItem(Entry entry) { if (FrameManager.isGeneListMode()) { IGV.getInstance().setGeneList(null, false); } - (new SearchCommand(FrameManager.getDefaultFrame(), locus, false)).execute(); + LongRunningTask.submit(new SearchCommand(FrameManager.getDefaultFrame(), locus, false)); //Zoom should be implicit in the locus //FrameManager.getDefaultFrame().setZoom(entry.getZoom()); } diff --git a/src/main/java/org/broad/igv/track/FeatureSource.java b/src/main/java/org/broad/igv/track/FeatureSource.java index b001557f65..0e0f9be3c0 100644 --- a/src/main/java/org/broad/igv/track/FeatureSource.java +++ b/src/main/java/org/broad/igv/track/FeatureSource.java @@ -26,6 +26,7 @@ package org.broad.igv.track; import htsjdk.tribble.Feature; +import htsjdk.tribble.NamedFeature; import org.broad.igv.feature.LocusScore; import org.broad.igv.ui.panel.ReferenceFrame; @@ -92,4 +93,17 @@ default void close() { default Object getHeader() { return null; } + + /** + * Return true if the source can be searched for a feature by name + * + * @return + */ + default boolean isSearchable() { + return false; + } + + default NamedFeature search(String name) { + return null; + } } diff --git a/src/main/java/org/broad/igv/track/FeatureTrack.java b/src/main/java/org/broad/igv/track/FeatureTrack.java index 3af5ea1414..b78480e832 100644 --- a/src/main/java/org/broad/igv/track/FeatureTrack.java +++ b/src/main/java/org/broad/igv/track/FeatureTrack.java @@ -26,6 +26,7 @@ package org.broad.igv.track; import htsjdk.tribble.Feature; +import htsjdk.tribble.NamedFeature; import htsjdk.tribble.TribbleException; import org.broad.igv.event.IGVEvent; import org.broad.igv.logging.*; @@ -1056,6 +1057,16 @@ public void marshalXML(Document document, Element element) { } + @Override + public boolean isSearchable() { + return source.isSearchable(); + } + + @Override + public NamedFeature search(String token) { + return source.search(token); + } + @Override public void unmarshalXML(Element element, Integer version) { diff --git a/src/main/java/org/broad/igv/track/Track.java b/src/main/java/org/broad/igv/track/Track.java index 2732bd2e79..c127af7607 100644 --- a/src/main/java/org/broad/igv/track/Track.java +++ b/src/main/java/org/broad/igv/track/Track.java @@ -31,6 +31,7 @@ import htsjdk.tribble.Feature; +import htsjdk.tribble.NamedFeature; import org.broad.igv.renderer.ContinuousColorScale; import org.broad.igv.renderer.DataRange; import org.broad.igv.renderer.Renderer; @@ -272,9 +273,12 @@ default Color getExplicitAltColor() { void setAutoScale(boolean autoScale); - default boolean isShowFeatureNames() {return true;} + default boolean isShowFeatureNames() { + return true; + } - default void setShowFeatureNames(boolean b) {} + default void setShowFeatureNames(boolean b) { + } /** * Return the java property or attribute for the feature display name. Default is "null", in which case the @@ -286,6 +290,19 @@ default String getLabelField() { return null; } + /** + * Return true if the track can be searched for a feature by name. + * + * @return + */ + default boolean isSearchable() { + return false; + } + + default NamedFeature search(String token) { + return null; + } + default void repaint() { IGV.getInstance().repaint(this); } diff --git a/src/main/java/org/broad/igv/track/TrackLoader.java b/src/main/java/org/broad/igv/track/TrackLoader.java index 7186cad29d..c82a233543 100644 --- a/src/main/java/org/broad/igv/track/TrackLoader.java +++ b/src/main/java/org/broad/igv/track/TrackLoader.java @@ -76,6 +76,7 @@ import org.broad.igv.sam.reader.IndexNotFoundException; import org.broad.igv.tdf.TDFDataSource; import org.broad.igv.tdf.TDFReader; +import org.broad.igv.ucsc.Trix; import org.broad.igv.ucsc.bb.BBDataSource; import org.broad.igv.ucsc.bb.BBFeatureSource; import org.broad.igv.ucsc.bb.BBFile; @@ -792,9 +793,9 @@ public void loadBWFile(ResourceLocator locator, List newTracks, Genome ge String trackId = locator.getPath(); String path = locator.getPath(); - BBFile reader = new BBFile(path, genome); - Track track = null; - + String trixURL = locator.getTrixURL(); + BBFile reader = trixURL == null ? new BBFile(path, genome) : new BBFile(path, genome, trixURL); + Track track; if (reader.isBigWigFile()) { BBDataSource bigwigSource = new BBDataSource(reader, genome); track = new DataSourceTrack(locator, trackId, trackName, bigwigSource); diff --git a/src/main/java/org/broad/igv/ucsc/Trix.java b/src/main/java/org/broad/igv/ucsc/Trix.java index d1e487256c..a4d7ec640c 100644 --- a/src/main/java/org/broad/igv/ucsc/Trix.java +++ b/src/main/java/org/broad/igv/ucsc/Trix.java @@ -111,7 +111,8 @@ String _getBuffer(String searchWord) throws IOException { int end = 65536; List indexes = this.getIndex(); for (IndexEntry entry : indexes) { - String trimmedKey = entry.key.substring(0, searchWord.length()); + int trimEnd = Math.min(entry.key.length(), searchWord.length()); + String trimmedKey = entry.key.substring(0, trimEnd); if (trimmedKey.compareTo(searchWord) < 0) { start = entry.value; end = entry.value + 65536; diff --git a/src/main/java/org/broad/igv/ucsc/bb/BBFeatureSource.java b/src/main/java/org/broad/igv/ucsc/bb/BBFeatureSource.java index 38a46bcfd7..bef7a13667 100644 --- a/src/main/java/org/broad/igv/ucsc/bb/BBFeatureSource.java +++ b/src/main/java/org/broad/igv/ucsc/bb/BBFeatureSource.java @@ -25,9 +25,12 @@ package org.broad.igv.ucsc.bb; +import htsjdk.tribble.NamedFeature; import org.broad.igv.feature.BasicFeature; import org.broad.igv.feature.LocusScore; import org.broad.igv.feature.genome.Genome; +import org.broad.igv.logging.LogManager; +import org.broad.igv.logging.Logger; import org.broad.igv.track.FeatureSource; import org.broad.igv.track.WindowFunction; @@ -42,10 +45,9 @@ */ public class BBFeatureSource implements FeatureSource { - final int screenWidth = 1000; // TODO use actual screen width + private static Logger log = LogManager.getLogger(BBFeatureSource.class); private final Genome genome; - Collection availableWindowFunctions = Arrays.asList(WindowFunction.min, WindowFunction.mean, WindowFunction.max, WindowFunction.none); @@ -56,9 +58,6 @@ public class BBFeatureSource implements FeatureSource { private Map> wholeGenomeScores; - // Lookup table to support chromosome aliasing. - private Map chrNameMap = new HashMap(); - public BBFeatureSource(BBFile reader, Genome genome) throws IOException { super(); @@ -108,6 +107,20 @@ public List getCoverageScores(String chr, int start, int end, int zo return null; } + public boolean isSearchable() { + return reader.isSearchable(); + } + + @Override + public NamedFeature search(String term) { + try { + return reader.search(term); + } catch (IOException e) { + log.error("Error searching for: " + term, e); + return null; + } + } + static class FeatureIterator implements Iterator { List features; @@ -125,10 +138,9 @@ public FeatureIterator(List features, int start, int end) { } private void advance() { - if(idx == features.size()) { + if (idx == features.size()) { next = null; - } - else { + } else { while (idx < features.size()) { next = features.get(idx++); if (next.getStart() > end) { diff --git a/src/main/java/org/broad/igv/ucsc/bb/BBFile.java b/src/main/java/org/broad/igv/ucsc/bb/BBFile.java index b573c81d6f..fa032a9e06 100644 --- a/src/main/java/org/broad/igv/ucsc/bb/BBFile.java +++ b/src/main/java/org/broad/igv/ucsc/bb/BBFile.java @@ -1,6 +1,7 @@ package org.broad.igv.ucsc.bb; import htsjdk.samtools.seekablestream.SeekableStream; +import htsjdk.tribble.NamedFeature; import org.broad.igv.data.BasicScore; import org.broad.igv.feature.BasicFeature; import org.broad.igv.feature.LocusScore; @@ -112,6 +113,9 @@ public Set getChromosomeNames() { return chrNames; } + public void setTrix(Trix trix) { + } + enum Type {BIGWIG, BIGBED} @@ -384,6 +388,9 @@ BBZoomHeader zoomLevelForScale(double bpPerPixel) { return lastLevel.reductionLevel / 2 < bpPerPixel ? lastLevel : null; } + public boolean isSearchable() { + return header.extraIndexCount > 0; + } /** * Search the extended BP tree for the search term, and return any matching features. This only works @@ -394,6 +401,7 @@ BBZoomHeader zoomLevelForScale(double bpPerPixel) { * @param term * @returns {Promise} */ + public BasicFeature search(String term) throws IOException { if (this.header == null) { @@ -403,6 +411,16 @@ public BasicFeature search(String term) throws IOException { return null; } + if (this.trix != null) { + String termLower = term.toLowerCase(); + Map results = trix.search(termLower); + if (results != null) { + String[] exactMatches = results.get(termLower); + if (exactMatches.length > 0) term = exactMatches[0]; + } + } + + long[] region = this.searchForRegions(term); // Either 1 or no (undefined) reginos returned for now if (region != null) { long start = region[0]; @@ -412,11 +430,18 @@ public BasicFeature search(String term) throws IOException { is.seek(start); is.readFully(buffer); List features = decodeFeatures(buffer, -1, -1, -1); - BasicFeature largest = features.stream().reduce((f1, f2) -> { + + // Filter features to those matching term + final String searchTerm = term; + + BasicFeature largest = features.stream().filter(f -> { + return f.getName().equalsIgnoreCase(searchTerm) || f.getAttributes().values().stream().anyMatch(v -> v.equalsIgnoreCase(searchTerm)); + }).reduce((f1, f2) -> { int l1 = f1.getEnd() - f1.getStart(); int l2 = f2.getEnd() - f2.getStart(); return l1 > l2 ? f1 : f2; }).get(); + return largest; } } diff --git a/src/main/java/org/broad/igv/ucsc/twobit/TwoBitSequence.java b/src/main/java/org/broad/igv/ucsc/twobit/TwoBitSequence.java index c396fff00e..8c76aa36d0 100644 --- a/src/main/java/org/broad/igv/ucsc/twobit/TwoBitSequence.java +++ b/src/main/java/org/broad/igv/ucsc/twobit/TwoBitSequence.java @@ -2,6 +2,9 @@ import org.broad.igv.feature.Chromosome; import org.broad.igv.feature.genome.Sequence; +import org.broad.igv.feature.genome.SequenceNotFoundException; +import org.broad.igv.logging.LogManager; +import org.broad.igv.logging.Logger; import org.broad.igv.ucsc.BPIndex; import org.broad.igv.ucsc.BPTree; @@ -22,6 +25,8 @@ public class TwoBitSequence implements Sequence { + private static Logger log = LogManager.getLogger(TwoBitSequence.class); + // the number 0x1A412743 in the architecture of the machine that created the file static int SIGNATURE = 0x1a412743; String path; @@ -85,12 +90,15 @@ public List getChromosomeNames() { } @Override - public int getChromosomeLength(String chrname) { + public int getChromosomeLength(String seq) { try { - SequenceRecord sequenceRecord = getSequenceRecord(chrname); + SequenceRecord sequenceRecord = getSequenceRecord(seq); return sequenceRecord.getDnaSize(); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (SequenceNotFoundException e) { + return -1; + } catch (IOException e) { + log.error("Error reading sequence " + seq, e); + return -1; } } @@ -183,7 +191,7 @@ public SequenceRecord getSequenceRecord(String seqName) throws IOException { if (record == null) { long[] offset_length = this.index.search(seqName); if (offset_length == null) { - throw new RuntimeException("Unknown sequence: " + seqName); + throw new SequenceNotFoundException("Unknown sequence: " + seqName); } long offset = offset_length[0]; diff --git a/src/main/java/org/broad/igv/ui/IGV.java b/src/main/java/org/broad/igv/ui/IGV.java index b7fa6e962f..b0ad43ec92 100644 --- a/src/main/java/org/broad/igv/ui/IGV.java +++ b/src/main/java/org/broad/igv/ui/IGV.java @@ -259,7 +259,7 @@ public void windowGainedFocus(WindowEvent windowEvent) { subscribeToEvents(); // Start running periodic autosaves (unless the user has specified not to retain timed autosaves) - if(PreferencesManager.getPreferences().getAsInt(Constants.AUTOSAVES_TO_KEEP) > 0) { + if (PreferencesManager.getPreferences().getAsInt(Constants.AUTOSAVES_TO_KEEP) > 0) { int timerDelay = PreferencesManager.getPreferences().getAsInt(AUTOSAVE_FREQUENCY) * 60000; // Convert timer delay to ms sessionAutosaveTimer.scheduleAtFixedRate(new AutosaveTimerTask(this), timerDelay, timerDelay); } @@ -515,11 +515,10 @@ final public void saveStateForExit() { this.stopTimedAutosave(); // Autosave current session if configured to do so - if(PreferencesManager.getPreferences().getAsBoolean(AUTOSAVE_ON_EXIT)) { + if (PreferencesManager.getPreferences().getAsBoolean(AUTOSAVE_ON_EXIT)) { try { SessionAutosaveManager.saveExitSessionAutosaveFile(session); - } - catch(Exception e) { + } catch (Exception e) { log.error("Error autosaving session", e); } } @@ -885,9 +884,9 @@ public boolean scrollToTrack(String trackName) { if (tp.getScrollPane().getNamePanel().scrollTo(trackName)) { found = true; } - } - return found; } + return found; + } /** @@ -1238,7 +1237,7 @@ public List load(ResourceLocator locator) throws DataLoadException { track.setAttributeValue(Globals.TRACK_DATA_TYPE_ATTRIBUTE, track.getTrackType().toString()); TrackProperties properties = locator.getTrackProperties(); - if(properties != null) { + if (properties != null) { track.setProperties(properties); } } @@ -1287,7 +1286,7 @@ public TrackPanel getPanelFor(Track track) { } } - if(track.getClass() == FeatureTrack.class && !PreferencesManager.getPreferences().getAsBoolean(SHOW_SINGLE_TRACK_PANE_KEY)) + if (track.getClass() == FeatureTrack.class && !PreferencesManager.getPreferences().getAsBoolean(SHOW_SINGLE_TRACK_PANE_KEY)) return getTrackPanel(FEATURE_PANEL_NAME); else { return getTrackPanel(DATA_PANEL_NAME); @@ -1365,7 +1364,7 @@ public void sortAlignmentTracks(SortOption option, String tag, final boolean inv public void sortAlignmentTracks(SortOption option, Double location, String tag, boolean invertSort, Set priorityRecords) { List alignmentTracks = getAllTracks().stream() .filter(track -> track instanceof AlignmentTrack) - .map(track -> (AlignmentTrack)track) + .map(track -> (AlignmentTrack) track) .peek(track -> track.sortRows(option, location, tag, invertSort, priorityRecords)) .collect(Collectors.toList()); this.repaint(alignmentTracks); @@ -1900,8 +1899,7 @@ public void run() { boolean autosavePresent = false; try { autosavePresent = SessionAutosaveManager.getMostRecentAutosaveFile().isPresent(); - } - catch(Exception e) { + } catch (Exception e) { log.error("Failure trying to get most recent autosave file", e); } boolean loadAutosave = autosavePresent && PreferencesManager.getPreferences().getAsBoolean(AUTOLOAD_LAST_AUTOSAVE); @@ -1918,7 +1916,7 @@ public void run() { } } // If we're not loading a session file, attempt to load a default genome file - if(igvArgs.getSessionFile() == null && !loadAutosave && !genomeLoaded) { + if (igvArgs.getSessionFile() == null && !loadAutosave && !genomeLoaded) { String genomeId = preferences.getDefaultGenome(); try { GenomeManager.getInstance().loadGenomeById(genomeId); @@ -2041,8 +2039,7 @@ public void run() { // Get the last autosave and attempt to load File sessionAutosave = SessionAutosaveManager.getMostRecentAutosaveFile().get(); success = loadSession(sessionAutosave.getAbsolutePath(), null); - } - catch(Exception e) { + } catch (Exception e) { log.error("Failure trying to load most recent autosave file", e); } diff --git a/src/main/java/org/broad/igv/ui/action/SearchCommand.java b/src/main/java/org/broad/igv/ui/action/SearchCommand.java index 6e89e48442..242779f91f 100644 --- a/src/main/java/org/broad/igv/ui/action/SearchCommand.java +++ b/src/main/java/org/broad/igv/ui/action/SearchCommand.java @@ -39,21 +39,18 @@ import org.broad.igv.lists.GeneList; import org.broad.igv.prefs.Constants; import org.broad.igv.prefs.PreferencesManager; +import org.broad.igv.track.Track; import org.broad.igv.ui.IGV; import org.broad.igv.ui.panel.FrameManager; import org.broad.igv.ui.panel.ReferenceFrame; -import org.broad.igv.ui.util.IGVMouseInputAdapter; import org.broad.igv.ui.util.MessageUtils; import org.broad.igv.util.HttpUtils; -import org.broad.igv.util.liftover.Liftover; -import javax.swing.*; -import java.awt.*; -import java.awt.event.MouseEvent; import java.net.URL; import java.util.List; import java.util.*; + /** * A class for performing search actions. The class takes a view context and * search string as parameters. The search string can be either @@ -62,7 +59,7 @@ * * @author jrobinso */ -public class SearchCommand { +public class SearchCommand implements Runnable { private static Logger log = LogManager.getLogger(SearchCommand.class); public static int SEARCH_LIMIT = 10000; @@ -73,36 +70,10 @@ public class SearchCommand { Genome genome; - private static HashMap tokenMatchers; - - static { - - //Regexp for a number with commas in it (no periods) - String num_withcommas = "(((\\d)+,?)+)"; - - //chromosome can include anything except whitespace - String chromo_string = "(\\S)+"; - - String chromo = chromo_string; - //This will match chr1:1-100, chr1:1, chr1 1, chr1 1 100 - String chromo_range = chromo_string + "(:|(\\s)+)" + num_withcommas + "(-|(\\s)+)?" + num_withcommas + "?(\\s)*"; + private static HashMap tokenMatchers; - //Simple feature - String feature = chromo_string; - //Amino acid mutation notation. e.g. KRAS:G12C. * is stop codon - String featureMutAA = chromo_string + ":[A-Z,a-z,*]" + num_withcommas + "[A-Z,a-z,*]"; - - //Nucleotide mutation notation. e.g. KRAS:123A>T - String nts = "[A,C,G,T,a,c,g,t]"; - String featureMutNT = chromo_string + ":" + num_withcommas + nts + "\\>" + nts; - - tokenMatchers = new HashMap(); - tokenMatchers.put(ResultType.CHROMOSOME, chromo); - tokenMatchers.put(ResultType.FEATURE, feature); - tokenMatchers.put(ResultType.LOCUS, chromo_range); - tokenMatchers.put(ResultType.FEATURE_MUT_AA, featureMutAA); - tokenMatchers.put(ResultType.FEATURE_MUT_NT, featureMutNT); - } + static String featureMutAA = "(\\S)+" + ":[A-Z,a-z,*]" + "(((\\d)+,?)+)" + "[A-Z,a-z,*]"; + static String featureMutNT = "(\\S)+" + ":" + "(\\S)+" + "[A,C,G,T,a,c,g,t]" + "\\>" + "[A,C,G,T,a,c,g,t]"; public SearchCommand(ReferenceFrame referenceFrame, String searchString) { @@ -121,7 +92,7 @@ public SearchCommand(ReferenceFrame referenceFrame, String searchString, boolean } - public void execute() { + public void run() { List results = runSearch(searchString); showSearchResult(results); } @@ -146,60 +117,62 @@ public List runSearch(String searchString) { // Check for special "liftover" syntax. This allows searching based on coordinates from another genome // (the "target" genome) if an associated liftover map is defined for the target genome. - Liftover liftover = null; - if (searchString.startsWith("!") && genome.getLiftoverMap() != null) { - int idx = searchString.indexOf(' '); - String genomeKey = searchString.substring(1, idx); - liftover = genome.getLiftoverMap().get(genomeKey); - if (liftover != null) { - searchString = searchString.substring(idx + 1); - } - } +// Liftover liftover = null; +// if (searchString.startsWith("!") && genome.getLiftoverMap() != null) { +// int idx = searchString.indexOf(' '); +// String genomeKey = searchString.substring(1, idx); +// liftover = genome.getLiftoverMap().get(genomeKey); +// if (liftover != null) { +// searchString = searchString.substring(idx + 1); +// } +// } List results = new ArrayList<>(); searchString = searchString.replace("\"", ""); - Set wholeStringType = checkTokenType(searchString); - if (wholeStringType.contains(ResultType.LOCUS)) { - results.add(calcChromoLocus(searchString)); - } else { - // Space delimited? - String[] tokens = searchString.split("\\s+"); - for (String s : tokens) { - SearchResult result = parseToken(s); - if (result != null) { - results.add(result); - } else { - SearchResult unknownResult = new SearchResult(); - unknownResult.setMessage("Unknown search term: " + s); - results.add(unknownResult); + // If the search string is space delimited see if it looks like a space delimited locus string (e.g. chr 100 200) + String[] tokens = searchString.split("\\s+"); + if (tokens.length > 1 && tokens.length <= 3) { + boolean mightBeLocus = true; + for (int i = 1; i < tokens.length; i++) { + mightBeLocus = mightBeLocus && isInteger(tokens[i]); + } + if (mightBeLocus) { + Chromosome c1 = genome.getChromosome(tokens[0]); + if (c1 != null) { + Chromosome c2 = genome.getChromosome(tokens[1]); + if (c2 == null) { + results.add(calcChromoLocus(searchString)); + return results; + } } } } - if (results.size() == 0) { - SearchResult result = new SearchResult(); - result.setMessage("\"" + searchString + " \" not found."); - results.add(result); + for (String s : tokens) { + SearchResult result = parseToken(s); + if (result != null) { + results.add(result); + } } + // If this is a liftover search map the results - // TODO -- support gene name lookup - if(liftover != null) { - List mappedResults = new ArrayList<>(); - for(SearchResult result : results) { - if(result.getType() == ResultType.LOCUS) { - List mapped = liftover.map(new Range(result.getChr(), result.getStart(), result.getEnd())); - for(Range m : mapped) { - mappedResults.add(new SearchResult(result.type, m.chr, m.start, m.end)); - } - } else { - // ??? Error - } - } - results = mappedResults; - } +// if (liftover != null) { +// List mappedResults = new ArrayList<>(); +// for (SearchResult result : results) { +// if (result.getType() == ResultType.LOCUS) { +// List mapped = liftover.map(new Range(result.getChr(), result.getStart(), result.getEnd())); +// for (Range m : mapped) { +// mappedResults.add(new SearchResult(result.type, m.chr, m.start, m.end)); +// } +// } else { +// // ??? Error +// } +// } +// results = mappedResults; +// } return results; } @@ -231,23 +204,23 @@ public void showSearchResult(List results) { showFlankedRegion(result.chr, result.start, result.end); break; case LOCUS: - - Chromosome chromosome = GenomeManager.getInstance().getCurrentGenome().getChromosome(result.chr); - if (chromosome == null) { - message = "Unknow chromosome: " + result.chr; - success = false; - showMessage = true; - } else if (result.start > chromosome.getLength()) { - message = "Range " + result.locus + " is beyond the end of the chromosome"; - success = false; - showMessage = true; + if (result.chr.equalsIgnoreCase(Globals.CHR_ALL)) { + referenceFrame.changeChromosome(Globals.CHR_ALL, false); } else { - referenceFrame.jumpTo(result.chr, result.start, result.end); + Chromosome chromosome = GenomeManager.getInstance().getCurrentGenome().getChromosome(result.chr); + if (chromosome == null) { + message = "Unknow chromosome: " + result.chr; + success = false; + showMessage = true; + } else if (result.start > chromosome.getLength()) { + message = "Range " + result.locus + " is beyond the end of the chromosome"; + success = false; + showMessage = true; + } else { + referenceFrame.jumpTo(result.chr, result.start, result.end); + } } break; - case CHROMOSOME: - referenceFrame.changeChromosome(result.chr, true); - break; case ERROR: default: { message = "Cannot find feature or locus: " + searchString; @@ -320,9 +293,9 @@ public static Object[] getSelectionList(List results, boolean long Set checkTokenType(String token) { token = token.trim(); Set possibles = new HashSet<>(); - for (ResultType type : tokenMatchers.keySet()) { - if (token.matches(tokenMatchers.get(type))) { //note: entire string must match - possibles.add(type); + for (String key : tokenMatchers.keySet()) { + if (token.matches(key)) { + possibles.add(tokenMatchers.get(key)); } } return possibles; @@ -336,22 +309,32 @@ Set checkTokenType(String token) { */ private SearchResult parseToken(String token) { - List features; - - //Guess at token type via regex. - //We don't assume success - Set types = checkTokenType(token); - SearchResult result; - if (types.contains(ResultType.LOCUS) || types.contains(ResultType.CHROMOSOME)) { - //Check if a full or partial locus string - result = calcChromoLocus(token); - if (result.type != ResultType.ERROR) { - return result; + // Check featureDB first -- this is cheap + NamedFeature feat = searchFeatureDBs(token); + if (feat != null) { + return new SearchResult(feat); + } + + + //Check if a full or partial locus string + SearchResult result = calcChromoLocus(token); + if (result != null) { + return result; + } + + //Check if we have an exact match for the feature name + List searchableTracks = IGV.getInstance().getAllTracks().stream().filter(Track::isSearchable).toList(); + for (Track t : searchableTracks) { + NamedFeature match = t.search(token); + if (match != null) { + return new SearchResult(match); } } //2 possible mutation notations, either amino acid (A123B) or nucleotide (123G>C) - if (types.contains(ResultType.FEATURE_MUT_AA) || types.contains(ResultType.FEATURE_MUT_NT)) { + boolean mutAA = token.matches(featureMutAA); + boolean mutNT = token.matches(featureMutNT); + if (mutAA || mutNT) { //We know it has the right form, but may //not be valid feature name or mutation //which exists. @@ -363,7 +346,7 @@ private SearchResult parseToken(String token) { Map genomePosList; //Should never match both mutation notations - if (types.contains(ResultType.FEATURE_MUT_AA)) { + if (mutAA) { String refSymbol = coords.substring(0, 1); String mutSymbol = coords.substring(coordLength - 1); @@ -371,7 +354,7 @@ private SearchResult parseToken(String token) { int location = Integer.parseInt(strLoc) - 1; genomePosList = FeatureDB.getMutationAA(name, location + 1, refSymbol, mutSymbol, genome); - } else if (types.contains(ResultType.FEATURE_MUT_NT)) { + } else if (mutNT) { //Exclude the "A>T" at end String strLoc = coords.substring(0, coordLength - 3); String refSymbol = coords.substring(coordLength - 3, coordLength - 2); @@ -383,20 +366,15 @@ private SearchResult parseToken(String token) { } for (int genomePos : genomePosList.keySet()) { - Feature feat = genomePosList.get(genomePos); + Feature feature = genomePosList.get(genomePos); //Zoom in on mutation of interest //The +2 accounts for centering on the center of the amino acid, not beginning //and converting from 0-based to 1-based (which getStartEnd expects) int[] locs = getStartEnd("" + (genomePos + 2)); - return new SearchResult(ResultType.LOCUS, feat.getChr(), locs[0], locs[1]); - } - } else if (types.contains(ResultType.FEATURE)) { - //Check if we have an exact name for the feature name - NamedFeature feat = searchFeatureDBs(token); - if (feat != null) { - return new SearchResult(feat); + return new SearchResult(ResultType.LOCUS, feature.getChr(), locs[0], locs[1]); } } + return null; } @@ -458,27 +436,32 @@ private SearchResult calcChromoLocus(String searchString) { int colonIdx = searchString.lastIndexOf(":"); if (colonIdx > 0) { chr = searchString.substring(0, colonIdx); - String posString = searchString.substring(colonIdx).replace(":", ""); - startEnd = getStartEnd(posString); - //This MAY for case of chromoname having semicolon in it - if (startEnd == null) { - chr = searchString; + Chromosome chromosome = genome.getChromosome(chr); + if (chromosome == null) { + // try entire search string, chr name may have embedded colon + if (genome.getChromosome(searchString) != null) { + chr = searchString; + startEnd = null; + } + } else { + String posString = searchString.substring(colonIdx).replace(":", ""); + startEnd = getStartEnd(posString); + } } } // Show the "All chromosomes" view if the search string is "*" if (chr.equals("*") || chr.toLowerCase().equals("all")) { - return new SearchResult(ResultType.CHROMOSOME, Globals.CHR_ALL, 0, 1); + return new SearchResult(ResultType.LOCUS, Globals.CHR_ALL, 0, Integer.MAX_VALUE); } //startEnd will have coordinates if found. - chr = genome.getCanonicalChrName(chr); Chromosome chromosome = genome.getChromosome(chr); //If we couldn't find chromosome, check //whole string if (chromosome == null) { - chr = genome.getCanonicalChrName(tokens[0]); + chr = tokens[0]; chromosome = genome.getChromosome(chr); if (chromosome != null) { //Found chromosome @@ -487,18 +470,16 @@ private SearchResult calcChromoLocus(String searchString) { } if (chromosome != null && !searchString.equals(Globals.CHR_ALL)) { - if (startEnd != null) { - if (startEnd[1] >= startEnd[0]) { - return new SearchResult(ResultType.LOCUS, chr, startEnd[0], startEnd[1]); - } else { - SearchResult error = new SearchResult(ResultType.ERROR, chr, startEnd[0], startEnd[1]); - error.setMessage("End must be greater than start"); - return error; - } + chr = chromosome.getName(); + if (startEnd == null) { + return new SearchResult(ResultType.LOCUS, chr, 0, chromosome.getLength()); + } else { + int start = Math.min(startEnd[0], startEnd[1]); + int end = Math.max(startEnd[0], startEnd[1]); + return new SearchResult(ResultType.LOCUS, chr, start, end); } - return new SearchResult(ResultType.CHROMOSOME, chr, 0, chromosome.getLength() - 1); } - return new SearchResult(ResultType.ERROR, chr, -1, -1); + return null; } private void showFlankedRegion(String chr, int start, int end) { @@ -555,10 +536,7 @@ private static int[] getStartEnd(String posString) { public enum ResultType { FEATURE, - FEATURE_MUT_AA, - FEATURE_MUT_NT, LOCUS, - CHROMOSOME, ERROR, LIFTOVER } @@ -675,4 +653,11 @@ public static List getResults(List objects) { return results; } + private static boolean isInteger(String str) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c < '0' || c > '9') return false; + } + return true; + } } \ No newline at end of file diff --git a/src/main/java/org/broad/igv/ui/commandbar/IGVCommandBar.java b/src/main/java/org/broad/igv/ui/commandbar/IGVCommandBar.java index 5655357e66..cf35c8d122 100644 --- a/src/main/java/org/broad/igv/ui/commandbar/IGVCommandBar.java +++ b/src/main/java/org/broad/igv/ui/commandbar/IGVCommandBar.java @@ -328,7 +328,7 @@ public void searchByLocus(final String searchText) { if ((searchText != null) && (searchText.length() > 0)) { String homeChr = GenomeManager.getInstance().getCurrentGenome().getHomeChromosome(); - if (searchText.equalsIgnoreCase("home") || searchText.equalsIgnoreCase(homeChr)) { + if (searchText.equalsIgnoreCase("home")) { homeButtonActionPerformed(null); } else { searchTextField.setText(searchText); diff --git a/src/main/java/org/broad/igv/ui/commandbar/SearchTextField.java b/src/main/java/org/broad/igv/ui/commandbar/SearchTextField.java index bf50368aca..8444142639 100644 --- a/src/main/java/org/broad/igv/ui/commandbar/SearchTextField.java +++ b/src/main/java/org/broad/igv/ui/commandbar/SearchTextField.java @@ -7,6 +7,7 @@ import org.broad.igv.logging.Logger; import org.broad.igv.ui.action.SearchCommand; import org.broad.igv.ui.panel.FrameManager; +import org.broad.igv.util.LongRunningTask; import javax.swing.*; import javax.swing.text.JTextComponent; @@ -32,9 +33,7 @@ public SearchTextField() { public void searchByLocus(final String searchText) { - - (new SearchCommand(FrameManager.getDefaultFrame(), searchText)).execute(); - + LongRunningTask.submit((new SearchCommand(FrameManager.getDefaultFrame(), searchText))); } private class SearchHints extends ListDataIntelliHints { diff --git a/src/main/java/org/broad/igv/ui/panel/HeaderPanel.java b/src/main/java/org/broad/igv/ui/panel/HeaderPanel.java index f9aa680844..db2866e7df 100644 --- a/src/main/java/org/broad/igv/ui/panel/HeaderPanel.java +++ b/src/main/java/org/broad/igv/ui/panel/HeaderPanel.java @@ -94,7 +94,7 @@ private void init() { geneListPanel.setPreferredSize(new java.awt.Dimension(0, 0)); geneListPanel.setLayout(new java.awt.BorderLayout()); - label = new JLabel(frame.getName()); + label = new JLabel(frame.getFormattedLocusString()); label.setForeground(Color.blue); label.setToolTipText("Go to " + frame.getName()); label.addMouseListener(new MouseAdapter() { diff --git a/src/main/java/org/broad/igv/ui/panel/ReferenceFrame.java b/src/main/java/org/broad/igv/ui/panel/ReferenceFrame.java index fcc3499a29..c05be54458 100644 --- a/src/main/java/org/broad/igv/ui/panel/ReferenceFrame.java +++ b/src/main/java/org/broad/igv/ui/panel/ReferenceFrame.java @@ -635,13 +635,13 @@ public int getMidpoint() { */ public String getFormattedLocusString() { - if (zoom == 0) { - return getGenome().getChromosomeDisplayName(getChrName()); - } else { +// if (zoom == 0) { +// return getGenome().getChromosomeDisplayName(getChrName()); +// } else { Range range = getCurrentRange(); String c = getGenome().getChromosomeDisplayName(range.getChr()); return Locus.getFormattedLocusString(c, range.getStart(), range.getEnd()); - } + // } } public Range getCurrentRange() { diff --git a/src/main/java/org/broad/igv/util/ResourceLocator.java b/src/main/java/org/broad/igv/util/ResourceLocator.java index 3544116812..2677e8209d 100644 --- a/src/main/java/org/broad/igv/util/ResourceLocator.java +++ b/src/main/java/org/broad/igv/util/ResourceLocator.java @@ -129,6 +129,7 @@ public class ResourceLocator { private boolean htsget; private Integer visibilityWindow; + private String trixURL; public static List getLocators(Collection files) { @@ -608,6 +609,14 @@ private static boolean isCloudOrDropbox(String path) { } } + public void setTrixURL(String trixURL) { + this.trixURL = trixURL; + } + + public String getTrixURL() { + return trixURL; + } + /** * FOR LOAD FROM SERVER */ diff --git a/src/test/java/org/broad/igv/ucsc/TrixTest.java b/src/test/java/org/broad/igv/ucsc/TrixTest.java index 4130b1398b..ada21bcecf 100644 --- a/src/test/java/org/broad/igv/ucsc/TrixTest.java +++ b/src/test/java/org/broad/igv/ucsc/TrixTest.java @@ -17,5 +17,9 @@ public void testTrix() throws IOException { Map results = trix.search("ykoX"); String[] exactMatches = results.get("ykox"); assertEquals("NP_389226.1", exactMatches[0]); + + String notfound = "zzzzzz"; + results = trix.search(notfound); + } } \ No newline at end of file diff --git a/src/test/java/org/broad/igv/ucsc/bb/BBFeatureSourceTest.java b/src/test/java/org/broad/igv/ucsc/bb/BBFeatureSourceTest.java index da0198fe68..df70640290 100644 --- a/src/test/java/org/broad/igv/ucsc/bb/BBFeatureSourceTest.java +++ b/src/test/java/org/broad/igv/ucsc/bb/BBFeatureSourceTest.java @@ -3,6 +3,7 @@ import htsjdk.tribble.Feature; import org.broad.igv.feature.BasicFeature; import org.broad.igv.feature.genome.Genome; +import org.broad.igv.ucsc.Trix; import org.broad.igv.util.TestUtils; import org.junit.Test; @@ -215,22 +216,6 @@ public void testBigRmsk() throws IOException { assertTrue(count > 0); } - @Test - public void testExtraIndexSearch() throws IOException { - - String path = TestUtils.DATA_DIR + "genomes/GCF_000002655.1.chromAlias.bb"; - BBFile bbReader = new BBFile(path, null); - - // There are 5 extra indexes, 1 for each alias - String ncbiName = "3"; - BasicFeature f1 = (BasicFeature) bbReader.search(ncbiName); - assertNotNull(f1); - assertEquals(ncbiName, f1.getAttribute("ncbi")); - - String ucscName = "chr2"; - BasicFeature f2 = (BasicFeature) bbReader.search(ucscName); - assertEquals(ucscName, f2.getAttribute("ucsc")); - } } \ No newline at end of file diff --git a/src/test/java/org/broad/igv/ucsc/bb/BBFileTest.java b/src/test/java/org/broad/igv/ucsc/bb/BBFileTest.java index 9535348b9f..6928f172e5 100644 --- a/src/test/java/org/broad/igv/ucsc/bb/BBFileTest.java +++ b/src/test/java/org/broad/igv/ucsc/bb/BBFileTest.java @@ -31,13 +31,36 @@ public void testExtraIndexSearch() throws IOException { // There are 5 extra indexes, 1 for each alias String ncbiName = "3"; - BasicFeature f1 = (BasicFeature) bbReader.search(ncbiName); + BasicFeature f1 = bbReader.search(ncbiName); assertNotNull(f1); assertEquals(ncbiName, f1.getAttribute("ncbi")); String ucscName = "chr2"; - BasicFeature f2 = (BasicFeature) bbReader.search(ucscName); + BasicFeature f2 = bbReader.search(ucscName); assertEquals(ucscName, f2.getAttribute("ucsc")); + + assertNull(bbReader.search("zzzz")); + } + + @Test + public void testExtraIndexTrixSearch() throws IOException { + + String path = TestUtils.DATA_DIR + "bb/GCF_000009045.1_ASM904v1.ncbiGene.bb"; + String trixPath = TestUtils.DATA_DIR + "bb/ixIxx/GCF_000009045.1_ASM904v1.ncbiGene.ix"; + BBFile bbReader = new BBFile(path, null, trixPath); + + // Search by name, which is the index parameter, does not require trix + String name = "NP_389226.1"; + BasicFeature f = bbReader.search(name); + assertEquals(name, f.getName()); + + + // Search by alternate name, does require trix + String name2 = "ykoX"; + BasicFeature f2 = bbReader.search(name2); + assertEquals(name, f2.getName()); + + assertNull(bbReader.search("zzzz")); } diff --git a/src/test/java/org/broad/igv/ui/action/SearchCommandTest.java b/src/test/java/org/broad/igv/ui/action/SearchCommandTest.java index 3aa6451a41..df5934b757 100644 --- a/src/test/java/org/broad/igv/ui/action/SearchCommandTest.java +++ b/src/test/java/org/broad/igv/ui/action/SearchCommandTest.java @@ -53,7 +53,7 @@ public void setUp() throws Exception { } @Test - public void testSingleChromosomes() throws Exception { + public void testSingleLOCUSs() throws Exception { int min = 1; int max = 22; @@ -66,8 +66,8 @@ public void testSingleChromosomes() throws Exception { chrs[cn - min] = chr; nums[cn - min] = "" + cn; } - tstFeatureTypes(chrs, SearchCommand.ResultType.CHROMOSOME); - tstFeatureTypes(nums, SearchCommand.ResultType.CHROMOSOME); + tstFeatureTypes(chrs, SearchCommand.ResultType.LOCUS); + tstFeatureTypes(nums, SearchCommand.ResultType.LOCUS); } @Test @@ -93,7 +93,7 @@ public void testChromoWithColon() throws Exception { cmd = new SearchCommand(null, searchStr, genome); List results = cmd.runSearch(cmd.searchString); assertEquals(1, results.size()); - assertEquals(SearchCommand.ResultType.CHROMOSOME, results.get(0).type); + assertEquals(SearchCommand.ResultType.LOCUS, results.get(0).type); assertEquals(chr, results.get(0).chr); } } @@ -180,8 +180,8 @@ public void tstMultiFeatures(String delim) throws Exception { SearchCommand.ResultType.FEATURE, SearchCommand.ResultType.FEATURE, SearchCommand.ResultType.LOCUS, - SearchCommand.ResultType.CHROMOSOME, - SearchCommand.ResultType.CHROMOSOME + SearchCommand.ResultType.LOCUS, + SearchCommand.ResultType.LOCUS }; String searchStr = tokens[0]; for (int ii = 1; ii < tokens.length; ii++) { @@ -204,7 +204,7 @@ public void tstMultiFeatures(String delim) throws Exception { } @Test - public void testMultiChromosomes() throws Exception { + public void testMultiLOCUSs() throws Exception { String[] tokens = {"chr1", "chr5", "4", "12", "X", "Y"}; String searchStr = tokens[0]; for (int ii = 1; ii < tokens.length; ii++) { @@ -218,16 +218,11 @@ public void testMultiChromosomes() throws Exception { for (int ii = 0; ii < tokens.length; ii++) { SearchCommand.SearchResult result = results.get(ii); - assertEquals(SearchCommand.ResultType.CHROMOSOME, result.type); + assertEquals(SearchCommand.ResultType.LOCUS, result.type); assertTrue(result.getLocus().contains(tokens[ii])); } } - @Test - public void testError() throws Exception { - String[] tokens = {"ueth", "EGFRa", "BRCA56", "EGFR:?1?"}; - tstFeatureTypes(tokens, SearchCommand.ResultType.ERROR); - } /** @@ -239,7 +234,7 @@ public void testTokenChecking() { String[] chromos = {"chr3", "chr20", "chrX", "chrY"}; SearchCommand cmd = new SearchCommand(null, "", genome); for (String chr : chromos) { - assertEquals(SearchCommand.ResultType.CHROMOSOME, cmd.checkTokenType(chr)); + assertEquals(SearchCommand.ResultType.LOCUS, cmd.checkTokenType(chr)); } String[] starts = {"39,239,480", "958392", "0,4829,44", "5"}; diff --git a/src/test/java/org/broad/igv/variant/vcf/VCFVariantTest.java b/src/test/java/org/broad/igv/variant/vcf/VCFVariantTest.java index 1a66493fd3..62bdd41b9d 100644 --- a/src/test/java/org/broad/igv/variant/vcf/VCFVariantTest.java +++ b/src/test/java/org/broad/igv/variant/vcf/VCFVariantTest.java @@ -107,7 +107,7 @@ private VCFVariant tstStart(String chr, int fileFeatStart, VariantContext.Type v @Test public void tstFakeV4() throws Exception{ - System.setProperty("samjdk.optimistic_vcf_4_4", "true"); + System.setProperty("samjdk.optimistic_vcf_4_4", "true"); // Doesn't seem to work from command line (gradlew test) String filePath = TestUtils.DATA_DIR + "vcf/fake_v4.vcf"; TestUtils.createIndex(filePath);