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