0f12eb833a6973565eae6804d73d3c9fcc9115d0
[jalview.git] / src / jalview / util / Format.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 /**
22  * A class for formatting numbers that follows printf conventions.
23  * Also implements C-like atoi and atof functions
24  * @version 1.03 25 Oct 1997
25  * @author Cay Horstmann
26  */
27 package jalview.util;
28
29 import java.util.Arrays;
30
31 /**
32  * A utility class that provides a variety of formatting methods.
33  * 
34  * Principle method {@code format} formats the number following printf
35  * conventions. Main limitation: Can only handle one format parameter at a time
36  * Use multiple Format objects to format more than one number
37  * 
38  * @author unknown
39  */
40 public class Format
41 {
42   private int width;
43
44   private int precision;
45
46   private String pre;
47
48   private String post;
49
50   private boolean leading_zeroes;
51
52   private boolean show_plus;
53
54   private boolean alternate;
55
56   private boolean show_space;
57
58   private boolean left_align;
59
60   private char fmt; // one of cdeEfgGiosxXos
61
62   private final String formatString;
63
64   /**
65    * Creates a new Format object given a format descriptor, which follows printf
66    * conventions The string has a prefix, a format code and a suffix. The prefix
67    * and suffix become part of the formatted output. The format code directs the
68    * formatting of the (single) parameter to be formatted. The code has the
69    * following structure
70    * <ul>
71    * <li>a % (required)
72    * <li>a modifier (optional)
73    * <dl>
74    * <dt>+
75    * <dd>forces display of + for positive numbers
76    * <dt>0
77    * <dd>show leading zeroes
78    * <dt>-
79    * <dd>align left in the field
80    * <dt>space
81    * <dd>prepend a space in front of positive numbers
82    * <dt>#
83    * <dd>use "alternate" format. Add 0 or 0x for octal or hexadecimal numbers.
84    * Don't suppress trailing zeroes in general floating point format.
85    * </dl>
86    * <li>an integer denoting field width (optional)
87    * <li>a period followed by an integer denoting precision (optional)
88    * <li>a format descriptor (required)
89    * <dl>
90    * <dt>f
91    * <dd>floating point number in fixed format
92    * <dt>e, E
93    * <dd>floating point number in exponential notation (scientific format). The
94    * E format results in an uppercase E for the exponent (1.14130E+003), the e
95    * format in a lowercase e.
96    * <dt>g, G
97    * <dd>floating point number in general format (fixed format for small
98    * numbers, exponential format for large numbers). Trailing zeroes are
99    * suppressed. The G format results in an uppercase E for the exponent (if
100    * any), the g format in a lowercase e.
101    * <dt>d, i
102    * <dd>integer in decimal
103    * <dt>x
104    * <dd>integer in hexadecimal
105    * <dt>o
106    * <dd>integer in octal
107    * <dt>s
108    * <dd>string
109    * <dt>c
110    * <dd>character
111    * </dl>
112    * </ul>
113    * 
114    * @param s
115    *          the format descriptor
116    */
117   public Format(final String s)
118   {
119     formatString = s;
120     width = 0;
121     precision = -1;
122     pre = "";
123     post = "";
124     leading_zeroes = false;
125     show_plus = false;
126     alternate = false;
127     show_space = false;
128     left_align = false;
129     fmt = ' ';
130
131     int length = s.length();
132     int parse_state = 0;
133
134     // 0 = prefix, 1 = flags, 2 = width, 3 = precision,
135     // 4 = format, 5 = end
136     int i = 0;
137
138     while (parse_state == 0)
139     {
140       if (i >= length)
141       {
142         parse_state = 5;
143       }
144       else if (s.charAt(i) == '%')
145       {
146         if (i < (length - 1))
147         {
148           if (s.charAt(i + 1) == '%')
149           {
150             pre = pre + '%';
151             i++;
152           }
153           else
154           {
155             parse_state = 1;
156           }
157         }
158         else
159         {
160           throw new java.lang.IllegalArgumentException();
161         }
162       }
163       else
164       {
165         pre = pre + s.charAt(i);
166       }
167
168       i++;
169     }
170
171     while (parse_state == 1)
172     {
173       if (i >= length)
174       {
175         parse_state = 5;
176       }
177       else if (s.charAt(i) == ' ')
178       {
179         show_space = true;
180       }
181       else if (s.charAt(i) == '-')
182       {
183         left_align = true;
184       }
185       else if (s.charAt(i) == '+')
186       {
187         show_plus = true;
188       }
189       else if (s.charAt(i) == '0')
190       {
191         leading_zeroes = true;
192       }
193       else if (s.charAt(i) == '#')
194       {
195         alternate = true;
196       }
197       else
198       {
199         parse_state = 2;
200         i--;
201       }
202
203       i++;
204     }
205
206     while (parse_state == 2)
207     {
208       if (i >= length)
209       {
210         parse_state = 5;
211       }
212       else if (('0' <= s.charAt(i)) && (s.charAt(i) <= '9'))
213       {
214         width = ((width * 10) + s.charAt(i)) - '0';
215         i++;
216       }
217       else if (s.charAt(i) == '.')
218       {
219         parse_state = 3;
220         precision = 0;
221         i++;
222       }
223       else
224       {
225         parse_state = 4;
226       }
227     }
228
229     while (parse_state == 3)
230     {
231       if (i >= length)
232       {
233         parse_state = 5;
234       }
235       else if (('0' <= s.charAt(i)) && (s.charAt(i) <= '9'))
236       {
237         precision = ((precision * 10) + s.charAt(i)) - '0';
238         i++;
239       }
240       else
241       {
242         parse_state = 4;
243       }
244     }
245
246     if (parse_state == 4)
247     {
248       if (i >= length)
249       {
250         parse_state = 5;
251       }
252       else
253       {
254         fmt = s.charAt(i);
255       }
256
257       i++;
258     }
259
260     if (i < length)
261     {
262       post = s.substring(i, length);
263     }
264   }
265
266   /**
267    * Returns a 6 character string consisting of the hex values of the colour's
268    * rgb values (left padded with zeroes if necessary)
269    * 
270    * @param color
271    * @return
272    */
273   public static String getHexString(java.awt.Color color)
274   {
275     String r;
276     String g;
277     String b;
278     r = Integer.toHexString(color.getRed());
279
280     if (r.length() < 2)
281     {
282       r = "0" + r;
283     }
284
285     g = Integer.toHexString(color.getGreen());
286
287     if (g.length() < 2)
288     {
289       g = "0" + g;
290     }
291
292     b = Integer.toHexString(color.getBlue());
293
294     if (b.length() < 2)
295     {
296       b = "0" + b;
297     }
298
299     return r + g + b;
300   }
301
302   /**
303    * prints a formatted number following printf conventions
304    * 
305    * @param s
306    *          a PrintStream
307    * @param fmt
308    *          the format string
309    * @param x
310    *          the double to print
311    */
312   public static void print(java.io.PrintStream s, String fmt, double x)
313   {
314     s.print(new Format(fmt).form(x));
315   }
316
317   /**
318    * prints a formatted number following printf conventions
319    * 
320    * @param s
321    *          a PrintStream
322    * @param fmt
323    *          the format string
324    * @param x
325    *          the long to print
326    */
327   public static void print(java.io.PrintStream s, String fmt, long x)
328   {
329     s.print(new Format(fmt).form(x));
330   }
331
332   /**
333    * prints a formatted number following printf conventions
334    * 
335    * @param s
336    *          a PrintStream
337    * @param fmt
338    *          the format string
339    * @param x
340    *          the character to
341    */
342   public static void print(java.io.PrintStream s, String fmt, char x)
343   {
344     s.print(new Format(fmt).form(x));
345   }
346
347   /**
348    * prints a formatted number following printf conventions
349    * 
350    * @param s
351    *          a PrintStream, fmt the format string
352    * @param x
353    *          a string that represents the digits to print
354    */
355   public static void print(java.io.PrintStream s, String fmt, String x)
356   {
357     s.print(new Format(fmt).form(x));
358   }
359
360   /**
361    * Converts a string of digits (decimal, octal or hex) to an integer
362    * 
363    * @param s
364    *          a string
365    * @return the numeric value of the prefix of s representing a base 10 integer
366    */
367   public static int atoi(String s)
368   {
369     return (int) atol(s);
370   }
371
372   /**
373    * Converts a string of digits (decimal, octal or hex) to a long integer
374    * 
375    * @param s
376    *          a string
377    * @return the numeric value of the prefix of s representing a base 10 integer
378    */
379   public static long atol(String s)
380   {
381     int i = 0;
382
383     while ((i < s.length()) && Character.isWhitespace(s.charAt(i)))
384     {
385       i++;
386     }
387
388     if ((i < s.length()) && (s.charAt(i) == '0'))
389     {
390       if (((i + 1) < s.length())
391               && ((s.charAt(i + 1) == 'x') || (s.charAt(i + 1) == 'X')))
392       {
393         return parseLong(s.substring(i + 2), 16);
394       }
395       else
396       {
397         return parseLong(s, 8);
398       }
399     }
400     else
401     {
402       return parseLong(s, 10);
403     }
404   }
405
406   /**
407    * DOCUMENT ME!
408    * 
409    * @param s
410    *          DOCUMENT ME!
411    * @param base
412    *          DOCUMENT ME!
413    * 
414    * @return DOCUMENT ME!
415    */
416   private static long parseLong(String s, int base)
417   {
418     int i = 0;
419     int sign = 1;
420     long r = 0;
421
422     while ((i < s.length()) && Character.isWhitespace(s.charAt(i)))
423     {
424       i++;
425     }
426
427     if ((i < s.length()) && (s.charAt(i) == '-'))
428     {
429       sign = -1;
430       i++;
431     }
432     else if ((i < s.length()) && (s.charAt(i) == '+'))
433     {
434       i++;
435     }
436
437     while (i < s.length())
438     {
439       char ch = s.charAt(i);
440
441       if (('0' <= ch) && (ch < ('0' + base)))
442       {
443         r = ((r * base) + ch) - '0';
444       }
445       else if (('A' <= ch) && (ch < (('A' + base) - 10)))
446       {
447         r = ((r * base) + ch) - 'A' + 10;
448       }
449       else if (('a' <= ch) && (ch < (('a' + base) - 10)))
450       {
451         r = ((r * base) + ch) - 'a' + 10;
452       }
453       else
454       {
455         return r * sign;
456       }
457
458       i++;
459     }
460
461     return r * sign;
462   }
463
464   /**
465    * Converts a string of digits to an double
466    * 
467    * @param s
468    *          a string
469    */
470   public static double atof(String s)
471   {
472     int i = 0;
473     int sign = 1;
474     double r = 0; // integer part
475     // double f = 0; // fractional part
476     double p = 1; // exponent of fractional part
477     int state = 0; // 0 = int part, 1 = frac part
478
479     while ((i < s.length()) && Character.isWhitespace(s.charAt(i)))
480     {
481       i++;
482     }
483
484     if ((i < s.length()) && (s.charAt(i) == '-'))
485     {
486       sign = -1;
487       i++;
488     }
489     else if ((i < s.length()) && (s.charAt(i) == '+'))
490     {
491       i++;
492     }
493
494     while (i < s.length())
495     {
496       char ch = s.charAt(i);
497
498       if (('0' <= ch) && (ch <= '9'))
499       {
500         if (state == 0)
501         {
502           r = ((r * 10) + ch) - '0';
503         }
504         else if (state == 1)
505         {
506           p = p / 10;
507           r = r + (p * (ch - '0'));
508         }
509       }
510       else if (ch == '.')
511       {
512         if (state == 0)
513         {
514           state = 1;
515         }
516         else
517         {
518           return sign * r;
519         }
520       }
521       else if ((ch == 'e') || (ch == 'E'))
522       {
523         long e = (int) parseLong(s.substring(i + 1), 10);
524
525         return sign * r * Math.pow(10, e);
526       }
527       else
528       {
529         return sign * r;
530       }
531
532       i++;
533     }
534
535     return sign * r;
536   }
537
538   /**
539    * Formats a double into a string (like sprintf in C)
540    * 
541    * @param x
542    *          the number to format
543    * @return the formatted string
544    * @exception IllegalArgumentException
545    *              if bad argument
546    */
547   public String form(double x)
548   {
549     String r;
550
551     if (precision < 0)
552     {
553       precision = 6;
554     }
555
556     int s = 1;
557
558     if (x < 0)
559     {
560       x = -x;
561       s = -1;
562     }
563
564     if (fmt == 'f')
565     {
566       r = fixed_format(x);
567     }
568     else if ((fmt == 'e') || (fmt == 'E') || (fmt == 'g') || (fmt == 'G'))
569     {
570       r = exp_format(x);
571     }
572     else
573     {
574       throw new java.lang.IllegalArgumentException();
575     }
576
577     return pad(sign(s, r));
578   }
579
580   /**
581    * Formats a long integer into a string (like sprintf in C)
582    * 
583    * @param x
584    *          the number to format
585    * @return the formatted string
586    */
587   public String form(long x)
588   {
589     String r;
590     int s = 0;
591
592     if ((fmt == 'd') || (fmt == 'i'))
593     {
594       if (x < 0)
595       {
596         r = ("" + x).substring(1);
597         s = -1;
598       }
599       else
600       {
601         r = "" + x;
602         s = 1;
603       }
604     }
605     else if (fmt == 'o')
606     {
607       r = convert(x, 3, 7, "01234567");
608     }
609     else if (fmt == 'x')
610     {
611       r = convert(x, 4, 15, "0123456789abcdef");
612     }
613     else if (fmt == 'X')
614     {
615       r = convert(x, 4, 15, "0123456789ABCDEF");
616     }
617     else
618     {
619       throw new java.lang.IllegalArgumentException();
620     }
621
622     return pad(sign(s, r));
623   }
624
625   /**
626    * Formats a character into a string (like sprintf in C)
627    * 
628    * @param debounceTrap
629    *          the value to format
630    * @return the formatted string
631    */
632   public String form(char c)
633   {
634     if (fmt != 'c')
635     {
636       throw new java.lang.IllegalArgumentException();
637     }
638
639     String r = "" + c;
640
641     return pad(r);
642   }
643
644   /**
645    * Formats a string into a larger string (like sprintf in C)
646    * 
647    * @param debounceTrap
648    *          the value to format
649    * @return the formatted string
650    */
651   public String form(String s)
652   {
653     if (fmt != 's')
654     {
655       throw new java.lang.IllegalArgumentException();
656     }
657
658     if (precision >= 0)
659     {
660       s = s.substring(0, precision);
661     }
662
663     return pad(s);
664   }
665
666   /**
667    * Returns a string consisting of n repeats of character c
668    * 
669    * @param c
670    * @param n
671    * 
672    * @return
673    */
674   static String repeat(char c, int n)
675   {
676     if (n <= 0)
677     {
678       return "";
679     }
680     char[] chars = new char[n];
681     Arrays.fill(chars, c);
682     return new String(chars);
683   }
684
685   /**
686    * DOCUMENT ME!
687    * 
688    * @param x
689    *          DOCUMENT ME!
690    * @param n
691    *          DOCUMENT ME!
692    * @param m
693    *          DOCUMENT ME!
694    * @param d
695    *          DOCUMENT ME!
696    * 
697    * @return DOCUMENT ME!
698    */
699   private static String convert(long x, int n, int m, String d)
700   {
701     if (x == 0)
702     {
703       return "0";
704     }
705
706     String r = "";
707
708     while (x != 0)
709     {
710       r = d.charAt((int) (x & m)) + r;
711       x = x >>> n;
712     }
713
714     return r;
715   }
716
717   /**
718    * DOCUMENT ME!
719    * 
720    * @param r
721    *          DOCUMENT ME!
722    * 
723    * @return DOCUMENT ME!
724    */
725   private String pad(String r)
726   {
727     String p = repeat(' ', width - r.length());
728
729     if (left_align)
730     {
731       return pre + r + p + post;
732     }
733     else
734     {
735       return pre + p + r + post;
736     }
737   }
738
739   /**
740    * DOCUMENT ME!
741    * 
742    * @param s
743    *          DOCUMENT ME!
744    * @param r
745    *          DOCUMENT ME!
746    * 
747    * @return DOCUMENT ME!
748    */
749   private String sign(int s, String r)
750   {
751     String p = "";
752
753     if (s < 0)
754     {
755       p = "-";
756     }
757     else if (s > 0)
758     {
759       if (show_plus)
760       {
761         p = "+";
762       }
763       else if (show_space)
764       {
765         p = " ";
766       }
767     }
768     else
769     {
770       if ((fmt == 'o') && alternate && (r.length() > 0)
771               && (r.charAt(0) != '0'))
772       {
773         p = "0";
774       }
775       else if ((fmt == 'x') && alternate)
776       {
777         p = "0x";
778       }
779       else if ((fmt == 'X') && alternate)
780       {
781         p = "0X";
782       }
783     }
784
785     int w = 0;
786
787     if (leading_zeroes)
788     {
789       w = width;
790     }
791     else if (((fmt == 'd') || (fmt == 'i') || (fmt == 'x') || (fmt == 'X')
792             || (fmt == 'o')) && (precision > 0))
793     {
794       w = precision;
795     }
796
797     return p + repeat('0', w - p.length() - r.length()) + r;
798   }
799
800   /**
801    * DOCUMENT ME!
802    * 
803    * @param d
804    *          DOCUMENT ME!
805    * 
806    * @return DOCUMENT ME!
807    */
808   private String fixed_format(double d)
809   {
810     boolean removeTrailing = ((fmt == 'G') || (fmt == 'g')) && !alternate;
811
812     // remove trailing zeroes and decimal point
813     if (d > 0x7FFFFFFFFFFFFFFFL)
814     {
815       return exp_format(d);
816     }
817
818     if (precision == 0)
819     {
820       return (long) (d + 0.5) + (removeTrailing ? "" : ".");
821     }
822
823     long whole = (long) d;
824     double fr = d - whole; // fractional part
825
826     if ((fr >= 1) || (fr < 0))
827     {
828       return exp_format(d);
829     }
830
831     double factor = 1;
832     String leading_zeroes = "";
833
834     for (int i = 1; (i <= precision)
835             && (factor <= 0x7FFFFFFFFFFFFFFFL); i++)
836     {
837       factor *= 10;
838       leading_zeroes = leading_zeroes + "0";
839     }
840
841     long l = (long) ((factor * fr) + 0.5);
842
843     if (l >= factor)
844     {
845       l = 0;
846       whole++;
847     }
848
849     // CSH 10-25-97
850     String z = leading_zeroes + l;
851     z = "." + z.substring(z.length() - precision, z.length());
852
853     if (removeTrailing)
854     {
855       int t = z.length() - 1;
856
857       while ((t >= 0) && (z.charAt(t) == '0'))
858       {
859         t--;
860       }
861
862       if ((t >= 0) && (z.charAt(t) == '.'))
863       {
864         t--;
865       }
866
867       z = z.substring(0, t + 1);
868     }
869
870     return whole + z;
871   }
872
873   /**
874    * DOCUMENT ME!
875    * 
876    * @param d
877    *          DOCUMENT ME!
878    * 
879    * @return DOCUMENT ME!
880    */
881   private String exp_format(double d)
882   {
883     String f = "";
884     int e = 0;
885     double dd = d;
886
887     if (d != 0)
888     {
889       while (dd > 10)
890       {
891         e++;
892         dd = dd / 10;
893       }
894
895       while (dd < 1)
896       {
897         e--;
898         dd = dd * 10;
899       }
900     }
901
902     if (((fmt == 'g') || (fmt == 'G')) && (e >= -4) && (e < precision))
903     {
904       return fixed_format(d);
905     }
906
907     f = f + fixed_format(dd);
908
909     if ((fmt == 'e') || (fmt == 'g'))
910     {
911       f = f + "e";
912     }
913     else
914     {
915       f = f + "E";
916     }
917
918     String p = "000";
919
920     if (e >= 0)
921     {
922       f = f + "+";
923       p = p + e;
924     }
925     else
926     {
927       f = f + "-";
928       p = p + (-e);
929     }
930
931     return f + p.substring(p.length() - 3, p.length());
932   }
933
934   @Override
935   public String toString()
936   {
937     return formatString;
938   }
939
940   /**
941    * Bespoke method to format percentage float value to the specified number of
942    * decimal places. Avoids use of general-purpose format parsers as a
943    * processing hotspot.
944    * 
945    * @param sb
946    * @param value
947    * @param dp
948    */
949   public static void appendPercentage(StringBuilder sb, float value, int dp)
950   {
951     /*
952      * rounding first
953      */
954     double d = value;
955     long factor = 1L;
956     for (int i = 0; i < dp; i++)
957     {
958       factor *= 10;
959     }
960     d *= factor;
961     d += 0.5;
962
963     /*
964      * integer part
965      */
966     value = (float) (d / factor);
967     sb.append((long) value);
968
969     /*
970      * decimal places
971      */
972     if (dp > 0)
973     {
974       sb.append(".");
975       while (dp > 0)
976       {
977         value = value - (int) value;
978         value *= 10;
979         sb.append((int) value);
980         dp--;
981       }
982     }
983   }
984 }