1/* Writing Java ResourceBundles. 2 Copyright (C) 2001-2003, 2005-2007 Free Software Foundation, Inc. 3 Written by Bruno Haible <haible@clisp.cons.org>, 2001. 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 18#ifdef HAVE_CONFIG_H 19# include <config.h> 20#endif 21#include <alloca.h> 22 23/* Specification. */ 24#include "write-java.h" 25 26#include <errno.h> 27#include <limits.h> 28#include <stdbool.h> 29#include <stdlib.h> 30#include <stdio.h> 31#include <string.h> 32 33#include <sys/stat.h> 34#if !defined S_ISDIR && defined S_IFDIR 35# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 36#endif 37#if !S_IRUSR && S_IREAD 38# define S_IRUSR S_IREAD 39#endif 40#if !S_IRUSR 41# define S_IRUSR 00400 42#endif 43#if !S_IWUSR && S_IWRITE 44# define S_IWUSR S_IWRITE 45#endif 46#if !S_IWUSR 47# define S_IWUSR 00200 48#endif 49#if !S_IXUSR && S_IEXEC 50# define S_IXUSR S_IEXEC 51#endif 52#if !S_IXUSR 53# define S_IXUSR 00100 54#endif 55 56#include "c-ctype.h" 57#include "error.h" 58#include "xerror.h" 59#include "xvasprintf.h" 60#include "javacomp.h" 61#include "message.h" 62#include "msgfmt.h" 63#include "msgl-iconv.h" 64#include "plural-exp.h" 65#include "po-charset.h" 66#include "xalloc.h" 67#include "xmalloca.h" 68#include "filename.h" 69#include "fwriteerror.h" 70#include "clean-temp.h" 71#include "unistr.h" 72#include "gettext.h" 73 74#define _(str) gettext (str) 75 76 77/* Check that the resource name is a valid Java class name. To simplify 78 things, we allow only ASCII characters in the class name. 79 Return the number of dots in the class name, or -1 if not OK. */ 80static int 81check_resource_name (const char *name) 82{ 83 int ndots = 0; 84 const char *p = name; 85 86 for (;;) 87 { 88 /* First character, see Character.isJavaIdentifierStart. */ 89 if (!(c_isalpha (*p) || (*p == '$') || (*p == '_'))) 90 return -1; 91 /* Following characters, see Character.isJavaIdentifierPart. */ 92 do 93 p++; 94 while (c_isalpha (*p) || (*p == '$') || (*p == '_') || c_isdigit (*p)); 95 if (*p == '\0') 96 break; 97 if (*p != '.') 98 return -1; 99 p++; 100 ndots++; 101 } 102 return ndots; 103} 104 105 106/* Return the Java hash code of a string mod 2^31. 107 The Java String.hashCode() function returns the same values across 108 Java implementations. 109 (See http://www.javasoft.com/docs/books/jls/clarify.html) 110 It returns a signed 32-bit integer. We add a mod 2^31 afterwards; 111 this removes one bit but greatly simplifies the following "mod hash_size" 112 and "mod (hash_size - 2)" operations. */ 113static unsigned int 114string_hashcode (const char *str) 115{ 116 const char *str_limit = str + strlen (str); 117 int hash = 0; 118 while (str < str_limit) 119 { 120 unsigned int uc; 121 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); 122 if (uc < 0x10000) 123 /* Single UCS-2 'char'. */ 124 hash = 31 * hash + uc; 125 else 126 { 127 /* UTF-16 surrogate: two 'char's. */ 128 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10); 129 unsigned int uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); 130 hash = 31 * hash + uc1; 131 hash = 31 * hash + uc2; 132 } 133 } 134 return hash & 0x7fffffff; 135} 136 137 138/* Return the Java hash code of a (msgctxt, msgid) pair mod 2^31. */ 139static unsigned int 140msgid_hashcode (const char *msgctxt, const char *msgid) 141{ 142 if (msgctxt == NULL) 143 return string_hashcode (msgid); 144 else 145 { 146 size_t msgctxt_len = strlen (msgctxt); 147 size_t msgid_len = strlen (msgid); 148 size_t combined_len = msgctxt_len + 1 + msgid_len; 149 char *combined; 150 unsigned int result; 151 152 combined = (char *) xmalloca (combined_len); 153 memcpy (combined, msgctxt, msgctxt_len); 154 combined[msgctxt_len] = MSGCTXT_SEPARATOR; 155 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1); 156 157 result = string_hashcode (combined); 158 159 freea (combined); 160 161 return result; 162 } 163} 164 165 166/* Compute a good hash table size for the given set of msgids. */ 167static unsigned int 168compute_hashsize (message_list_ty *mlp, bool *collisionp) 169{ 170 /* This is an O(n^2) algorithm, but should be sufficient because few 171 programs have more than 1000 messages in a single domain. */ 172#define XXN 3 /* can be tweaked */ 173#define XXS 3 /* can be tweaked */ 174 unsigned int n = mlp->nitems; 175 unsigned int *hashcodes = 176 (unsigned int *) xmalloca (n * sizeof (unsigned int)); 177 unsigned int hashsize; 178 unsigned int best_hashsize; 179 unsigned int best_score; 180 size_t j; 181 182 for (j = 0; j < n; j++) 183 hashcodes[j] = msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid); 184 185 /* Try all numbers between n and 3*n. The score depends on the size of the 186 table -- the smaller the better -- and the number of collision lookups, 187 i.e. total number of times that 1 + (hashcode % (hashsize - 2)) 188 is added to the index during lookup. If there are collisions, only odd 189 hashsize values are allowed. */ 190 best_hashsize = 0; 191 best_score = UINT_MAX; 192 for (hashsize = n; hashsize <= XXN * n; hashsize++) 193 { 194 char *bitmap; 195 unsigned int score; 196 197 /* Premature end of the loop if all future scores are known to be 198 larger than the already reached best_score. This relies on the 199 ascending loop and on the fact that score >= hashsize. */ 200 if (hashsize >= best_score) 201 break; 202 203 bitmap = XNMALLOC (hashsize, char); 204 memset (bitmap, 0, hashsize); 205 206 score = 0; 207 for (j = 0; j < n; j++) 208 { 209 unsigned int idx = hashcodes[j] % hashsize; 210 211 if (bitmap[idx] != 0) 212 { 213 /* Collision. Cannot deal with it if hashsize is even. */ 214 if ((hashsize % 2) == 0) 215 /* Try next hashsize. */ 216 goto bad_hashsize; 217 else 218 { 219 unsigned int idx0 = idx; 220 unsigned int incr = 1 + (hashcodes[j] % (hashsize - 2)); 221 score += 2; /* Big penalty for the additional division */ 222 do 223 { 224 score++; /* Small penalty for each loop round */ 225 idx += incr; 226 if (idx >= hashsize) 227 idx -= hashsize; 228 if (idx == idx0) 229 /* Searching for a hole, we performed a whole round 230 across the table. This happens particularly 231 frequently if gcd(hashsize,incr) > 1. Try next 232 hashsize. */ 233 goto bad_hashsize; 234 } 235 while (bitmap[idx] != 0); 236 } 237 } 238 bitmap[idx] = 1; 239 } 240 241 /* Big hashsize also gives a penalty. */ 242 score = XXS * score + hashsize; 243 244 /* If for any incr between 1 and hashsize - 2, an whole round 245 (idx0, idx0 + incr, ...) is occupied, and the lookup function 246 must deal with collisions, then some inputs would lead to 247 an endless loop in the lookup function. */ 248 if (score > hashsize) 249 { 250 unsigned int incr; 251 252 /* Since the set { idx0, idx0 + incr, ... } depends only on idx0 253 and gcd(hashsize,incr), we only need to conside incr that 254 divides hashsize. */ 255 for (incr = 1; incr <= hashsize / 2; incr++) 256 if ((hashsize % incr) == 0) 257 { 258 unsigned int idx0; 259 260 for (idx0 = 0; idx0 < incr; idx0++) 261 { 262 bool full = true; 263 unsigned int idx; 264 265 for (idx = idx0; idx < hashsize; idx += incr) 266 if (bitmap[idx] == 0) 267 { 268 full = false; 269 break; 270 } 271 if (full) 272 /* A whole round is occupied. */ 273 goto bad_hashsize; 274 } 275 } 276 } 277 278 if (false) 279 bad_hashsize: 280 score = UINT_MAX; 281 282 free (bitmap); 283 284 if (score < best_score) 285 { 286 best_score = score; 287 best_hashsize = hashsize; 288 } 289 } 290 if (best_hashsize == 0 || best_score < best_hashsize) 291 abort (); 292 293 freea (hashcodes); 294 295 /* There are collisions if and only if best_score > best_hashsize. */ 296 *collisionp = (best_score > best_hashsize); 297 return best_hashsize; 298} 299 300 301struct table_item { unsigned int index; message_ty *mp; }; 302 303static int 304compare_index (const void *pval1, const void *pval2) 305{ 306 return (int)((const struct table_item *) pval1)->index 307 - (int)((const struct table_item *) pval2)->index; 308} 309 310/* Compute the list of messages and table indices, sorted according to the 311 indices. */ 312static struct table_item * 313compute_table_items (message_list_ty *mlp, unsigned int hashsize) 314{ 315 unsigned int n = mlp->nitems; 316 struct table_item *arr = XNMALLOC (n, struct table_item); 317 char *bitmap; 318 size_t j; 319 320 bitmap = XNMALLOC (hashsize, char); 321 memset (bitmap, 0, hashsize); 322 323 for (j = 0; j < n; j++) 324 { 325 unsigned int hashcode = 326 msgid_hashcode (mlp->item[j]->msgctxt, mlp->item[j]->msgid); 327 unsigned int idx = hashcode % hashsize; 328 329 if (bitmap[idx] != 0) 330 { 331 unsigned int incr = 1 + (hashcode % (hashsize - 2)); 332 do 333 { 334 idx += incr; 335 if (idx >= hashsize) 336 idx -= hashsize; 337 } 338 while (bitmap[idx] != 0); 339 } 340 bitmap[idx] = 1; 341 342 arr[j].index = idx; 343 arr[j].mp = mlp->item[j]; 344 } 345 346 free (bitmap); 347 348 qsort (arr, n, sizeof (arr[0]), compare_index); 349 350 return arr; 351} 352 353 354/* Write a string in Java Unicode notation to the given stream. */ 355static void 356write_java_string (FILE *stream, const char *str) 357{ 358 static const char hexdigit[] = "0123456789abcdef"; 359 const char *str_limit = str + strlen (str); 360 361 fprintf (stream, "\""); 362 while (str < str_limit) 363 { 364 unsigned int uc; 365 str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str); 366 if (uc < 0x10000) 367 { 368 /* Single UCS-2 'char'. */ 369 if (uc == 0x000a) 370 fprintf (stream, "\\n"); 371 else if (uc == 0x000d) 372 fprintf (stream, "\\r"); 373 else if (uc == 0x0022) 374 fprintf (stream, "\\\""); 375 else if (uc == 0x005c) 376 fprintf (stream, "\\\\"); 377 else if (uc >= 0x0020 && uc < 0x007f) 378 fprintf (stream, "%c", uc); 379 else 380 fprintf (stream, "\\u%c%c%c%c", 381 hexdigit[(uc >> 12) & 0x0f], hexdigit[(uc >> 8) & 0x0f], 382 hexdigit[(uc >> 4) & 0x0f], hexdigit[uc & 0x0f]); 383 } 384 else 385 { 386 /* UTF-16 surrogate: two 'char's. */ 387 unsigned int uc1 = 0xd800 + ((uc - 0x10000) >> 10); 388 unsigned int uc2 = 0xdc00 + ((uc - 0x10000) & 0x3ff); 389 fprintf (stream, "\\u%c%c%c%c", 390 hexdigit[(uc1 >> 12) & 0x0f], hexdigit[(uc1 >> 8) & 0x0f], 391 hexdigit[(uc1 >> 4) & 0x0f], hexdigit[uc1 & 0x0f]); 392 fprintf (stream, "\\u%c%c%c%c", 393 hexdigit[(uc2 >> 12) & 0x0f], hexdigit[(uc2 >> 8) & 0x0f], 394 hexdigit[(uc2 >> 4) & 0x0f], hexdigit[uc2 & 0x0f]); 395 } 396 } 397 fprintf (stream, "\""); 398} 399 400 401/* Write a (msgctxt, msgid) pair as a string in Java Unicode notation to the 402 given stream. */ 403static void 404write_java_msgid (FILE *stream, message_ty *mp) 405{ 406 const char *msgctxt = mp->msgctxt; 407 const char *msgid = mp->msgid; 408 409 if (msgctxt == NULL) 410 write_java_string (stream, msgid); 411 else 412 { 413 size_t msgctxt_len = strlen (msgctxt); 414 size_t msgid_len = strlen (msgid); 415 size_t combined_len = msgctxt_len + 1 + msgid_len; 416 char *combined; 417 418 combined = (char *) xmalloca (combined_len); 419 memcpy (combined, msgctxt, msgctxt_len); 420 combined[msgctxt_len] = MSGCTXT_SEPARATOR; 421 memcpy (combined + msgctxt_len + 1, msgid, msgid_len + 1); 422 423 write_java_string (stream, combined); 424 425 freea (combined); 426 } 427} 428 429 430/* Write Java code that returns the value for a message. If the message 431 has plural forms, it is an expression of type String[], otherwise it is 432 an expression of type String. */ 433static void 434write_java_msgstr (FILE *stream, message_ty *mp) 435{ 436 if (mp->msgid_plural != NULL) 437 { 438 bool first; 439 const char *p; 440 441 fprintf (stream, "new java.lang.String[] { "); 442 for (p = mp->msgstr, first = true; 443 p < mp->msgstr + mp->msgstr_len; 444 p += strlen (p) + 1, first = false) 445 { 446 if (!first) 447 fprintf (stream, ", "); 448 write_java_string (stream, p); 449 } 450 fprintf (stream, " }"); 451 } 452 else 453 { 454 if (mp->msgstr_len != strlen (mp->msgstr) + 1) 455 abort (); 456 457 write_java_string (stream, mp->msgstr); 458 } 459} 460 461 462/* Writes the body of the function which returns the local value for a key 463 named 'msgid'. */ 464static void 465write_lookup_code (FILE *stream, unsigned int hashsize, bool collisions) 466{ 467 fprintf (stream, " int hash_val = msgid.hashCode() & 0x7fffffff;\n"); 468 fprintf (stream, " int idx = (hash_val %% %d) << 1;\n", hashsize); 469 if (collisions) 470 { 471 fprintf (stream, " {\n"); 472 fprintf (stream, " java.lang.Object found = table[idx];\n"); 473 fprintf (stream, " if (found == null)\n"); 474 fprintf (stream, " return null;\n"); 475 fprintf (stream, " if (msgid.equals(found))\n"); 476 fprintf (stream, " return table[idx + 1];\n"); 477 fprintf (stream, " }\n"); 478 fprintf (stream, " int incr = ((hash_val %% %d) + 1) << 1;\n", 479 hashsize - 2); 480 fprintf (stream, " for (;;) {\n"); 481 fprintf (stream, " idx += incr;\n"); 482 fprintf (stream, " if (idx >= %d)\n", 2 * hashsize); 483 fprintf (stream, " idx -= %d;\n", 2 * hashsize); 484 fprintf (stream, " java.lang.Object found = table[idx];\n"); 485 fprintf (stream, " if (found == null)\n"); 486 fprintf (stream, " return null;\n"); 487 fprintf (stream, " if (msgid.equals(found))\n"); 488 fprintf (stream, " return table[idx + 1];\n"); 489 fprintf (stream, " }\n"); 490 } 491 else 492 { 493 fprintf (stream, " java.lang.Object found = table[idx];\n"); 494 fprintf (stream, " if (found != null && msgid.equals(found))\n"); 495 fprintf (stream, " return table[idx + 1];\n"); 496 fprintf (stream, " return null;\n"); 497 } 498} 499 500 501/* Tests whether a plural expression, evaluated according to the C rules, 502 can only produce the values 0 and 1. */ 503static bool 504is_expression_boolean (struct expression *exp) 505{ 506 switch (exp->operation) 507 { 508 case var: 509 case mult: 510 case divide: 511 case module: 512 case plus: 513 case minus: 514 return false; 515 case lnot: 516 case less_than: 517 case greater_than: 518 case less_or_equal: 519 case greater_or_equal: 520 case equal: 521 case not_equal: 522 case land: 523 case lor: 524 return true; 525 case num: 526 return (exp->val.num == 0 || exp->val.num == 1); 527 case qmop: 528 return is_expression_boolean (exp->val.args[1]) 529 && is_expression_boolean (exp->val.args[2]); 530 default: 531 abort (); 532 } 533} 534 535 536/* Write Java code that evaluates a plural expression according to the C rules. 537 The variable is called 'n'. */ 538static void 539write_java_expression (FILE *stream, const struct expression *exp, bool as_boolean) 540{ 541 /* We use parentheses everywhere. This frees us from tracking the priority 542 of arithmetic operators. */ 543 if (as_boolean) 544 { 545 /* Emit a Java expression of type 'boolean'. */ 546 switch (exp->operation) 547 { 548 case num: 549 fprintf (stream, "%s", exp->val.num ? "true" : "false"); 550 return; 551 case lnot: 552 fprintf (stream, "(!"); 553 write_java_expression (stream, exp->val.args[0], true); 554 fprintf (stream, ")"); 555 return; 556 case less_than: 557 fprintf (stream, "("); 558 write_java_expression (stream, exp->val.args[0], false); 559 fprintf (stream, " < "); 560 write_java_expression (stream, exp->val.args[1], false); 561 fprintf (stream, ")"); 562 return; 563 case greater_than: 564 fprintf (stream, "("); 565 write_java_expression (stream, exp->val.args[0], false); 566 fprintf (stream, " > "); 567 write_java_expression (stream, exp->val.args[1], false); 568 fprintf (stream, ")"); 569 return; 570 case less_or_equal: 571 fprintf (stream, "("); 572 write_java_expression (stream, exp->val.args[0], false); 573 fprintf (stream, " <= "); 574 write_java_expression (stream, exp->val.args[1], false); 575 fprintf (stream, ")"); 576 return; 577 case greater_or_equal: 578 fprintf (stream, "("); 579 write_java_expression (stream, exp->val.args[0], false); 580 fprintf (stream, " >= "); 581 write_java_expression (stream, exp->val.args[1], false); 582 fprintf (stream, ")"); 583 return; 584 case equal: 585 fprintf (stream, "("); 586 write_java_expression (stream, exp->val.args[0], false); 587 fprintf (stream, " == "); 588 write_java_expression (stream, exp->val.args[1], false); 589 fprintf (stream, ")"); 590 return; 591 case not_equal: 592 fprintf (stream, "("); 593 write_java_expression (stream, exp->val.args[0], false); 594 fprintf (stream, " != "); 595 write_java_expression (stream, exp->val.args[1], false); 596 fprintf (stream, ")"); 597 return; 598 case land: 599 fprintf (stream, "("); 600 write_java_expression (stream, exp->val.args[0], true); 601 fprintf (stream, " && "); 602 write_java_expression (stream, exp->val.args[1], true); 603 fprintf (stream, ")"); 604 return; 605 case lor: 606 fprintf (stream, "("); 607 write_java_expression (stream, exp->val.args[0], true); 608 fprintf (stream, " || "); 609 write_java_expression (stream, exp->val.args[1], true); 610 fprintf (stream, ")"); 611 return; 612 case qmop: 613 if (is_expression_boolean (exp->val.args[1]) 614 && is_expression_boolean (exp->val.args[2])) 615 { 616 fprintf (stream, "("); 617 write_java_expression (stream, exp->val.args[0], true); 618 fprintf (stream, " ? "); 619 write_java_expression (stream, exp->val.args[1], true); 620 fprintf (stream, " : "); 621 write_java_expression (stream, exp->val.args[2], true); 622 fprintf (stream, ")"); 623 return; 624 } 625 /*FALLTHROUGH*/ 626 case var: 627 case mult: 628 case divide: 629 case module: 630 case plus: 631 case minus: 632 fprintf (stream, "("); 633 write_java_expression (stream, exp, false); 634 fprintf (stream, " != 0)"); 635 return; 636 default: 637 abort (); 638 } 639 } 640 else 641 { 642 /* Emit a Java expression of type 'long'. */ 643 switch (exp->operation) 644 { 645 case var: 646 fprintf (stream, "n"); 647 return; 648 case num: 649 fprintf (stream, "%lu", exp->val.num); 650 return; 651 case mult: 652 fprintf (stream, "("); 653 write_java_expression (stream, exp->val.args[0], false); 654 fprintf (stream, " * "); 655 write_java_expression (stream, exp->val.args[1], false); 656 fprintf (stream, ")"); 657 return; 658 case divide: 659 fprintf (stream, "("); 660 write_java_expression (stream, exp->val.args[0], false); 661 fprintf (stream, " / "); 662 write_java_expression (stream, exp->val.args[1], false); 663 fprintf (stream, ")"); 664 return; 665 case module: 666 fprintf (stream, "("); 667 write_java_expression (stream, exp->val.args[0], false); 668 fprintf (stream, " %% "); 669 write_java_expression (stream, exp->val.args[1], false); 670 fprintf (stream, ")"); 671 return; 672 case plus: 673 fprintf (stream, "("); 674 write_java_expression (stream, exp->val.args[0], false); 675 fprintf (stream, " + "); 676 write_java_expression (stream, exp->val.args[1], false); 677 fprintf (stream, ")"); 678 return; 679 case minus: 680 fprintf (stream, "("); 681 write_java_expression (stream, exp->val.args[0], false); 682 fprintf (stream, " - "); 683 write_java_expression (stream, exp->val.args[1], false); 684 fprintf (stream, ")"); 685 return; 686 case qmop: 687 fprintf (stream, "("); 688 write_java_expression (stream, exp->val.args[0], true); 689 fprintf (stream, " ? "); 690 write_java_expression (stream, exp->val.args[1], false); 691 fprintf (stream, " : "); 692 write_java_expression (stream, exp->val.args[2], false); 693 fprintf (stream, ")"); 694 return; 695 case lnot: 696 case less_than: 697 case greater_than: 698 case less_or_equal: 699 case greater_or_equal: 700 case equal: 701 case not_equal: 702 case land: 703 case lor: 704 fprintf (stream, "("); 705 write_java_expression (stream, exp, true); 706 fprintf (stream, " ? 1 : 0)"); 707 return; 708 default: 709 abort (); 710 } 711 } 712} 713 714 715/* Write the Java code for the ResourceBundle subclass to the given stream. 716 Note that we use fully qualified class names and no "import" statements, 717 because applications can have their own classes called X.Y.ResourceBundle 718 or X.Y.String. */ 719static void 720write_java_code (FILE *stream, const char *class_name, message_list_ty *mlp, 721 bool assume_java2) 722{ 723 const char *last_dot; 724 unsigned int plurals; 725 size_t j; 726 727 fprintf (stream, 728 "/* Automatically generated by GNU msgfmt. Do not modify! */\n"); 729 last_dot = strrchr (class_name, '.'); 730 if (last_dot != NULL) 731 { 732 fprintf (stream, "package "); 733 fwrite (class_name, 1, last_dot - class_name, stream); 734 fprintf (stream, ";\npublic class %s", last_dot + 1); 735 } 736 else 737 fprintf (stream, "public class %s", class_name); 738 fprintf (stream, " extends java.util.ResourceBundle {\n"); 739 740 /* Determine whether there are plural messages. */ 741 plurals = 0; 742 for (j = 0; j < mlp->nitems; j++) 743 if (mlp->item[j]->msgid_plural != NULL) 744 plurals++; 745 746 if (assume_java2) 747 { 748 unsigned int hashsize; 749 bool collisions; 750 struct table_item *table_items; 751 const char *table_eltype; 752 753 /* Determine the hash table size and whether it leads to collisions. */ 754 hashsize = compute_hashsize (mlp, &collisions); 755 756 /* Determines which indices in the table contain a message. The others 757 are null. */ 758 table_items = compute_table_items (mlp, hashsize); 759 760 /* Emit the table of pairs (msgid, msgstr). If there are plurals, 761 it is of type Object[], otherwise of type String[]. We use a static 762 code block because that makes less code: The Java compilers also 763 generate code for the 'null' entries, which is dumb. */ 764 table_eltype = (plurals ? "java.lang.Object" : "java.lang.String"); 765 fprintf (stream, " private static final %s[] table;\n", table_eltype); 766 fprintf (stream, " static {\n"); 767 fprintf (stream, " %s[] t = new %s[%d];\n", table_eltype, table_eltype, 768 2 * hashsize); 769 for (j = 0; j < mlp->nitems; j++) 770 { 771 struct table_item *ti = &table_items[j]; 772 773 fprintf (stream, " t[%d] = ", 2 * ti->index); 774 write_java_msgid (stream, ti->mp); 775 fprintf (stream, ";\n"); 776 fprintf (stream, " t[%d] = ", 2 * ti->index + 1); 777 write_java_msgstr (stream, ti->mp); 778 fprintf (stream, ";\n"); 779 } 780 fprintf (stream, " table = t;\n"); 781 fprintf (stream, " }\n"); 782 783 /* Emit the msgid_plural strings. Only used by msgunfmt. */ 784 if (plurals) 785 { 786 bool first; 787 fprintf (stream, " public static final java.lang.String[] get_msgid_plural_table () {\n"); 788 fprintf (stream, " return new java.lang.String[] { "); 789 first = true; 790 for (j = 0; j < mlp->nitems; j++) 791 { 792 struct table_item *ti = &table_items[j]; 793 if (ti->mp->msgid_plural != NULL) 794 { 795 if (!first) 796 fprintf (stream, ", "); 797 write_java_string (stream, ti->mp->msgid_plural); 798 first = false; 799 } 800 } 801 fprintf (stream, " };\n"); 802 fprintf (stream, " }\n"); 803 } 804 805 if (plurals) 806 { 807 /* Emit the lookup function. It is a common subroutine for 808 handleGetObject and ngettext. */ 809 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n"); 810 write_lookup_code (stream, hashsize, collisions); 811 fprintf (stream, " }\n"); 812 } 813 814 /* Emit the handleGetObject function. It is declared abstract in 815 ResourceBundle. It implements a local version of gettext. */ 816 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n"); 817 if (plurals) 818 { 819 fprintf (stream, " java.lang.Object value = lookup(msgid);\n"); 820 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n"); 821 } 822 else 823 write_lookup_code (stream, hashsize, collisions); 824 fprintf (stream, " }\n"); 825 826 /* Emit the getKeys function. It is declared abstract in ResourceBundle. 827 The inner class is not avoidable. */ 828 fprintf (stream, " public java.util.Enumeration getKeys () {\n"); 829 fprintf (stream, " return\n"); 830 fprintf (stream, " new java.util.Enumeration() {\n"); 831 fprintf (stream, " private int idx = 0;\n"); 832 fprintf (stream, " { while (idx < %d && table[idx] == null) idx += 2; }\n", 833 2 * hashsize); 834 fprintf (stream, " public boolean hasMoreElements () {\n"); 835 fprintf (stream, " return (idx < %d);\n", 2 * hashsize); 836 fprintf (stream, " }\n"); 837 fprintf (stream, " public java.lang.Object nextElement () {\n"); 838 fprintf (stream, " java.lang.Object key = table[idx];\n"); 839 fprintf (stream, " do idx += 2; while (idx < %d && table[idx] == null);\n", 840 2 * hashsize); 841 fprintf (stream, " return key;\n"); 842 fprintf (stream, " }\n"); 843 fprintf (stream, " };\n"); 844 fprintf (stream, " }\n"); 845 } 846 else 847 { 848 /* Java 1.1.x uses a different hash function. If compatibility with 849 this Java version is required, the hash table must be built at run time, 850 not at compile time. */ 851 fprintf (stream, " private static final java.util.Hashtable table;\n"); 852 fprintf (stream, " static {\n"); 853 fprintf (stream, " java.util.Hashtable t = new java.util.Hashtable();\n"); 854 for (j = 0; j < mlp->nitems; j++) 855 { 856 fprintf (stream, " t.put("); 857 write_java_msgid (stream, mlp->item[j]); 858 fprintf (stream, ","); 859 write_java_msgstr (stream, mlp->item[j]); 860 fprintf (stream, ");\n"); 861 } 862 fprintf (stream, " table = t;\n"); 863 fprintf (stream, " }\n"); 864 865 /* Emit the msgid_plural strings. Only used by msgunfmt. */ 866 if (plurals) 867 { 868 fprintf (stream, " public static final java.util.Hashtable get_msgid_plural_table () {\n"); 869 fprintf (stream, " java.util.Hashtable p = new java.util.Hashtable();\n"); 870 for (j = 0; j < mlp->nitems; j++) 871 if (mlp->item[j]->msgid_plural != NULL) 872 { 873 fprintf (stream, " p.put("); 874 write_java_msgid (stream, mlp->item[j]); 875 fprintf (stream, ","); 876 write_java_string (stream, mlp->item[j]->msgid_plural); 877 fprintf (stream, ");\n"); 878 } 879 fprintf (stream, " return p;\n"); 880 fprintf (stream, " }\n"); 881 } 882 883 if (plurals) 884 { 885 /* Emit the lookup function. It is a common subroutine for 886 handleGetObject and ngettext. */ 887 fprintf (stream, " public java.lang.Object lookup (java.lang.String msgid) {\n"); 888 fprintf (stream, " return table.get(msgid);\n"); 889 fprintf (stream, " }\n"); 890 } 891 892 /* Emit the handleGetObject function. It is declared abstract in 893 ResourceBundle. It implements a local version of gettext. */ 894 fprintf (stream, " public java.lang.Object handleGetObject (java.lang.String msgid) throws java.util.MissingResourceException {\n"); 895 if (plurals) 896 { 897 fprintf (stream, " java.lang.Object value = table.get(msgid);\n"); 898 fprintf (stream, " return (value instanceof java.lang.String[] ? ((java.lang.String[])value)[0] : value);\n"); 899 } 900 else 901 fprintf (stream, " return table.get(msgid);\n"); 902 fprintf (stream, " }\n"); 903 904 /* Emit the getKeys function. It is declared abstract in 905 ResourceBundle. */ 906 fprintf (stream, " public java.util.Enumeration getKeys () {\n"); 907 fprintf (stream, " return table.keys();\n"); 908 fprintf (stream, " }\n"); 909 } 910 911 /* Emit the pluralEval function. It is a subroutine for ngettext. */ 912 if (plurals) 913 { 914 message_ty *header_entry; 915 const struct expression *plural; 916 unsigned long int nplurals; 917 918 header_entry = message_list_search (mlp, NULL, ""); 919 extract_plural_expression (header_entry ? header_entry->msgstr : NULL, 920 &plural, &nplurals); 921 922 fprintf (stream, " public static long pluralEval (long n) {\n"); 923 fprintf (stream, " return "); 924 write_java_expression (stream, plural, false); 925 fprintf (stream, ";\n"); 926 fprintf (stream, " }\n"); 927 } 928 929 /* Emit the getParent function. It is a subroutine for ngettext. */ 930 fprintf (stream, " public java.util.ResourceBundle getParent () {\n"); 931 fprintf (stream, " return parent;\n"); 932 fprintf (stream, " }\n"); 933 934 fprintf (stream, "}\n"); 935} 936 937 938int 939msgdomain_write_java (message_list_ty *mlp, const char *canon_encoding, 940 const char *resource_name, const char *locale_name, 941 const char *directory, 942 bool assume_java2) 943{ 944 int retval; 945 struct temp_dir *tmpdir; 946 int ndots; 947 char *class_name; 948 char **subdirs; 949 char *java_file_name; 950 FILE *java_file; 951 const char *java_sources[1]; 952 953 /* If no entry for this resource/domain, don't even create the file. */ 954 if (mlp->nitems == 0) 955 return 0; 956 957 retval = 1; 958 959 /* Convert the messages to Unicode. */ 960 iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL); 961 962 /* Create a temporary directory where we can put the Java file. */ 963 tmpdir = create_temp_dir ("msg", NULL, false); 964 if (tmpdir == NULL) 965 goto quit1; 966 967 /* Assign a default value to the resource name. */ 968 if (resource_name == NULL) 969 resource_name = "Messages"; 970 971 /* Prepare the list of subdirectories. */ 972 ndots = check_resource_name (resource_name); 973 if (ndots < 0) 974 { 975 error (0, 0, _("not a valid Java class name: %s"), resource_name); 976 goto quit2; 977 } 978 979 if (locale_name != NULL) 980 class_name = xasprintf ("%s_%s", resource_name, locale_name); 981 else 982 class_name = xstrdup (resource_name); 983 984 subdirs = (ndots > 0 ? (char **) xmalloca (ndots * sizeof (char *)) : NULL); 985 { 986 const char *p; 987 const char *last_dir; 988 int i; 989 990 last_dir = tmpdir->dir_name; 991 p = resource_name; 992 for (i = 0; i < ndots; i++) 993 { 994 const char *q = strchr (p, '.'); 995 size_t n = q - p; 996 char *part = (char *) xmalloca (n + 1); 997 memcpy (part, p, n); 998 part[n] = '\0'; 999 subdirs[i] = concatenated_filename (last_dir, part, NULL); 1000 freea (part); 1001 last_dir = subdirs[i]; 1002 p = q + 1; 1003 } 1004 1005 if (locale_name != NULL) 1006 { 1007 char *suffix = xasprintf ("_%s.java", locale_name); 1008 java_file_name = concatenated_filename (last_dir, p, suffix); 1009 free (suffix); 1010 } 1011 else 1012 java_file_name = concatenated_filename (last_dir, p, ".java"); 1013 } 1014 1015 /* Create the subdirectories. This is needed because some older Java 1016 compilers verify that the source of class A.B.C really sits in a 1017 directory whose name ends in /A/B. */ 1018 { 1019 int i; 1020 1021 for (i = 0; i < ndots; i++) 1022 { 1023 register_temp_subdir (tmpdir, subdirs[i]); 1024 if (mkdir (subdirs[i], S_IRUSR | S_IWUSR | S_IXUSR) < 0) 1025 { 1026 error (0, errno, _("failed to create \"%s\""), subdirs[i]); 1027 unregister_temp_subdir (tmpdir, subdirs[i]); 1028 goto quit3; 1029 } 1030 } 1031 } 1032 1033 /* Create the Java file. */ 1034 register_temp_file (tmpdir, java_file_name); 1035 java_file = fopen_temp (java_file_name, "w"); 1036 if (java_file == NULL) 1037 { 1038 error (0, errno, _("failed to create \"%s\""), java_file_name); 1039 unregister_temp_file (tmpdir, java_file_name); 1040 goto quit3; 1041 } 1042 1043 write_java_code (java_file, class_name, mlp, assume_java2); 1044 1045 if (fwriteerror_temp (java_file)) 1046 { 1047 error (0, errno, _("error while writing \"%s\" file"), java_file_name); 1048 goto quit3; 1049 } 1050 1051 /* Compile the Java file to a .class file. 1052 directory must be non-NULL, because when the -d option is omitted, the 1053 Java compilers create the class files in the source file's directory - 1054 which is in a temporary directory in our case. */ 1055 java_sources[0] = java_file_name; 1056 if (compile_java_class (java_sources, 1, NULL, 0, "1.3", "1.1", directory, 1057 true, false, true, verbose)) 1058 { 1059 if (!verbose) 1060 error (0, 0, _("\ 1061compilation of Java class failed, please try --verbose or set $JAVAC")); 1062 else 1063 error (0, 0, _("\ 1064compilation of Java class failed, please try to set $JAVAC")); 1065 goto quit3; 1066 } 1067 1068 retval = 0; 1069 1070 quit3: 1071 { 1072 int i; 1073 free (java_file_name); 1074 for (i = 0; i < ndots; i++) 1075 free (subdirs[i]); 1076 } 1077 freea (subdirs); 1078 free (class_name); 1079 quit2: 1080 cleanup_temp_dir (tmpdir); 1081 quit1: 1082 return retval; 1083} 1084