1 /* 2 * Jalview - A Sequence Alignment Editor and Viewer (2.11.1.4) 3 * Copyright (C) 2021 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 */ Format(String s)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 */ getHexString(java.awt.Color color)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 */ print(java.io.PrintStream s, String fmt, double x)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 */ print(java.io.PrintStream s, String fmt, long x)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 */ print(java.io.PrintStream s, String fmt, char x)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 */ print(java.io.PrintStream s, String fmt, String x)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 */ atoi(String s)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 */ atol(String s)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 */ parseLong(String s, int base)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 */ atof(String s)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 */ form(double x)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 */ form(long x)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 */ form(char c)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 */ form(String s)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 */ repeat(char c, int n)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 */ convert(long x, int n, int m, String d)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 */ pad(String r)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 */ sign(int s, String r)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 */ fixed_format(double d)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 */ exp_format(double d)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 toString()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 */ appendPercentage(StringBuilder sb, float value, int dp)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 } 987