enable phylogram if positive bl edited
[jalview.git] / forester / java / src / org / forester / surfacing / DomainSimilarity.java
1 // $Id:
2 //
3 // FORESTER -- software libraries and applications
4 // for evolutionary biology research and applications.
5 //
6 // Copyright (C) 2008-2009 Christian M. Zmasek
7 // Copyright (C) 2008-2009 Burnham Institute for Medical Research
8 // All rights reserved
9 //
10 // This library is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU Lesser General Public
12 // License as published by the Free Software Foundation; either
13 // version 2.1 of the License, or (at your option) any later version.
14 //
15 // This library is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 // Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public
21 // License along with this library; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 //
24 // Contact: phylosoft @ gmail . com
25 // WWW: https://sites.google.com/site/cmzmasek/home/software/forester
26
27 package org.forester.surfacing;
28
29 import java.awt.Color;
30 import java.util.Comparator;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Map.Entry;
36 import java.util.Set;
37 import java.util.SortedMap;
38 import java.util.SortedSet;
39 import java.util.TreeMap;
40 import java.util.TreeSet;
41
42 import org.forester.phylogeny.Phylogeny;
43 import org.forester.species.Species;
44 import org.forester.surfacing.DomainSimilarityCalculator.Detailedness;
45 import org.forester.util.ForesterUtil;
46
47 public class DomainSimilarity implements Comparable<DomainSimilarity> {
48
49     final public static String                              SPECIES_SEPARATOR          = "  ";
50     final private static int                                EQUAL                      = 0;
51     final private static String                             NO_SPECIES                 = "     ";
52     private static final boolean                            OUTPUT_TAXCODES_PER_DOMAIN = false;
53     final private CombinableDomains                         _combinable_domains;
54     private DomainSimilarityCalculator.Detailedness         _detailedness;
55     final private double                                    _max;
56     private final int                                       _max_difference;
57     private final int                                       _max_difference_in_counts;
58     final private double                                    _mean;
59     final private double                                    _min;
60     final private int                                       _n;
61     final private double                                    _sd;
62     final private SortedMap<Species, SpeciesSpecificDcData> _species_data;
63     private List<Species>                                   _species_order;
64     private final boolean                                   _treat_as_binary_comparison;
65
66     public DomainSimilarity( final CombinableDomains combinable_domains,
67                              final double min,
68                              final double max,
69                              final double mean,
70                              final double median,
71                              final double sd,
72                              final int n,
73                              final int max_difference_in_counts,
74                              final int max_difference,
75                              final SortedMap<Species, SpeciesSpecificDcData> species_data,
76                              final boolean sort_by_species_count_first,
77                              final boolean treat_as_binary_comparison ) {
78         if ( combinable_domains == null ) {
79             throw new IllegalArgumentException( "attempt to use null combinable domains" );
80         }
81         if ( species_data == null ) {
82             throw new IllegalArgumentException( "attempt to use null species data" );
83         }
84         if ( species_data.size() < 1 ) {
85             throw new IllegalArgumentException( "attempt to use empty species data" );
86         }
87         if ( n < 0 ) {
88             throw new IllegalArgumentException( "attempt to use N less than 0" );
89         }
90         if ( ( species_data.size() > 1 ) && ( n < 1 ) ) {
91             throw new IllegalArgumentException( "attempt to use N less than 1" );
92         }
93         if ( sd < 0.0 ) {
94             throw new IllegalArgumentException( "attempt to use negative SD" );
95         }
96         if ( max < min ) {
97             throw new IllegalArgumentException( "attempt to use max smaller than min" );
98         }
99         init();
100         _combinable_domains = combinable_domains;
101         _min = min;
102         _max = max;
103         _mean = mean;
104         _sd = sd;
105         _n = n;
106         _max_difference_in_counts = max_difference_in_counts;
107         _max_difference = max_difference;
108         _species_data = species_data;
109         _treat_as_binary_comparison = treat_as_binary_comparison;
110         final int s = species_data.size();
111         if ( ( ( s * s ) - s ) != ( getN() * 2 ) ) {
112             throw new IllegalArgumentException( "illegal species count and n: species count:" + s + ", n:" + _n
113                                                 + " for domain " + combinable_domains.getKeyDomain() );
114         }
115         if ( s > 2 ) {
116             if ( getMaximalDifferenceInCounts() < 0 ) {
117                 throw new IllegalArgumentException( "attempt to use negative max difference in counts with more than two species" );
118             }
119             if ( getMaximalDifference() < 0 ) {
120                 throw new IllegalArgumentException( "attempt to use negative max difference with more than two species" );
121             }
122         }
123     }
124
125     public DomainSimilarity( final CombinableDomains combinable_domains,
126                              final int max_difference_in_counts,
127                              final int max_difference,
128                              final SortedMap<Species, SpeciesSpecificDcData> species_data,
129                              final boolean sort_by_species_count_first,
130                              final boolean treat_as_binary_comparison ) {
131         if ( combinable_domains == null ) {
132             throw new IllegalArgumentException( "attempt to use null combinable domains" );
133         }
134         if ( species_data == null ) {
135             throw new IllegalArgumentException( "attempt to use null species data" );
136         }
137         if ( species_data.size() < 1 ) {
138             throw new IllegalArgumentException( "attempt to use empty species data" );
139         }
140         init();
141         _combinable_domains = combinable_domains;
142         _min = -1;
143         _max = -1;
144         _mean = -1;
145         _sd = -1;
146         _n = -1;
147         _max_difference_in_counts = max_difference_in_counts;
148         _max_difference = max_difference;
149         _species_data = species_data;
150         _treat_as_binary_comparison = treat_as_binary_comparison;
151         final int s = species_data.size();
152         if ( s > 2 ) {
153             if ( getMaximalDifferenceInCounts() < 0 ) {
154                 throw new IllegalArgumentException( "attempt to use negative max difference in counts with more than two species" );
155             }
156             if ( getMaximalDifference() < 0 ) {
157                 throw new IllegalArgumentException( "attempt to use negative max difference with more than two species" );
158             }
159         }
160     }
161
162     @Override
163     public int compareTo( final DomainSimilarity domain_similarity ) {
164         if ( this == domain_similarity ) {
165             return EQUAL;
166         }
167         else if ( domain_similarity == null ) {
168             throw new IllegalArgumentException( "attempt to compare " + this.getClass() + " to null" );
169         }
170         else if ( domain_similarity.getClass() != this.getClass() ) {
171             throw new IllegalArgumentException( "attempt to compare " + this.getClass() + " to "
172                     + domain_similarity.getClass() );
173         }
174         return compareByDomainId( domain_similarity );
175     }
176
177     public SortedSet<String> getCombinableDomainIds( final Species species_of_combinable_domain ) {
178         final SortedSet<String> sorted_ids = new TreeSet<String>();
179         if ( getSpeciesData().containsKey( species_of_combinable_domain ) ) {
180             for( final String id : getSpeciesData().get( species_of_combinable_domain )
181                     .getCombinableDomainIdToCountsMap().keySet() ) {
182                 sorted_ids.add( id );
183             }
184         }
185         return sorted_ids;
186     }
187
188     public String getDomainId() {
189         return getCombinableDomains().getKeyDomain();
190     }
191
192     /**
193      * For pairwise similarities, this should return the "difference"; for example the difference in counts
194      * for copy number based features (the same as getMaximalDifferenceInCounts(), or the number
195      * of actually different domain combinations.
196      * For pairwise similarities, this should return the difference,
197      * while for comparisons of more than two domains, this should return the maximal difference
198      *
199      */
200     public int getMaximalDifference() {
201         return _max_difference;
202     }
203
204     /**
205      * For pairwise similarities, this should return the difference in counts,
206      * while for comparisons of more than two domains, this should return the maximal difference
207      * in counts
208      *
209      *
210      * @return the (maximal) difference in counts
211      */
212     public int getMaximalDifferenceInCounts() {
213         return _max_difference_in_counts;
214     }
215
216     public double getMaximalSimilarityScore() {
217         return _max;
218     }
219
220     public double getMeanSimilarityScore() {
221         return _mean;
222     }
223
224     public double getMinimalSimilarityScore() {
225         return _min;
226     }
227
228     /**
229      * This should return the number of pairwise distances used to calculate
230      * this similarity score
231      *
232      * @return the number of pairwise distances
233      */
234     public int getN() {
235         return _n;
236     }
237
238     public SortedSet<Species> getSpecies() {
239         final SortedSet<Species> species = new TreeSet<Species>();
240         for( final Species s : getSpeciesData().keySet() ) {
241             species.add( s );
242         }
243         return species;
244     }
245
246     public List<Species> getSpeciesCustomOrder() {
247         return _species_order;
248     }
249
250     /**
251      * This should return a map, which maps species names to
252      * SpeciesSpecificDomainSimilariyData
253      *
254      *
255      * @return SortedMap<String, SpeciesSpecificDomainSimilariyData>
256      */
257     public SortedMap<Species, SpeciesSpecificDcData> getSpeciesData() {
258         return _species_data;
259     }
260
261     public double getStandardDeviationOfSimilarityScore() {
262         return _sd;
263     }
264
265     public void setDetailedness( final Detailedness detailedness ) {
266         _detailedness = detailedness;
267     }
268
269     public void setSpeciesOrder( final List<Species> species_order ) {
270         if ( !species_order.containsAll( getSpeciesData().keySet() ) ) {
271             throw new IllegalArgumentException( "list to order species must contain all species of multiple combinable domains similarity" );
272         }
273         _species_order = species_order;
274     }
275
276     public StringBuffer toStringBuffer( final DomainSimilarity.PRINT_OPTION print_option,
277                                         final Map<String, Integer> tax_code_to_id_map,
278                                         final Phylogeny phy ) {
279         switch ( print_option ) {
280             case SIMPLE_TAB_DELIMITED:
281                 return toStringBufferSimpleTabDelimited();
282             case HTML:
283                 return toStringBufferDetailedHTML( tax_code_to_id_map, phy, OUTPUT_TAXCODES_PER_DOMAIN );
284             default:
285                 throw new AssertionError( "Unknown print option: " + print_option );
286         }
287     }
288
289     private void addSpeciesSpecificDomainData( final StringBuffer sb,
290                                                final Species species,
291                                                final boolean html,
292                                                final Map<String, Integer> tax_code_to_id_map,
293                                                final Phylogeny phy ) {
294         if ( html ) {
295             sb.append( "<tr>" );
296             sb.append( "<td>" );
297             addTaxWithLink( sb, species.getSpeciesId(), tax_code_to_id_map, phy );
298             sb.append( "</td>" );
299         }
300         else {
301             sb.append( species.getSpeciesId() );
302         }
303         if ( getDetaildness() != DomainSimilarityCalculator.Detailedness.BASIC ) {
304             if ( html ) {
305                 //sb.append( ":" );
306             }
307             else {
308                 sb.append( "\t" );
309             }
310             sb.append( getSpeciesData().get( species ).toStringBuffer( getDetaildness(), html ) );
311         }
312         if ( html ) {
313             //sb.append( "<br>" );
314             sb.append( "</tr>" );
315         }
316         else {
317             sb.append( "\n\t" );
318         }
319     }
320
321     private void addTaxWithLink( final StringBuffer sb,
322                                  final String tax_code,
323                                  final Map<String, Integer> tax_code_to_id_map,
324                                  final Phylogeny phy ) {
325         String hex = null;
326         if ( ( phy != null ) && !phy.isEmpty() ) {
327             hex = SurfacingUtil.obtainHexColorStringDependingOnTaxonomyGroup( tax_code, phy );
328         }
329         sb.append( "<b>" );
330         if ( !ForesterUtil.isEmpty( tax_code )
331                 && ( ( tax_code_to_id_map != null ) && tax_code_to_id_map.containsKey( tax_code ) ) ) {
332             if ( !ForesterUtil.isEmpty( hex ) ) {
333                 sb.append( "<a href=\"" );
334                 sb.append( SurfacingConstants.UNIPROT_TAXONOMY_ID_LINK );
335                 sb.append( tax_code_to_id_map.get( tax_code ) );
336                 sb.append( "\" target=\"tw\"><span style=\"color:" );
337                 sb.append( hex );
338                 sb.append( "\">" );
339                 sb.append( tax_code );
340                 sb.append( "</span></a>" );
341             }
342             else {
343                 sb.append( "<a href=\"" );
344                 sb.append( SurfacingConstants.UNIPROT_TAXONOMY_ID_LINK );
345                 sb.append( tax_code_to_id_map.get( tax_code ) );
346                 sb.append( "\" target=\"tw\">" );
347                 sb.append( tax_code );
348                 sb.append( "</a>" );
349             }
350         }
351         else {
352             sb.append( tax_code );
353         }
354         sb.append( "</b>" );
355     }
356
357     private int compareByDomainId( final DomainSimilarity other ) {
358         return getDomainId().compareToIgnoreCase( other.getDomainId() );
359     }
360
361     private CombinableDomains getCombinableDomains() {
362         return _combinable_domains;
363     }
364
365     private DomainSimilarityCalculator.Detailedness getDetaildness() {
366         return _detailedness;
367     }
368
369     private StringBuffer getDomainDataInAlphabeticalOrder() {
370         final SortedMap<String, SortedSet<String>> m = new TreeMap<String, SortedSet<String>>();
371         final StringBuffer sb = new StringBuffer();
372         for( final Species species : getSpeciesData().keySet() ) {
373             for( final String combable_dom : getCombinableDomainIds( species ) ) {
374                 if ( !m.containsKey( combable_dom ) ) {
375                     m.put( combable_dom, new TreeSet<String>() );
376                 }
377                 m.get( combable_dom ).add( species.getSpeciesId() );
378             }
379         }
380         for( final Map.Entry<String, SortedSet<String>> e : m.entrySet() ) {
381             sb.append( "<a href=\"" + SurfacingConstants.PFAM_FAMILY_ID_LINK + e.getKey() + "\">" + e.getKey() + "</a>" );
382             sb.append( " " );
383             sb.append( "<span style=\"font-size:7px\">" );
384             for( final String tax : e.getValue() ) {
385                 final String hex = SurfacingUtil.obtainHexColorStringDependingOnTaxonomyGroup( tax, null );
386                 if ( !ForesterUtil.isEmpty( hex ) ) {
387                     sb.append( "<span style=\"color:" );
388                     sb.append( hex );
389                     sb.append( "\">" );
390                     sb.append( tax );
391                     sb.append( "</span>" );
392                 }
393                 else {
394                     sb.append( tax );
395                 }
396                 sb.append( " " );
397             }
398             sb.append( "</span>" );
399             sb.append( "<br>\n" );
400         }
401         return sb;
402     }
403
404     private StringBuffer getSpeciesDataInAlphabeticalOrder( final boolean html,
405                                                             final Map<String, Integer> tax_code_to_id_map,
406                                                             final Phylogeny phy ) {
407         final StringBuffer sb = new StringBuffer();
408         sb.append( "<table>" );
409         for( final Species species : getSpeciesData().keySet() ) {
410             addSpeciesSpecificDomainData( sb, species, html, tax_code_to_id_map, phy );
411         }
412         sb.append( "</table>" );
413         return sb;
414     }
415
416     private StringBuffer getSpeciesDataInCustomOrder( final boolean html,
417                                                       final Map<String, Integer> tax_code_to_id_map,
418                                                       final Phylogeny phy ) {
419         final StringBuffer sb = new StringBuffer();
420         for( final Species order_species : getSpeciesCustomOrder() ) {
421             if ( getSpeciesData().keySet().contains( order_species ) ) {
422                 addSpeciesSpecificDomainData( sb, order_species, html, tax_code_to_id_map, phy );
423             }
424             else {
425                 sb.append( DomainSimilarity.NO_SPECIES );
426                 sb.append( DomainSimilarity.SPECIES_SEPARATOR );
427             }
428         }
429         return sb;
430     }
431
432     private StringBuffer getTaxonomyGroupDistribution( final Phylogeny tol ) {
433         final SortedMap<String, Set<String>> domain_to_species_set_map = new TreeMap<String, Set<String>>();
434         for( final Species species : getSpeciesData().keySet() ) {
435             for( final String combable_dom : getCombinableDomainIds( species ) ) {
436                 if ( !domain_to_species_set_map.containsKey( combable_dom ) ) {
437                     domain_to_species_set_map.put( combable_dom, new HashSet<String>() );
438                 }
439                 domain_to_species_set_map.get( combable_dom ).add( species.getSpeciesId() );
440             }
441         }
442         final StringBuffer sb = new StringBuffer();
443         sb.append( "<table>" );
444         for( final Map.Entry<String, Set<String>> domain_to_species_set : domain_to_species_set_map.entrySet() ) {
445             final Map<String, Integer> counts = new HashMap<String, Integer>();
446             for( final String tax_code : domain_to_species_set.getValue() ) {
447                 final String group = SurfacingUtil.obtainTaxonomyGroup( tax_code, tol );
448                 if ( !ForesterUtil.isEmpty( group ) ) {
449                     if ( !counts.containsKey( group ) ) {
450                         counts.put( group, 1 );
451                     }
452                     else {
453                         counts.put( group, counts.get( group ) + 1 );
454                     }
455                 }
456                 else {
457                     return null;
458                 }
459             }
460             final SortedMap<Integer, SortedSet<String>> counts_to_groups = new TreeMap<Integer, SortedSet<String>>( new Comparator<Integer>() {
461
462                 @Override
463                 public int compare( final Integer first, final Integer second ) {
464                     return second.compareTo( first );
465                 }
466             } );
467             for( final Map.Entry<String, Integer> group_to_counts : counts.entrySet() ) {
468                 final int c = group_to_counts.getValue();
469                 if ( !counts_to_groups.containsKey( c ) ) {
470                     counts_to_groups.put( c, new TreeSet<String>() );
471                 }
472                 counts_to_groups.get( c ).add( group_to_counts.getKey() );
473             }
474             sb.append( "<tr>" );
475             sb.append( "<td>" );
476             sb.append( "<a href=\"" + SurfacingConstants.PFAM_FAMILY_ID_LINK + domain_to_species_set.getKey() + "\">"
477                     + domain_to_species_set.getKey() + "</a>" );
478             sb.append( " " );
479             sb.append( "</td>" );
480             boolean first = true;
481             for( final Entry<Integer, SortedSet<String>> count_to_groups : counts_to_groups.entrySet() ) {
482                 if ( first ) {
483                     first = false;
484                 }
485                 else {
486                     sb.append( "<tr>" );
487                     sb.append( "<td>" );
488                     sb.append( "</td>" );
489                 }
490                 sb.append( "<td>" );
491                 final SortedSet<String> groups = count_to_groups.getValue();
492                 sb.append( count_to_groups.getKey() );
493                 sb.append( " " );
494                 for( final String group : groups ) {
495                     final Color color = ForesterUtil.obtainColorDependingOnTaxonomyGroup( group );
496                     if ( color == null ) {
497                         throw new IllegalArgumentException( "no color found for taxonomy group\"" + group + "\"" );
498                     }
499                     final String hex = String.format( "#%02x%02x%02x",
500                                                       color.getRed(),
501                                                       color.getGreen(),
502                                                       color.getBlue() );
503                     sb.append( "<span style=\"color:" );
504                     sb.append( hex );
505                     sb.append( "\">" );
506                     sb.append( " " );
507                     sb.append( group );
508                     sb.append( "</span>" );
509                 }
510                 sb.append( "</td>" );
511                 sb.append( "</tr>" );
512             }
513             sb.append( ForesterUtil.getLineSeparator() );
514         }
515         sb.append( "</table>" );
516         return sb;
517     }
518
519     private void init() {
520         _detailedness = DomainSimilarityCalculator.Detailedness.PUNCTILIOUS;
521     }
522
523     private boolean isTreatAsBinaryComparison() {
524         return _treat_as_binary_comparison;
525     }
526
527     private StringBuffer toStringBufferDetailedHTML( final Map<String, Integer> tax_code_to_id_map,
528                                                      final Phylogeny phy,
529                                                      final boolean output_tax_codes_per_domain ) {
530         final StringBuffer sb = new StringBuffer();
531         sb.append( "<tr>" );
532         sb.append( "<td>" );
533         sb.append( "<b>" );
534         sb.append( "<a href=\"" + SurfacingConstants.PFAM_FAMILY_ID_LINK + getDomainId() + "\" target=\"pfam_window\">"
535                 + getDomainId() + "</a>" );
536         sb.append( "</b>" );
537         sb.append( "<a name=\"" + getDomainId() + "\">" );
538         sb.append( "</td>" );
539         sb.append( "<td>" );
540         sb.append( "<a href=\"" + SurfacingConstants.GOOGLE_SCHOLAR_SEARCH + getDomainId()
541                    + "\" target=\"gs_window\">gs</a>" );
542         sb.append( "</td>" );
543         if ( getMaximalSimilarityScore() > 0 ) {
544             sb.append( "<td>" );
545             sb.append( ForesterUtil.round( getMeanSimilarityScore(), 3 ) );
546             sb.append( "</td>" );
547             if ( SurfacingConstants.PRINT_MORE_DOM_SIMILARITY_INFO ) {
548                 if ( !isTreatAsBinaryComparison() ) {
549                     sb.append( "<td>" );
550                     sb.append( "(" );
551                     sb.append( ForesterUtil.round( getStandardDeviationOfSimilarityScore(), 3 ) );
552                     sb.append( ")" );
553                     sb.append( "</td>" );
554                     sb.append( "<td>" );
555                     sb.append( "[" );
556                     sb.append( ForesterUtil.round( getMinimalSimilarityScore(), 3 ) );
557                     sb.append( "-" );
558                     sb.append( ForesterUtil.round( getMaximalSimilarityScore(), 3 ) );
559                     sb.append( "]" );
560                     sb.append( "</td>" );
561                 }
562             }
563         }
564         sb.append( "<td>" );
565         sb.append( getMaximalDifference() );
566         sb.append( "</td>" );
567         sb.append( "<td>" );
568         if ( isTreatAsBinaryComparison() ) {
569             sb.append( getMaximalDifferenceInCounts() );
570         }
571         else {
572             sb.append( Math.abs( getMaximalDifferenceInCounts() ) );
573         }
574         sb.append( "</td>" );
575         if ( !isTreatAsBinaryComparison() ) {
576             sb.append( "<td>" );
577             sb.append( "<b>" );
578             sb.append( getSpeciesData().size() );
579             sb.append( "</b>" );
580             sb.append( "</td>" );
581         }
582         if ( ( getSpeciesCustomOrder() == null ) || getSpeciesCustomOrder().isEmpty() ) {
583             sb.append( "<td>" );
584             sb.append( getSpeciesDataInAlphabeticalOrder( true, tax_code_to_id_map, phy ) );
585             if ( output_tax_codes_per_domain ) {
586                 sb.append( getDomainDataInAlphabeticalOrder() );
587             }
588             sb.append( getTaxonomyGroupDistribution( phy ) );
589             sb.append( "</td>" );
590         }
591         else {
592             sb.append( "<td>" );
593             sb.append( getSpeciesDataInCustomOrder( true, tax_code_to_id_map, phy ) );
594             if ( output_tax_codes_per_domain ) {
595                 sb.append( getDomainDataInAlphabeticalOrder() );
596             }
597             sb.append( getTaxonomyGroupDistribution( phy ) );
598             sb.append( "</td>" );
599         }
600         sb.append( "</tr>" );
601         return sb;
602     }
603
604     private StringBuffer toStringBufferSimpleTabDelimited() {
605         final StringBuffer sb = new StringBuffer();
606         sb.append( getDomainId() );
607         sb.append( "\t" );
608         sb.append( getSpeciesDataInAlphabeticalOrder( false, null, null ) );
609         sb.append( "\n" );
610         return sb;
611     }
612
613     static public enum DomainSimilarityScoring {
614         COMBINATIONS, DOMAINS, PROTEINS;
615     }
616
617     public static enum DomainSimilaritySortField {
618         ABS_MAX_COUNTS_DIFFERENCE, DOMAIN_ID, MAX, MAX_COUNTS_DIFFERENCE, MAX_DIFFERENCE, MEAN, MIN, SD, SPECIES_COUNT,
619     }
620
621     public static enum PRINT_OPTION {
622         HTML, SIMPLE_TAB_DELIMITED;
623     }
624
625     class ValueComparator implements Comparator<String> {
626
627         final private Map<String, Integer> _base;
628
629         public ValueComparator( final Map<String, Integer> base ) {
630             _base = base;
631         }
632
633         @Override
634         public int compare( final String a, final String b ) {
635             if ( _base.get( a ) >= _base.get( b ) ) {
636                 return -1;
637             }
638             else {
639                 return 1;
640             } // returning 0 would merge keys
641         }
642     }
643 }