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