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