diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CovariateCounterWalker.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CovariateCounterWalker.java index 553582dc7..c8d39da7f 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CovariateCounterWalker.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CovariateCounterWalker.java @@ -86,6 +86,12 @@ public class CovariateCounterWalker extends LocusWalker { @Argument(fullName="process_nth_locus", shortName="pN", required=false, doc="Only process every Nth covered locus we see.") private int PROCESS_EVERY_NTH_LOCUS = 1; + ///////////////////////////// + // Debugging-only Arguments + ///////////////////////////// + @Argument(fullName="dont_sort_output", shortName="unsorted", required=false, doc="If specified, the output table recalibration csv file will be in an unsorted, arbitrary order to save some run time.") + private boolean DONT_SORT_OUTPUT = false; + ///////////////////////////// // Private Member Variables ///////////////////////////// @@ -97,7 +103,7 @@ public class CovariateCounterWalker extends LocusWalker { private long solidInsertedReferenceBases = 0; // Number of bases where we believe SOLID has inserted the reference because the color space is inconsistent with the read base private long otherColorSpaceInconsistency = 0; // Number of bases where the color space is inconsistent with the read but the reference wasn't inserted. private int numUnprocessed = 0; // Number of consecutive loci skipped because we are only processing every Nth site - private static final String versionString = "v2.2.0"; // Major version, minor version, and build number + private static final String versionString = "v2.2.1"; // Major version, minor version, and build number private Pair dbSNP_counts = new Pair(0L, 0L); // mismatch/base counts for dbSNP loci private Pair novel_counts = new Pair(0L, 0L); // mismatch/base counts for non-dbSNP loci private static final double DBSNP_VS_NOVEL_MISMATCH_RATE = 2.0; // rate at which dbSNP sites (on an individual level) mismatch relative to novel sites (determined by looking at NA12878) @@ -207,11 +213,6 @@ public class CovariateCounterWalker extends LocusWalker { cov.initialize( RAC ); // Initialize any covariate member variables using the shared argument collection } - // Don't want to crash with out of heap space exception - //if( estimatedCapacity > 300 * 40 * 200 || estimatedCapacity < 0 ) { // Could be negative if overflowed - // estimatedCapacity = 300 * 40 * 200; - //} - dataManager = new RecalDataManager(); } @@ -313,9 +314,9 @@ public class CovariateCounterWalker extends LocusWalker { */ private static void updateMismatchCounts(Pair counts, AlignmentContext context, char ref) { for( PileupElement p : context.getPileup() ) { - char readChar = (char)(p.getBase()); - int readCharBaseIndex = BaseUtils.simpleBaseToBaseIndex(readChar); - int refCharBaseIndex = BaseUtils.simpleBaseToBaseIndex(ref); + final char readChar = (char)(p.getBase()); + final int readCharBaseIndex = BaseUtils.simpleBaseToBaseIndex(readChar); + final int refCharBaseIndex = BaseUtils.simpleBaseToBaseIndex(ref); if( readCharBaseIndex != -1 && refCharBaseIndex != -1 ) { if( readCharBaseIndex != refCharBaseIndex ) { @@ -331,12 +332,12 @@ public class CovariateCounterWalker extends LocusWalker { * Validate the dbSNP reference mismatch rates. */ private void validateDbsnpMismatchRate() { - if( novel_counts.second == 0 || dbSNP_counts.second == 0 ) { + if( novel_counts.second == 0L || dbSNP_counts.second == 0L ) { return; } - double fractionMM_novel = (double)novel_counts.first / (double)novel_counts.second; - double fractionMM_dbsnp = (double)dbSNP_counts.first / (double)dbSNP_counts.second; + final double fractionMM_novel = (double)novel_counts.first / (double)novel_counts.second; + final double fractionMM_dbsnp = (double)dbSNP_counts.first / (double)dbSNP_counts.second; if( fractionMM_dbsnp < DBSNP_VS_NOVEL_MISMATCH_RATE * fractionMM_novel ) { Utils.warnUser("The variation rate at the supplied list of known variant sites seems suspiciously low. Please double-check that the correct ROD is being used. " + @@ -358,23 +359,24 @@ public class CovariateCounterWalker extends LocusWalker { */ private void updateDataFromRead(final SAMRecord read, final int offset, final byte refBase) { - List key = new ArrayList(); + final Object[] key = new Object[requestedCovariates.size()]; - // Loop through the list of requested covariates and pick out the value from the read, offset, and reference + // Loop through the list of requested covariates and pick out the value from the read and offset + int iii = 0; for( Covariate covariate : requestedCovariates ) { - key.add(covariate.getValue( read, offset )); + key[iii++] = covariate.getValue( read, offset ); } // Using the list of covariate values as a key, pick out the RecalDatum from the data HashMap - RecalDatum datum = (RecalDatum) dataManager.data.get( key.toArray() ); + RecalDatum datum = (RecalDatum) dataManager.data.get( key ); if( datum == null ) { // key doesn't exist yet in the map so make a new bucket and add it datum = new RecalDatum(); // initialized with zeros, will be incremented at end of method - dataManager.data.put( datum, key.toArray() ); + dataManager.data.put( datum, (Object[])key ); } // Need the bases to determine whether or not we have a mismatch - byte base = read.getReadBases()[offset]; - long curMismatches = datum.getNumMismatches(); + final byte base = read.getReadBases()[offset]; + final long curMismatches = datum.getNumMismatches(); // Add one to the number of observations and potentially one to the number of mismatches datum.increment( (char)base, (char)refBase ); // Dangerous: If you don't cast to char than the bytes default to the (long, long) version of the increment method which is really bad @@ -449,15 +451,53 @@ public class CovariateCounterWalker extends LocusWalker { } recalTableStream.println("nObservations,nMismatches,Qempirical"); - // For each entry in the data hashmap - for( Pair entry : dataManager.data.entrySetSorted() ) { - // For each Covariate in the key - for( Object comp : entry.first ) { - // Output the Covariate's value - recalTableStream.print( comp + "," ); + if( DONT_SORT_OUTPUT ) { + printMappings(recalTableStream, 0, new Object[requestedCovariates.size()], dataManager.data.data); + } else { + printMappingsSorted(recalTableStream, 0, new Object[requestedCovariates.size()], dataManager.data.data); + } + } + + private void printMappingsSorted( final PrintStream recalTableStream, final int curPos, final Object[] key, final Map data) { + final ArrayList keyList = new ArrayList(); + for( Object comp : data.keySet() ) { + keyList.add((Comparable) comp); + } + + Collections.sort(keyList); + + for( Comparable comp : keyList ) { + key[curPos] = comp; + final Object val = data.get(comp); + if( val instanceof RecalDatum ) { // We are at the end of the nested hash maps + // For each Covariate in the key + for( Object compToPrint : key ) { + // Output the Covariate's value + recalTableStream.print( compToPrint + "," ); + } + // Output the RecalDatum entry + recalTableStream.println( ((RecalDatum)val).outputToCSV() ); + } else { // Another layer in the nested hash map + printMappingsSorted( recalTableStream, curPos + 1, key, (Map) val); + } + } + } + + private void printMappings( final PrintStream recalTableStream, final int curPos, final Object[] key, final Map data) { + for( Object comp : data.keySet() ) { + key[curPos] = comp; + final Object val = data.get(comp); + if( val instanceof RecalDatum ) { // We are at the end of the nested hash maps + // For each Covariate in the key + for( Object compToPrint : key ) { + // Output the Covariate's value + recalTableStream.print( compToPrint + "," ); + } + // Output the RecalDatum entry + recalTableStream.println( ((RecalDatum)val).outputToCSV() ); + } else { // Another layer in the nested hash map + printMappings( recalTableStream, curPos + 1, key, (Map) val); } - // Output the RecalDatum entry - recalTableStream.println( ((RecalDatum)entry.second).outputToCSV() ); } } } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java index 840c2a727..7d2fdfc65 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/CycleCovariate.java @@ -78,7 +78,7 @@ public class CycleCovariate implements StandardCovariate { //----------------------------- else if( read.getReadGroup().getPlatform().contains( "454" ) ) { // Some bams have "LS454" and others have just "454" - byte[] bases = read.getReadBases(); + final byte[] bases = read.getReadBases(); // BUGBUG: Consider looking at degradation of base quality scores in homopolymer runs to detect when the cycle incremented even though the nucleotide didn't change // For example, AAAAAAA was probably read in two flow cycles but here we count it as one diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/DinucCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/DinucCovariate.java index 01c9af3fd..5133812dc 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/DinucCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/DinucCovariate.java @@ -65,7 +65,7 @@ public class DinucCovariate implements StandardCovariate { byte base; byte prevBase; - byte[] bases = read.getReadBases(); + final byte[] bases = read.getReadBases(); // If this is a negative strand read then we need to reverse the direction for our previous base if( read.getReadNegativeStrandFlag() ) { // No dinuc at the beginning of the read @@ -95,7 +95,7 @@ public class DinucCovariate implements StandardCovariate { // Used to get the covariate's value from input csv file in TableRecalibrationWalker public final Comparable getValue( final String str ) { - Dinuc returnDinuc = dinucHashMap.get( Dinuc.hashBytes( (byte)str.charAt(0), (byte)str.charAt(1) ) ); + final Dinuc returnDinuc = dinucHashMap.get( Dinuc.hashBytes( (byte)str.charAt(0), (byte)str.charAt(1) ) ); if( returnDinuc.compareTo(NO_DINUC) == 0 ) { return null; } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/HomopolymerCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/HomopolymerCovariate.java index e9745b6a3..b5ea4e8fa 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/HomopolymerCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/HomopolymerCovariate.java @@ -73,7 +73,7 @@ public class HomopolymerCovariate implements ExperimentalCovariate { */ int numAgree = 0; // The number of consecutive bases that agree with you in the previous numBack bases of the read - byte[] bases = read.getReadBases(); + final byte[] bases = read.getReadBases(); int iii = offset; if( !read.getReadNegativeStrandFlag() ) { // Forward direction while( iii <= bases.length-2 && bases[iii] == bases[iii+1] && numAgree < numBack ) { diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MappingQualityCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MappingQualityCovariate.java index 0b19fd57b..589031c79 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MappingQualityCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MappingQualityCovariate.java @@ -42,8 +42,7 @@ public class MappingQualityCovariate implements ExperimentalCovariate { } // Used to pick out the covariate's value from attributes of the read - public final Comparable getValue( final SAMRecord read, final int offset ) { - + public final Comparable getValue( final SAMRecord read, final int offset ) { return read.getMappingQuality(); } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MinimumNQSCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MinimumNQSCovariate.java index c97d553d2..af316c4a7 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MinimumNQSCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/MinimumNQSCovariate.java @@ -49,10 +49,10 @@ public class MinimumNQSCovariate implements ExperimentalCovariate { public final Comparable getValue( final SAMRecord read, final int offset ) { // Loop over the list of base quality scores in the window and find the minimum - byte[] quals = read.getBaseQualities(); + final byte[] quals = read.getBaseQualities(); int minQual = quals[offset]; - int minIndex = Math.max(offset - windowReach, 0); - int maxIndex = Math.min(offset + windowReach, quals.length - 1); + final int minIndex = Math.max(offset - windowReach, 0); + final int maxIndex = Math.min(offset + windowReach, quals.length - 1); for ( int iii = minIndex; iii < maxIndex; iii++ ) { if( quals[iii] < minQual ) { minQual = quals[iii]; diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/QualityScoreCovariate.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/QualityScoreCovariate.java index a174f4477..021130609 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/QualityScoreCovariate.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/QualityScoreCovariate.java @@ -42,8 +42,7 @@ public class QualityScoreCovariate implements RequiredCovariate { } // Used to pick out the covariate's value from attributes of the read - public final Comparable getValue( final SAMRecord read, final int offset ) { - + public final Comparable getValue( final SAMRecord read, final int offset ) { return (int)(read.getBaseQualities()[offset]); } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java index 626077df4..a25045ffe 100644 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDataManager.java @@ -84,7 +84,7 @@ public class RecalDataManager { // The full dataset isn't actually ever used for anything because of the sequential calculation so no need to keep the full data HashMap around //data.put(key, thisDatum); // add the mapping to the main table - int qualityScore = Integer.parseInt( key[1].toString() ); + final int qualityScore = Integer.parseInt( key[1].toString() ); final Object[] readGroupCollapsedKey = new Object[1]; final Object[] qualityScoreCollapsedKey = new Object[2]; final Object[] covariateCollapsedKey = new Object[3]; @@ -135,15 +135,21 @@ public class RecalDataManager { */ public final void generateEmpiricalQualities( final int smoothing ) { - for( Pair entry : dataCollapsedReadGroup.entrySetSorted() ) { - ((RecalDatum)entry.second).calcCombinedEmpiricalQuality(smoothing); - } - for( Pair entry : dataCollapsedQualityScore.entrySetSorted() ) { - ((RecalDatum)entry.second).calcCombinedEmpiricalQuality(smoothing); - } + recursivelyGenerateEmpiricalQualities(dataCollapsedReadGroup.data, smoothing); + recursivelyGenerateEmpiricalQualities(dataCollapsedQualityScore.data, smoothing); for( NestedHashMap map : dataCollapsedByCovariate ) { - for( Pair entry : map.entrySetSorted() ) { - ((RecalDatum)entry.second).calcCombinedEmpiricalQuality(smoothing); + recursivelyGenerateEmpiricalQualities(map.data, smoothing); + } + } + + private void recursivelyGenerateEmpiricalQualities( final Map data, final int smoothing ) { + + for( Object comp : data.keySet() ) { + final Object val = data.get(comp); + if( val instanceof RecalDatum ) { // We are at the end of the nested hash maps + ((RecalDatum)val).calcCombinedEmpiricalQuality(smoothing); + } else { // Another layer in the nested hash map + recursivelyGenerateEmpiricalQualities( (Map) val, smoothing); } } } diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDatum.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDatum.java index d3cd163a7..98b357f63 100755 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDatum.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/RecalDatum.java @@ -111,8 +111,8 @@ public class RecalDatum { //--------------------------------------------------------------------------------------------------------------- public final double empiricalQualDouble( final int smoothing ) { - double doubleMismatches = (double) ( numMismatches + smoothing ); - double doubleObservations = (double) ( numObservations + smoothing ); + final double doubleMismatches = (double) ( numMismatches + smoothing ); + final double doubleObservations = (double) ( numObservations + smoothing ); double empiricalQual = -10 * Math.log10(doubleMismatches / doubleObservations); if (empiricalQual > QualityUtils.MAX_REASONABLE_Q_SCORE) { empiricalQual = QualityUtils.MAX_REASONABLE_Q_SCORE; } return empiricalQual; @@ -123,8 +123,8 @@ public class RecalDatum { public final byte empiricalQualByte( final int smoothing ) { - double doubleMismatches = (double) ( numMismatches + smoothing ); - double doubleObservations = (double) ( numObservations + smoothing ); + final double doubleMismatches = (double) ( numMismatches + smoothing ); + final double doubleObservations = (double) ( numObservations + smoothing ); return QualityUtils.probToQual( 1.0 - doubleMismatches / doubleObservations ); } public final byte empiricalQualByte() { return empiricalQualByte( 0 ); } // 'default' behavior is to use smoothing value of zero diff --git a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java index df93cb510..8c911dbf4 100644 --- a/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java +++ b/java/src/org/broadinstitute/sting/gatk/walkers/recalibration/TableRecalibrationWalker.java @@ -89,12 +89,10 @@ public class TableRecalibrationWalker extends ReadWalker requestedCovariates; // List of covariates to be used in this calculation - private ArrayList fullCovariateKey; // The list that will be used over and over again to hold the full set of covariate values - private ArrayList collapsedTableKey; // The key that will be used over and over again to query the collapsed tables private static final Pattern COMMENT_PATTERN = Pattern.compile("^#.*"); private static final Pattern OLD_RECALIBRATOR_HEADER = Pattern.compile("^rg,.*"); private static final Pattern COVARIATE_PATTERN = Pattern.compile("^ReadGroup,QualityScore,.*"); - private static final String versionString = "v2.2.0"; // Major version, minor version, and build number + private static final String versionString = "v2.2.1"; // Major version, minor version, and build number private SAMFileWriter OUTPUT_BAM = null;// The File Writer that will write out the recalibrated bam private Random coinFlip; // Random number generator is used to remove reference bias in solid bams private static final long RANDOM_SEED = 1032861495; @@ -138,9 +136,6 @@ public class TableRecalibrationWalker extends ReadWalker(); // Initialize the key only once - collapsedTableKey = new ArrayList(); // Initialize the key only once - // Read in the covariates that were used from the input file requestedCovariates = new ArrayList(); @@ -222,13 +217,17 @@ public class TableRecalibrationWalker extends ReadWalker key = new ArrayList(); + final Object[] key = new Object[requestedCovariates.size()]; Covariate cov; int iii; for( iii = 0; iii < requestedCovariates.size(); iii++ ) { cov = requestedCovariates.get( iii ); - key.add( cov.getValue( vals[iii] ) ); + key[iii] = cov.getValue( vals[iii] ); } + // Create a new datum using the number of observations, number of mismatches, and reported quality score - RecalDatum datum = new RecalDatum( Long.parseLong( vals[iii] ), Long.parseLong( vals[iii + 1] ), Double.parseDouble( vals[1] ), 0.0 ); + final RecalDatum datum = new RecalDatum( Long.parseLong( vals[iii] ), Long.parseLong( vals[iii + 1] ), Double.parseDouble( vals[1] ), 0.0 ); // Add that datum to all the collapsed tables which will be used in the sequential calculation - dataManager.addToAllTables( key.toArray(), datum, PRESERVE_QSCORES_LESS_THAN ); + dataManager.addToAllTables( key, datum, PRESERVE_QSCORES_LESS_THAN ); } @@ -304,24 +304,26 @@ public class TableRecalibrationWalker extends ReadWalker key ) { + private byte performSequentialQualityCalculation(Object... key ) { - String readGroupKeyElement = key.get(0).toString(); - int qualityScoreKeyElement = Integer.parseInt(key.get(1).toString()); - byte qualFromRead = (byte)qualityScoreKeyElement; + final String readGroupKeyElement = key[0].toString(); + final int qualityScoreKeyElement = Integer.parseInt(key[1].toString()); + final byte qualFromRead = (byte)qualityScoreKeyElement; + final Object[] readGroupCollapsedKey = new Object[1]; + final Object[] qualityScoreCollapsedKey = new Object[2]; + final Object[] covariateCollapsedKey = new Object[3]; // The global quality shift (over the read group only) - collapsedTableKey.add( readGroupKeyElement ); - RecalDatum globalRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(0).get( collapsedTableKey.toArray() )); + readGroupCollapsedKey[0] = readGroupKeyElement; + RecalDatum globalRecalDatum = ((RecalDatum)dataManager.getCollapsedTable(0).get( readGroupCollapsedKey )); double globalDeltaQ = 0.0; if( globalRecalDatum != null ) { double globalDeltaQEmpirical = globalRecalDatum.getEmpiricalQuality(); @@ -370,8 +375,9 @@ public class TableRecalibrationWalker extends ReadWalker } - double newQuality = qualFromRead + globalDeltaQ + deltaQReported + deltaQCovariates; - byte newQualityByte = QualityUtils.boundQual( (int)Math.round(newQuality), QualityUtils.MAX_REASONABLE_Q_SCORE ); + final double newQuality = qualFromRead + globalDeltaQ + deltaQReported + deltaQCovariates; + final byte newQualityByte = QualityUtils.boundQual( (int)Math.round(newQuality), QualityUtils.MAX_REASONABLE_Q_SCORE ); // Verbose printouts used to validate with old recalibrator @@ -406,7 +412,6 @@ public class TableRecalibrationWalker extends ReadWalker(); - private ArrayList> keyLists; // Used to output the mappings in sorted order + public final Map data = new HashMap(); public Object get( final Object... keys ) { Map map = this.data; for( int iii = 0; iii < keys.length; iii++ ) { if( iii == keys.length - 1 ) { return map.get(keys[iii]); - } - else { + } else { map = (Map) map.get(keys[iii]); if( map == null ) { return null; } } @@ -55,31 +53,11 @@ public class NestedHashMap{ public void put( final Object value, final Object... keys ) { - if( keyLists == null ) { - keyLists = new ArrayList>(); - for( Object obj : keys ) { - keyLists.add( new ArrayList() ); - } - } - - ArrayList thisList; - for( int iii = 0; iii < keys.length; iii++ ) { - thisList = keyLists.get( iii ); - if( thisList == null ) { - thisList = new ArrayList(); - } - if( !thisList.contains( (Comparable)keys[iii] ) ) { - thisList.add( (Comparable)keys[iii] ); - } - } - - Map map = this.data; for( int iii = 0; iii < keys.length; iii++ ) { if( iii == keys.length - 1 ) { map.put(keys[iii], value); - } - else { + } else { Map tmp = (Map) map.get(keys[iii]); if( tmp == null ) { tmp = new HashMap(); @@ -89,53 +67,4 @@ public class NestedHashMap{ } } } - - public ArrayList> entrySetSorted() { - - ArrayList> theSet = new ArrayList>(); - - for( ArrayList list : keyLists ) { - Collections.sort(list); - } - - int[] keyIndex = new int[ keyLists.size() ]; - int[] maxIndex = new int[ keyLists.size() ]; - for( int iii = 0; iii < keyLists.size(); iii++ ) { - keyIndex[iii] = 0; - maxIndex[iii] = keyLists.get(iii).size(); - } - - // Try all the possible keys in sorted order, add them to the output set if they are in the hashMap - // BUGBUG: make this more efficient - boolean triedAllKeys = false; - ArrayList newKey = null; - while( !triedAllKeys ) { - newKey = new ArrayList(); - for( int iii = 0; iii < keyLists.size(); iii++ ) { - newKey.add(keyLists.get(iii).get(keyIndex[iii])); - } - Object value = this.get( newKey.toArray() ); - if( value!= null ) { - theSet.add(new Pair( newKey.toArray(), value ) ); - } - - // Increment the keyIndex - keyIndex[keyLists.size() - 1]++; - for( int iii = keyLists.size() - 1; iii >= 0; iii-- ) { - if( keyIndex[iii] >= maxIndex[iii] ) { // Carry it forward - keyIndex[iii] = 0; - if( iii > 0 ) { - keyIndex[iii-1]++; - } else { - triedAllKeys = true; - break; - } - } else { - break; - } - } - } - return theSet; - } - }